@fluidframework/merge-tree 2.20.0 → 2.22.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 (183) hide show
  1. package/.eslintrc.cjs +0 -1
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +1 -0
  4. package/dist/attributionCollection.js +5 -7
  5. package/dist/attributionCollection.js.map +1 -1
  6. package/dist/localReference.js +6 -8
  7. package/dist/localReference.js.map +1 -1
  8. package/dist/mergeTree.d.ts.map +1 -1
  9. package/dist/mergeTree.js +69 -34
  10. package/dist/mergeTree.js.map +1 -1
  11. package/dist/mergeTreeNodes.d.ts +15 -4
  12. package/dist/mergeTreeNodes.d.ts.map +1 -1
  13. package/dist/mergeTreeNodes.js +1 -1
  14. package/dist/mergeTreeNodes.js.map +1 -1
  15. package/dist/partialLengths.d.ts +114 -144
  16. package/dist/partialLengths.d.ts.map +1 -1
  17. package/dist/partialLengths.js +431 -525
  18. package/dist/partialLengths.js.map +1 -1
  19. package/dist/perspective.d.ts +10 -1
  20. package/dist/perspective.d.ts.map +1 -1
  21. package/dist/perspective.js +10 -1
  22. package/dist/perspective.js.map +1 -1
  23. package/dist/properties.d.ts.map +1 -1
  24. package/dist/properties.js +2 -3
  25. package/dist/properties.js.map +1 -1
  26. package/dist/revertibles.js +3 -3
  27. package/dist/revertibles.js.map +1 -1
  28. package/dist/segmentInfos.d.ts +3 -0
  29. package/dist/segmentInfos.d.ts.map +1 -1
  30. package/dist/segmentInfos.js.map +1 -1
  31. package/dist/segmentPropertiesManager.js +3 -3
  32. package/dist/segmentPropertiesManager.js.map +1 -1
  33. package/dist/snapshotLoader.js +2 -2
  34. package/dist/snapshotLoader.js.map +1 -1
  35. package/dist/sortedSegmentSet.d.ts +5 -3
  36. package/dist/sortedSegmentSet.d.ts.map +1 -1
  37. package/dist/sortedSegmentSet.js +33 -41
  38. package/dist/sortedSegmentSet.js.map +1 -1
  39. package/dist/sortedSet.d.ts +20 -3
  40. package/dist/sortedSet.d.ts.map +1 -1
  41. package/dist/sortedSet.js +23 -14
  42. package/dist/sortedSet.js.map +1 -1
  43. package/dist/test/Snapshot.perf.spec.js +1 -1
  44. package/dist/test/Snapshot.perf.spec.js.map +1 -1
  45. package/dist/test/client.applyMsg.spec.js +20 -0
  46. package/dist/test/client.applyMsg.spec.js.map +1 -1
  47. package/dist/test/client.applyStashedOpFarm.spec.js +1 -1
  48. package/dist/test/client.applyStashedOpFarm.spec.js.map +1 -1
  49. package/dist/test/client.attributionFarm.spec.js +1 -1
  50. package/dist/test/client.attributionFarm.spec.js.map +1 -1
  51. package/dist/test/client.localReference.spec.js +48 -0
  52. package/dist/test/client.localReference.spec.js.map +1 -1
  53. package/dist/test/client.obliterateFarm.spec.d.ts +12 -0
  54. package/dist/test/client.obliterateFarm.spec.d.ts.map +1 -0
  55. package/dist/test/client.obliterateFarm.spec.js +89 -0
  56. package/dist/test/client.obliterateFarm.spec.js.map +1 -0
  57. package/dist/test/client.reconnectFarm.spec.js +1 -1
  58. package/dist/test/client.reconnectFarm.spec.js.map +1 -1
  59. package/dist/test/client.searchForMarker.spec.js +2 -2
  60. package/dist/test/client.searchForMarker.spec.js.map +1 -1
  61. package/dist/test/mergeTreeOperationRunner.d.ts +7 -2
  62. package/dist/test/mergeTreeOperationRunner.d.ts.map +1 -1
  63. package/dist/test/mergeTreeOperationRunner.js +31 -14
  64. package/dist/test/mergeTreeOperationRunner.js.map +1 -1
  65. package/dist/test/obliterate.concurrent.spec.js +45 -1
  66. package/dist/test/obliterate.concurrent.spec.js.map +1 -1
  67. package/dist/test/obliterate.rangeExpansion.spec.js +81 -5
  68. package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -1
  69. package/dist/test/obliterate.spec.js +3 -3
  70. package/dist/test/obliterate.spec.js.map +1 -1
  71. package/dist/test/obliterateOperations.d.ts +15 -0
  72. package/dist/test/obliterateOperations.d.ts.map +1 -0
  73. package/dist/test/obliterateOperations.js +132 -0
  74. package/dist/test/obliterateOperations.js.map +1 -0
  75. package/dist/test/partialSyncHelper.d.ts +42 -0
  76. package/dist/test/partialSyncHelper.d.ts.map +1 -0
  77. package/dist/test/partialSyncHelper.js +96 -0
  78. package/dist/test/partialSyncHelper.js.map +1 -0
  79. package/dist/test/revertibles.spec.js +3 -3
  80. package/dist/test/revertibles.spec.js.map +1 -1
  81. package/dist/test/sortedSegmentSet.spec.js +21 -0
  82. package/dist/test/sortedSegmentSet.spec.js.map +1 -1
  83. package/dist/test/testClient.d.ts +1 -1
  84. package/dist/test/testClient.d.ts.map +1 -1
  85. package/dist/test/testClient.js +1 -0
  86. package/dist/test/testClient.js.map +1 -1
  87. package/dist/test/testUtils.js +2 -2
  88. package/dist/test/testUtils.js.map +1 -1
  89. package/lib/attributionCollection.js +5 -7
  90. package/lib/attributionCollection.js.map +1 -1
  91. package/lib/localReference.js +6 -8
  92. package/lib/localReference.js.map +1 -1
  93. package/lib/mergeTree.d.ts.map +1 -1
  94. package/lib/mergeTree.js +69 -34
  95. package/lib/mergeTree.js.map +1 -1
  96. package/lib/mergeTreeNodes.d.ts +15 -4
  97. package/lib/mergeTreeNodes.d.ts.map +1 -1
  98. package/lib/mergeTreeNodes.js +1 -1
  99. package/lib/mergeTreeNodes.js.map +1 -1
  100. package/lib/partialLengths.d.ts +114 -144
  101. package/lib/partialLengths.d.ts.map +1 -1
  102. package/lib/partialLengths.js +432 -525
  103. package/lib/partialLengths.js.map +1 -1
  104. package/lib/perspective.d.ts +10 -1
  105. package/lib/perspective.d.ts.map +1 -1
  106. package/lib/perspective.js +10 -1
  107. package/lib/perspective.js.map +1 -1
  108. package/lib/properties.d.ts.map +1 -1
  109. package/lib/properties.js +2 -3
  110. package/lib/properties.js.map +1 -1
  111. package/lib/revertibles.js +3 -3
  112. package/lib/revertibles.js.map +1 -1
  113. package/lib/segmentInfos.d.ts +3 -0
  114. package/lib/segmentInfos.d.ts.map +1 -1
  115. package/lib/segmentInfos.js.map +1 -1
  116. package/lib/segmentPropertiesManager.js +3 -3
  117. package/lib/segmentPropertiesManager.js.map +1 -1
  118. package/lib/snapshotLoader.js +2 -2
  119. package/lib/snapshotLoader.js.map +1 -1
  120. package/lib/sortedSegmentSet.d.ts +5 -3
  121. package/lib/sortedSegmentSet.d.ts.map +1 -1
  122. package/lib/sortedSegmentSet.js +33 -41
  123. package/lib/sortedSegmentSet.js.map +1 -1
  124. package/lib/sortedSet.d.ts +20 -3
  125. package/lib/sortedSet.d.ts.map +1 -1
  126. package/lib/sortedSet.js +23 -14
  127. package/lib/sortedSet.js.map +1 -1
  128. package/lib/test/Snapshot.perf.spec.js +1 -1
  129. package/lib/test/Snapshot.perf.spec.js.map +1 -1
  130. package/lib/test/client.applyMsg.spec.js +20 -0
  131. package/lib/test/client.applyMsg.spec.js.map +1 -1
  132. package/lib/test/client.applyStashedOpFarm.spec.js +1 -1
  133. package/lib/test/client.applyStashedOpFarm.spec.js.map +1 -1
  134. package/lib/test/client.attributionFarm.spec.js +1 -1
  135. package/lib/test/client.attributionFarm.spec.js.map +1 -1
  136. package/lib/test/client.localReference.spec.js +48 -0
  137. package/lib/test/client.localReference.spec.js.map +1 -1
  138. package/lib/test/client.obliterateFarm.spec.d.ts +12 -0
  139. package/lib/test/client.obliterateFarm.spec.d.ts.map +1 -0
  140. package/lib/test/client.obliterateFarm.spec.js +88 -0
  141. package/lib/test/client.obliterateFarm.spec.js.map +1 -0
  142. package/lib/test/client.reconnectFarm.spec.js +1 -1
  143. package/lib/test/client.reconnectFarm.spec.js.map +1 -1
  144. package/lib/test/client.searchForMarker.spec.js +2 -2
  145. package/lib/test/client.searchForMarker.spec.js.map +1 -1
  146. package/lib/test/mergeTreeOperationRunner.d.ts +7 -2
  147. package/lib/test/mergeTreeOperationRunner.d.ts.map +1 -1
  148. package/lib/test/mergeTreeOperationRunner.js +31 -14
  149. package/lib/test/mergeTreeOperationRunner.js.map +1 -1
  150. package/lib/test/obliterate.concurrent.spec.js +45 -1
  151. package/lib/test/obliterate.concurrent.spec.js.map +1 -1
  152. package/lib/test/obliterate.rangeExpansion.spec.js +81 -5
  153. package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -1
  154. package/lib/test/obliterate.spec.js +3 -3
  155. package/lib/test/obliterate.spec.js.map +1 -1
  156. package/lib/test/obliterateOperations.d.ts +15 -0
  157. package/lib/test/obliterateOperations.d.ts.map +1 -0
  158. package/lib/test/obliterateOperations.js +123 -0
  159. package/lib/test/obliterateOperations.js.map +1 -0
  160. package/lib/test/partialSyncHelper.d.ts +42 -0
  161. package/lib/test/partialSyncHelper.d.ts.map +1 -0
  162. package/lib/test/partialSyncHelper.js +92 -0
  163. package/lib/test/partialSyncHelper.js.map +1 -0
  164. package/lib/test/revertibles.spec.js +3 -3
  165. package/lib/test/revertibles.spec.js.map +1 -1
  166. package/lib/test/sortedSegmentSet.spec.js +21 -0
  167. package/lib/test/sortedSegmentSet.spec.js.map +1 -1
  168. package/lib/test/testClient.d.ts +1 -1
  169. package/lib/test/testClient.d.ts.map +1 -1
  170. package/lib/test/testClient.js +1 -0
  171. package/lib/test/testClient.js.map +1 -1
  172. package/lib/test/testUtils.js +2 -2
  173. package/lib/test/testUtils.js.map +1 -1
  174. package/package.json +21 -79
  175. package/src/mergeTree.ts +80 -28
  176. package/src/mergeTreeNodes.ts +15 -4
  177. package/src/partialLengths.ts +559 -776
  178. package/src/perspective.ts +10 -1
  179. package/src/properties.ts +2 -3
  180. package/src/segmentInfos.ts +3 -0
  181. package/src/snapshotLoader.ts +1 -1
  182. package/src/sortedSegmentSet.ts +41 -50
  183. package/src/sortedSet.ts +32 -16
@@ -20,7 +20,7 @@ const sortedSet_js_1 = require("./sortedSet.js");
20
20
  * @internal
21
21
  */
22
22
  class SortedSegmentSet extends sortedSet_js_1.SortedSet {
23
- getKey(item) {
23
+ getOrdinal(item) {
24
24
  const maybeRef = item;
25
25
  if (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {
26
26
  const lref = maybeRef;
@@ -35,48 +35,40 @@ class SortedSegmentSet extends sortedSet_js_1.SortedSet {
35
35
  }
36
36
  return (0, segmentInfos_js_1.toMergeNodeInfo)(item)?.ordinal ?? "";
37
37
  }
38
- findItemPosition(item) {
39
- if (this.keySortedItems.length === 0) {
40
- return { exists: false, index: 0 };
38
+ getOffset(item) {
39
+ const maybeRef = item;
40
+ if (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {
41
+ const lref = maybeRef;
42
+ return lref.getOffset();
41
43
  }
42
- let start = 0;
43
- let end = this.keySortedItems.length - 1;
44
- const itemKey = this.getKey(item);
45
- let index = -1;
46
- while (start <= end) {
47
- index = start + Math.floor((end - start) / 2);
48
- const indexKey = this.getKey(this.keySortedItems[index]);
49
- if (indexKey > itemKey) {
50
- if (start === index) {
51
- return { exists: false, index };
52
- }
53
- end = index - 1;
54
- }
55
- else if (indexKey < itemKey) {
56
- if (index === end) {
57
- return { exists: false, index: index + 1 };
58
- }
59
- start = index + 1;
44
+ return 0;
45
+ }
46
+ compare(a, b) {
47
+ const aOrdinal = this.getOrdinal(a);
48
+ const bOrdinal = this.getOrdinal(b);
49
+ if (aOrdinal < bOrdinal) {
50
+ return -1;
51
+ }
52
+ if (aOrdinal > bOrdinal) {
53
+ return 1;
54
+ }
55
+ return this.getOffset(a) - this.getOffset(b);
56
+ }
57
+ onFindEquivalent(item, startIndex) {
58
+ // SortedSegmentSet may contain multiple items with the same key (e.g. a local ref at offset 0 and the segment it is on).
59
+ // Items should compare as reference-equal, so we do a linear walk to find the actual item in this case.
60
+ let index = startIndex;
61
+ if (item === this.sortedItems[index]) {
62
+ return { exists: true, index };
63
+ }
64
+ for (let b = index - 1; b >= 0 && this.compare(item, this.sortedItems[b]) === 0; b--) {
65
+ if (this.sortedItems[b] === item) {
66
+ return { exists: true, index: b };
60
67
  }
61
- else if (indexKey === itemKey) {
62
- // at this point we've found the key of the item
63
- // so we need to find the index of the item instance
64
- //
65
- if (item === this.keySortedItems[index]) {
66
- return { exists: true, index };
67
- }
68
- for (let b = index - 1; b >= 0 && this.getKey(this.keySortedItems[b]) === itemKey; b--) {
69
- if (this.keySortedItems[b] === item) {
70
- return { exists: true, index: b };
71
- }
72
- }
73
- for (index + 1; index < this.keySortedItems.length &&
74
- this.getKey(this.keySortedItems[index]) === itemKey; index++) {
75
- if (this.keySortedItems[index] === item) {
76
- return { exists: true, index };
77
- }
78
- }
79
- return { exists: false, index };
68
+ }
69
+ for (index + 1; index < this.sortedItems.length && this.compare(item, this.sortedItems[index]) === 0; index++) {
70
+ if (this.sortedItems[index] === item) {
71
+ return { exists: true, index };
80
72
  }
81
73
  }
82
74
  return { exists: false, index };
@@ -1 +1 @@
1
- {"version":3,"file":"sortedSegmentSet.js","sourceRoot":"","sources":["../src/sortedSegmentSet.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { LocalReferencePosition } from \"./localReference.js\";\nimport { ISegmentInternal } from \"./mergeTreeNodes.js\";\nimport { hasProp, toMergeNodeInfo } from \"./segmentInfos.js\";\nimport { SortedSet } from \"./sortedSet.js\";\n\n/**\n * @internal\n */\nexport type SortedSegmentSetItem =\n\t| ISegmentInternal\n\t| LocalReferencePosition\n\t| { readonly segment: ISegmentInternal };\n\n/**\n * Stores a unique and sorted set of segments, or objects with segments\n *\n * This differs from a normal sorted set in that the keys are not fixed.\n * The segments are sorted via their ordinals which can change as the merge tree is modified.\n * Even though the values of the ordinals can change their ordering and uniqueness cannot, so the order of a set of\n * segments ordered by their ordinals will always have the same order even if the ordinal values on\n * the segments changes. This invariant allows us to ensure the segments stay\n * ordered and unique, and that new segments can be inserted into that order.\n *\n * @internal\n */\n\nexport class SortedSegmentSet<\n\tT extends SortedSegmentSetItem = ISegmentInternal,\n> extends SortedSet<T, string> {\n\tprotected getKey(item: T): string {\n\t\tconst maybeRef = item as Partial<LocalReferencePosition>;\n\t\tif (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {\n\t\t\tconst lref = maybeRef as LocalReferencePosition;\n\t\t\t// If the reference position has no associated segment, assign it a sentinel value.\n\t\t\t// The particular value for comparison doesn't matter because `findItemPosition` tolerates\n\t\t\t// elements with duplicate keys (as it must, since local references use the same key as their segment).\n\t\t\t// All that matters is that it's consistent.\n\t\t\treturn toMergeNodeInfo(lref.getSegment())?.ordinal ?? \"\";\n\t\t}\n\t\tif (hasProp(item, \"segment\", \"object\")) {\n\t\t\treturn toMergeNodeInfo(item.segment)?.ordinal ?? \"\";\n\t\t}\n\n\t\treturn toMergeNodeInfo(item)?.ordinal ?? \"\";\n\t}\n\n\tprotected findItemPosition(item: T): { exists: boolean; index: number } {\n\t\tif (this.keySortedItems.length === 0) {\n\t\t\treturn { exists: false, index: 0 };\n\t\t}\n\t\tlet start = 0;\n\t\tlet end = this.keySortedItems.length - 1;\n\t\tconst itemKey = this.getKey(item);\n\t\tlet index = -1;\n\n\t\twhile (start <= end) {\n\t\t\tindex = start + Math.floor((end - start) / 2);\n\t\t\tconst indexKey = this.getKey(this.keySortedItems[index]);\n\t\t\tif (indexKey > itemKey) {\n\t\t\t\tif (start === index) {\n\t\t\t\t\treturn { exists: false, index };\n\t\t\t\t}\n\t\t\t\tend = index - 1;\n\t\t\t} else if (indexKey < itemKey) {\n\t\t\t\tif (index === end) {\n\t\t\t\t\treturn { exists: false, index: index + 1 };\n\t\t\t\t}\n\t\t\t\tstart = index + 1;\n\t\t\t} else if (indexKey === itemKey) {\n\t\t\t\t// at this point we've found the key of the item\n\t\t\t\t// so we need to find the index of the item instance\n\t\t\t\t//\n\t\t\t\tif (item === this.keySortedItems[index]) {\n\t\t\t\t\treturn { exists: true, index };\n\t\t\t\t}\n\t\t\t\tfor (\n\t\t\t\t\tlet b = index - 1;\n\t\t\t\t\tb >= 0 && this.getKey(this.keySortedItems[b]) === itemKey;\n\t\t\t\t\tb--\n\t\t\t\t) {\n\t\t\t\t\tif (this.keySortedItems[b] === item) {\n\t\t\t\t\t\treturn { exists: true, index: b };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (\n\t\t\t\t\tindex + 1;\n\t\t\t\t\tindex < this.keySortedItems.length &&\n\t\t\t\t\tthis.getKey(this.keySortedItems[index]) === itemKey;\n\t\t\t\t\tindex++\n\t\t\t\t) {\n\t\t\t\t\tif (this.keySortedItems[index] === item) {\n\t\t\t\t\t\treturn { exists: true, index };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { exists: false, index };\n\t\t\t}\n\t\t}\n\t\treturn { exists: false, index };\n\t}\n}\n"]}
1
+ {"version":3,"file":"sortedSegmentSet.js","sourceRoot":"","sources":["../src/sortedSegmentSet.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { LocalReferencePosition } from \"./localReference.js\";\nimport { ISegmentInternal } from \"./mergeTreeNodes.js\";\nimport { hasProp, toMergeNodeInfo } from \"./segmentInfos.js\";\nimport { SortedSet } from \"./sortedSet.js\";\n\n/**\n * @internal\n */\nexport type SortedSegmentSetItem =\n\t| ISegmentInternal\n\t| LocalReferencePosition\n\t| { readonly segment: ISegmentInternal };\n\n/**\n * Stores a unique and sorted set of segments, or objects with segments\n *\n * This differs from a normal sorted set in that the keys are not fixed.\n * The segments are sorted via their ordinals which can change as the merge tree is modified.\n * Even though the values of the ordinals can change their ordering and uniqueness cannot, so the order of a set of\n * segments ordered by their ordinals will always have the same order even if the ordinal values on\n * the segments changes. This invariant allows us to ensure the segments stay\n * ordered and unique, and that new segments can be inserted into that order.\n *\n * @internal\n */\nexport class SortedSegmentSet<\n\tT extends SortedSegmentSetItem = ISegmentInternal,\n> extends SortedSet<T> {\n\tprivate getOrdinal(item: T): string {\n\t\tconst maybeRef = item as Partial<LocalReferencePosition>;\n\t\tif (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {\n\t\t\tconst lref = maybeRef as LocalReferencePosition;\n\t\t\t// If the reference position has no associated segment, assign it a sentinel value.\n\t\t\t// The particular value for comparison doesn't matter because `findItemPosition` tolerates\n\t\t\t// elements with duplicate keys (as it must, since local references use the same key as their segment).\n\t\t\t// All that matters is that it's consistent.\n\t\t\treturn toMergeNodeInfo(lref.getSegment())?.ordinal ?? \"\";\n\t\t}\n\t\tif (hasProp(item, \"segment\", \"object\")) {\n\t\t\treturn toMergeNodeInfo(item.segment)?.ordinal ?? \"\";\n\t\t}\n\n\t\treturn toMergeNodeInfo(item)?.ordinal ?? \"\";\n\t}\n\n\tprivate getOffset(item: T): number {\n\t\tconst maybeRef = item as Partial<LocalReferencePosition>;\n\t\tif (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {\n\t\t\tconst lref = maybeRef as LocalReferencePosition;\n\t\t\treturn lref.getOffset();\n\t\t}\n\t\treturn 0;\n\t}\n\n\tprotected compare(a: T, b: T): number {\n\t\tconst aOrdinal = this.getOrdinal(a);\n\t\tconst bOrdinal = this.getOrdinal(b);\n\n\t\tif (aOrdinal < bOrdinal) {\n\t\t\treturn -1;\n\t\t}\n\t\tif (aOrdinal > bOrdinal) {\n\t\t\treturn 1;\n\t\t}\n\t\treturn this.getOffset(a) - this.getOffset(b);\n\t}\n\n\tprotected onFindEquivalent(item: T, startIndex: number): { exists: boolean; index: number } {\n\t\t// SortedSegmentSet may contain multiple items with the same key (e.g. a local ref at offset 0 and the segment it is on).\n\t\t// Items should compare as reference-equal, so we do a linear walk to find the actual item in this case.\n\t\tlet index = startIndex;\n\t\tif (item === this.sortedItems[index]) {\n\t\t\treturn { exists: true, index };\n\t\t}\n\t\tfor (let b = index - 1; b >= 0 && this.compare(item, this.sortedItems[b]) === 0; b--) {\n\t\t\tif (this.sortedItems[b] === item) {\n\t\t\t\treturn { exists: true, index: b };\n\t\t\t}\n\t\t}\n\t\tfor (\n\t\t\tindex + 1;\n\t\t\tindex < this.sortedItems.length && this.compare(item, this.sortedItems[index]) === 0;\n\t\t\tindex++\n\t\t) {\n\t\t\tif (this.sortedItems[index] === item) {\n\t\t\t\treturn { exists: true, index };\n\t\t\t}\n\t\t}\n\t\treturn { exists: false, index };\n\t}\n}\n"]}
@@ -5,9 +5,15 @@
5
5
  /**
6
6
  * @internal
7
7
  */
8
- export declare abstract class SortedSet<T, U extends string | number> {
9
- protected abstract getKey(t: T): U;
10
- protected readonly keySortedItems: T[];
8
+ export declare abstract class SortedSet<T> {
9
+ /**
10
+ * Standard comparator semantics:
11
+ * - If a \< b, return a negative number
12
+ * - If a \> b, return a positive number
13
+ * - If a and b are equivalent, return 0
14
+ */
15
+ protected abstract compare(a: T, b: T): number;
16
+ protected readonly sortedItems: T[];
11
17
  get size(): number;
12
18
  get items(): readonly T[];
13
19
  addOrUpdate(newItem: T, update?: (existingItem: T, newItem: T) => void): void;
@@ -17,5 +23,16 @@ export declare abstract class SortedSet<T, U extends string | number> {
17
23
  exists: boolean;
18
24
  index: number;
19
25
  };
26
+ /**
27
+ * Invoked when `findItemPosition` finds an equivalent item (i.e. `compare` returns 0 between that item and the search item).
28
+ *
29
+ * By default, `SortedSet` assumes that equivalent items are equal and returns the found index.
30
+ * @param item - The item that is being searched for (argument to `findItemPosition`)
31
+ * @param index - The index of the equivalent item in the sorted set
32
+ */
33
+ protected onFindEquivalent(item: T, index: number): {
34
+ exists: boolean;
35
+ index: number;
36
+ };
20
37
  }
21
38
  //# sourceMappingURL=sortedSet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sortedSet.d.ts","sourceRoot":"","sources":["../src/sortedSet.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,8BAAsB,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM;IAC3D,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC;IAElC,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,CAAM;IAE5C,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,IAAW,KAAK,IAAI,SAAS,CAAC,EAAE,CAE/B;IAEM,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,IAAI,GAAG,IAAI;IAS7E,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO;IASxB,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO;IAK5B,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CA4BvE"}
1
+ {"version":3,"file":"sortedSet.d.ts","sourceRoot":"","sources":["../src/sortedSet.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,8BAAsB,SAAS,CAAC,CAAC;IAChC;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM;IAE9C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,CAAM;IAEzC,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,IAAW,KAAK,IAAI,SAAS,CAAC,EAAE,CAE/B;IAEM,WAAW,CAAC,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,IAAI,GAAG,IAAI;IAS7E,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO;IASxB,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO;IAK5B,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IA4BvE;;;;;;OAMG;IACH,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAGtF"}
package/dist/sortedSet.js CHANGED
@@ -10,27 +10,27 @@ exports.SortedSet = void 0;
10
10
  */
11
11
  class SortedSet {
12
12
  constructor() {
13
- this.keySortedItems = [];
13
+ this.sortedItems = [];
14
14
  }
15
15
  get size() {
16
- return this.keySortedItems.length;
16
+ return this.sortedItems.length;
17
17
  }
18
18
  get items() {
19
- return this.keySortedItems;
19
+ return this.sortedItems;
20
20
  }
21
21
  addOrUpdate(newItem, update) {
22
22
  const position = this.findItemPosition(newItem);
23
23
  if (position.exists) {
24
- update?.(this.keySortedItems[position.index], newItem);
24
+ update?.(this.sortedItems[position.index], newItem);
25
25
  }
26
26
  else {
27
- this.keySortedItems.splice(position.index, 0, newItem);
27
+ this.sortedItems.splice(position.index, 0, newItem);
28
28
  }
29
29
  }
30
30
  remove(item) {
31
31
  const position = this.findItemPosition(item);
32
32
  if (position.exists) {
33
- this.keySortedItems.splice(position.index, 1);
33
+ this.sortedItems.splice(position.index, 1);
34
34
  return true;
35
35
  }
36
36
  return false;
@@ -40,34 +40,43 @@ class SortedSet {
40
40
  return position.exists;
41
41
  }
42
42
  findItemPosition(item) {
43
- if (this.keySortedItems.length === 0) {
43
+ if (this.sortedItems.length === 0) {
44
44
  return { exists: false, index: 0 };
45
45
  }
46
46
  let start = 0;
47
- let end = this.keySortedItems.length - 1;
48
- const itemKey = this.getKey(item);
47
+ let end = this.sortedItems.length - 1;
49
48
  let index = -1;
50
49
  while (start <= end) {
51
50
  index = start + Math.floor((end - start) / 2);
52
- const indexKey = this.getKey(this.keySortedItems[index]);
53
- if (indexKey > itemKey) {
51
+ const compareResult = this.compare(item, this.sortedItems[index]);
52
+ if (compareResult < 0) {
54
53
  if (start === index) {
55
54
  return { exists: false, index };
56
55
  }
57
56
  end = index - 1;
58
57
  }
59
- else if (indexKey < itemKey) {
58
+ else if (compareResult > 0) {
60
59
  if (index === end) {
61
60
  return { exists: false, index: index + 1 };
62
61
  }
63
62
  start = index + 1;
64
63
  }
65
- else if (indexKey === itemKey) {
66
- return { exists: true, index };
64
+ else if (compareResult === 0) {
65
+ return this.onFindEquivalent(item, index);
67
66
  }
68
67
  }
69
68
  return { exists: false, index };
70
69
  }
70
+ /**
71
+ * Invoked when `findItemPosition` finds an equivalent item (i.e. `compare` returns 0 between that item and the search item).
72
+ *
73
+ * By default, `SortedSet` assumes that equivalent items are equal and returns the found index.
74
+ * @param item - The item that is being searched for (argument to `findItemPosition`)
75
+ * @param index - The index of the equivalent item in the sorted set
76
+ */
77
+ onFindEquivalent(item, index) {
78
+ return { exists: true, index };
79
+ }
71
80
  }
72
81
  exports.SortedSet = SortedSet;
73
82
  //# sourceMappingURL=sortedSet.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sortedSet.js","sourceRoot":"","sources":["../src/sortedSet.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * @internal\n */\nexport abstract class SortedSet<T, U extends string | number> {\n\tprotected abstract getKey(t: T): U;\n\n\tprotected readonly keySortedItems: T[] = [];\n\n\tpublic get size(): number {\n\t\treturn this.keySortedItems.length;\n\t}\n\n\tpublic get items(): readonly T[] {\n\t\treturn this.keySortedItems;\n\t}\n\n\tpublic addOrUpdate(newItem: T, update?: (existingItem: T, newItem: T) => void): void {\n\t\tconst position = this.findItemPosition(newItem);\n\t\tif (position.exists) {\n\t\t\tupdate?.(this.keySortedItems[position.index], newItem);\n\t\t} else {\n\t\t\tthis.keySortedItems.splice(position.index, 0, newItem);\n\t\t}\n\t}\n\n\tpublic remove(item: T): boolean {\n\t\tconst position = this.findItemPosition(item);\n\t\tif (position.exists) {\n\t\t\tthis.keySortedItems.splice(position.index, 1);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic has(item: T): boolean {\n\t\tconst position = this.findItemPosition(item);\n\t\treturn position.exists;\n\t}\n\n\tprotected findItemPosition(item: T): { exists: boolean; index: number } {\n\t\tif (this.keySortedItems.length === 0) {\n\t\t\treturn { exists: false, index: 0 };\n\t\t}\n\t\tlet start = 0;\n\t\tlet end = this.keySortedItems.length - 1;\n\t\tconst itemKey = this.getKey(item);\n\t\tlet index = -1;\n\n\t\twhile (start <= end) {\n\t\t\tindex = start + Math.floor((end - start) / 2);\n\t\t\tconst indexKey = this.getKey(this.keySortedItems[index]);\n\t\t\tif (indexKey > itemKey) {\n\t\t\t\tif (start === index) {\n\t\t\t\t\treturn { exists: false, index };\n\t\t\t\t}\n\t\t\t\tend = index - 1;\n\t\t\t} else if (indexKey < itemKey) {\n\t\t\t\tif (index === end) {\n\t\t\t\t\treturn { exists: false, index: index + 1 };\n\t\t\t\t}\n\t\t\t\tstart = index + 1;\n\t\t\t} else if (indexKey === itemKey) {\n\t\t\t\treturn { exists: true, index };\n\t\t\t}\n\t\t}\n\t\treturn { exists: false, index };\n\t}\n}\n"]}
1
+ {"version":3,"file":"sortedSet.js","sourceRoot":"","sources":["../src/sortedSet.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * @internal\n */\nexport abstract class SortedSet<T> {\n\t/**\n\t * Standard comparator semantics:\n\t * - If a \\< b, return a negative number\n\t * - If a \\> b, return a positive number\n\t * - If a and b are equivalent, return 0\n\t */\n\tprotected abstract compare(a: T, b: T): number;\n\n\tprotected readonly sortedItems: T[] = [];\n\n\tpublic get size(): number {\n\t\treturn this.sortedItems.length;\n\t}\n\n\tpublic get items(): readonly T[] {\n\t\treturn this.sortedItems;\n\t}\n\n\tpublic addOrUpdate(newItem: T, update?: (existingItem: T, newItem: T) => void): void {\n\t\tconst position = this.findItemPosition(newItem);\n\t\tif (position.exists) {\n\t\t\tupdate?.(this.sortedItems[position.index], newItem);\n\t\t} else {\n\t\t\tthis.sortedItems.splice(position.index, 0, newItem);\n\t\t}\n\t}\n\n\tpublic remove(item: T): boolean {\n\t\tconst position = this.findItemPosition(item);\n\t\tif (position.exists) {\n\t\t\tthis.sortedItems.splice(position.index, 1);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic has(item: T): boolean {\n\t\tconst position = this.findItemPosition(item);\n\t\treturn position.exists;\n\t}\n\n\tprotected findItemPosition(item: T): { exists: boolean; index: number } {\n\t\tif (this.sortedItems.length === 0) {\n\t\t\treturn { exists: false, index: 0 };\n\t\t}\n\t\tlet start = 0;\n\t\tlet end = this.sortedItems.length - 1;\n\t\tlet index = -1;\n\n\t\twhile (start <= end) {\n\t\t\tindex = start + Math.floor((end - start) / 2);\n\t\t\tconst compareResult = this.compare(item, this.sortedItems[index]);\n\t\t\tif (compareResult < 0) {\n\t\t\t\tif (start === index) {\n\t\t\t\t\treturn { exists: false, index };\n\t\t\t\t}\n\t\t\t\tend = index - 1;\n\t\t\t} else if (compareResult > 0) {\n\t\t\t\tif (index === end) {\n\t\t\t\t\treturn { exists: false, index: index + 1 };\n\t\t\t\t}\n\t\t\t\tstart = index + 1;\n\t\t\t} else if (compareResult === 0) {\n\t\t\t\treturn this.onFindEquivalent(item, index);\n\t\t\t}\n\t\t}\n\t\treturn { exists: false, index };\n\t}\n\n\t/**\n\t * Invoked when `findItemPosition` finds an equivalent item (i.e. `compare` returns 0 between that item and the search item).\n\t *\n\t * By default, `SortedSet` assumes that equivalent items are equal and returns the found index.\n\t * @param item - The item that is being searched for (argument to `findItemPosition`)\n\t * @param index - The index of the equivalent item in the sorted set\n\t */\n\tprotected onFindEquivalent(item: T, index: number): { exists: boolean; index: number } {\n\t\treturn { exists: true, index };\n\t}\n}\n"]}
@@ -8,7 +8,7 @@ const benchmark_1 = require("@fluid-tools/benchmark");
8
8
  const snapshot_utils_js_1 = require("./snapshot.utils.js");
9
9
  describe("MergeTree snapshots", () => {
10
10
  let summary;
11
- for (const summarySize of [10, 50, 100, 500, 1000, 5000, 10000]) {
11
+ for (const summarySize of [10, 50, 100, 500, 1000, 5000, 10_000]) {
12
12
  (0, benchmark_1.benchmark)({
13
13
  type: benchmark_1.BenchmarkType.Measurement,
14
14
  title: `load snapshot with ${summarySize} segments`,
@@ -1 +1 @@
1
- {"version":3,"file":"Snapshot.perf.spec.js","sourceRoot":"","sources":["../../src/test/Snapshot.perf.spec.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,sDAAkE;AAGlE,2DAA+D;AAE/D,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACpC,IAAI,OAAiC,CAAC;IAEtC,KAAK,MAAM,WAAW,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAM,CAAC,EAAE,CAAC;QAClE,IAAA,qBAAS,EAAC;YACT,IAAI,EAAE,yBAAa,CAAC,WAAW;YAC/B,KAAK,EAAE,sBAAsB,WAAW,WAAW;YACnD,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,GAAG,EAAE;gBACZ,MAAM,GAAG,GAAG,IAAI,8BAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACxB,CAAC;gBAED,GAAG,CAAC,eAAe,EAAE,CAAC;gBACtB,OAAO,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YAC5B,CAAC;YACD,gBAAgB,EAAE,KAAK,IAAI,EAAE;gBAC5B,oEAAoE;gBACpE,MAAM,IAAA,gCAAY,EAAC,OAAQ,CAAC,CAAC;YAC9B,CAAC;YACD,KAAK,EAAE,GAAG,EAAE;gBACX,OAAO,GAAG,SAAS,CAAC;YACrB,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;AACF,CAAC,CAAC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { BenchmarkType, benchmark } from \"@fluid-tools/benchmark\";\nimport type { ISummaryTree } from \"@fluidframework/driver-definitions\";\n\nimport { TestString, loadSnapshot } from \"./snapshot.utils.js\";\n\ndescribe(\"MergeTree snapshots\", () => {\n\tlet summary: ISummaryTree | undefined;\n\n\tfor (const summarySize of [10, 50, 100, 500, 1000, 5000, 10_000]) {\n\t\tbenchmark({\n\t\t\ttype: BenchmarkType.Measurement,\n\t\t\ttitle: `load snapshot with ${summarySize} segments`,\n\t\t\tcategory: \"snapshot loading\",\n\t\t\tbefore: () => {\n\t\t\t\tconst str = new TestString(\"id\", {});\n\t\t\t\tfor (let i = 0; i < summarySize; i++) {\n\t\t\t\t\tstr.append(\"a\", false);\n\t\t\t\t}\n\n\t\t\t\tstr.applyPendingOps();\n\t\t\t\tsummary = str.getSummary();\n\t\t\t},\n\t\t\tbenchmarkFnAsync: async () => {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tawait loadSnapshot(summary!);\n\t\t\t},\n\t\t\tafter: () => {\n\t\t\t\tsummary = undefined;\n\t\t\t},\n\t\t});\n\t}\n});\n"]}
1
+ {"version":3,"file":"Snapshot.perf.spec.js","sourceRoot":"","sources":["../../src/test/Snapshot.perf.spec.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,sDAAkE;AAGlE,2DAA+D;AAE/D,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACpC,IAAI,OAAiC,CAAC;IAEtC,KAAK,MAAM,WAAW,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QAClE,IAAA,qBAAS,EAAC;YACT,IAAI,EAAE,yBAAa,CAAC,WAAW;YAC/B,KAAK,EAAE,sBAAsB,WAAW,WAAW;YACnD,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,GAAG,EAAE;gBACZ,MAAM,GAAG,GAAG,IAAI,8BAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACxB,CAAC;gBAED,GAAG,CAAC,eAAe,EAAE,CAAC;gBACtB,OAAO,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YAC5B,CAAC;YACD,gBAAgB,EAAE,KAAK,IAAI,EAAE;gBAC5B,oEAAoE;gBACpE,MAAM,IAAA,gCAAY,EAAC,OAAQ,CAAC,CAAC;YAC9B,CAAC;YACD,KAAK,EAAE,GAAG,EAAE;gBACX,OAAO,GAAG,SAAS,CAAC;YACrB,CAAC;SACD,CAAC,CAAC;IACJ,CAAC;AACF,CAAC,CAAC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { BenchmarkType, benchmark } from \"@fluid-tools/benchmark\";\nimport type { ISummaryTree } from \"@fluidframework/driver-definitions\";\n\nimport { TestString, loadSnapshot } from \"./snapshot.utils.js\";\n\ndescribe(\"MergeTree snapshots\", () => {\n\tlet summary: ISummaryTree | undefined;\n\n\tfor (const summarySize of [10, 50, 100, 500, 1000, 5000, 10_000]) {\n\t\tbenchmark({\n\t\t\ttype: BenchmarkType.Measurement,\n\t\t\ttitle: `load snapshot with ${summarySize} segments`,\n\t\t\tcategory: \"snapshot loading\",\n\t\t\tbefore: () => {\n\t\t\t\tconst str = new TestString(\"id\", {});\n\t\t\t\tfor (let i = 0; i < summarySize; i++) {\n\t\t\t\t\tstr.append(\"a\", false);\n\t\t\t\t}\n\n\t\t\t\tstr.applyPendingOps();\n\t\t\t\tsummary = str.getSummary();\n\t\t\t},\n\t\t\tbenchmarkFnAsync: async () => {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tawait loadSnapshot(summary!);\n\t\t\t},\n\t\t\tafter: () => {\n\t\t\t\tsummary = undefined;\n\t\t\t},\n\t\t});\n\t}\n});\n"]}
@@ -729,6 +729,26 @@ describe("client.applyMsg", () => {
729
729
  }
730
730
  logger.validate({ baseText: "BBBBBB B" });
731
731
  });
732
+ it("obliterate with mismatched final states", () => {
733
+ const clients = (0, testClientLogger_js_1.createClientsAtInitialState)({
734
+ initialState: "0{zzzzzzz}123{yyyyyy}45",
735
+ options: {
736
+ mergeTreeEnableObliterate: true,
737
+ mergeTreeEnableSidedObliterate: true,
738
+ mergeTreeEnableAnnotateAdjust: true,
739
+ },
740
+ }, "A", "B");
741
+ let seq = 0;
742
+ const logger = new testClientLogger_js_1.TestClientLogger(clients.all);
743
+ const ops = [];
744
+ const b = clients.B;
745
+ ops.push(b.makeOpMessage(b.obliterateRangeLocal({ pos: 1, side: sequencePlace_js_1.Side.After }, { pos: 9, side: sequencePlace_js_1.Side.Before }), ++seq), b.makeOpMessage(b.insertTextLocal(2, "xx"), ++seq), b.makeOpMessage(b.insertTextLocal(8, "BB"), ++seq), b.makeOpMessage(b.obliterateRangeLocal({ pos: 1, side: sequencePlace_js_1.Side.After }, { pos: 4, side: sequencePlace_js_1.Side.Before }), ++seq), b.makeOpMessage(b.insertTextLocal(2, "6666666666"), ++seq));
746
+ for (const op of ops.splice(0))
747
+ for (const c of clients.all) {
748
+ c.applyMsg(op);
749
+ }
750
+ logger.validate({ baseText: "0{6666666666}123BB{yyyyyy}45" });
751
+ });
732
752
  });
733
753
  describe("updates minSeq", () => {
734
754
  it("to the message's minSeq with no ops in flight", () => {