@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
@@ -35,7 +35,8 @@ export interface SeqTime {
35
35
 
36
36
  /**
37
37
  * Implementation of {@link Perspective}.
38
- * See {@link Client.createPerspective}.
38
+ * @privateRemarks
39
+ * TODO:AB#29765: This class does not support non-local-client perspectives, but should.
39
40
  */
40
41
  export class PerspectiveImpl implements Perspective {
41
42
  /**
@@ -85,6 +86,8 @@ export class PerspectiveImpl implements Perspective {
85
86
  * @param seq - The latest sequence number to consider.
86
87
  * @param localSeq - The latest local sequence number to consider.
87
88
  * @returns true iff this segment was removed in the given perspective.
89
+ * @privateRemarks
90
+ * TODO:AB#29765: This function does not support non-local-client perspectives, but should.
88
91
  */
89
92
  export function wasRemovedBefore(
90
93
  seg: SegmentWithInfo<IInsertionInfo & IRemovalInfo>,
@@ -106,6 +109,8 @@ export function wasRemovedBefore(
106
109
  * @param refSeq - The latest sequence number to consider.
107
110
  * @param localSeq - The latest local sequence number to consider.
108
111
  * @returns true iff this segment was moved (aka obliterated) in the given perspective.
112
+ * @privateRemarks
113
+ * TODO:AB#29765: This function does not support non-local-client perspectives, but should.
109
114
  */
110
115
  export function wasMovedBefore(
111
116
  seg: SegmentWithInfo<IInsertionInfo & IMoveInfo>,
@@ -123,6 +128,8 @@ export function wasMovedBefore(
123
128
 
124
129
  /**
125
130
  * See {@link wasRemovedBefore} and {@link wasMovedBefore}.
131
+ * @privateRemarks
132
+ * TODO:AB#29765: This function does not support non-local-client perspectives, but should.
126
133
  */
127
134
  export function wasRemovedOrMovedBefore(seg: ISegmentLeaf, seqTime: SeqTime): boolean {
128
135
  return (
@@ -138,6 +145,8 @@ export function wasRemovedOrMovedBefore(seg: ISegmentLeaf, seqTime: SeqTime): bo
138
145
  * @param seqTime - The latest sequence number and local sequence number to consider.
139
146
  * @returns true iff this segment was inserted before the given perspective,
140
147
  * and it was not removed or moved in the given perspective.
148
+ * @privateRemarks
149
+ * TODO:AB#29765: This function does not support non-local-client perspectives, but should.
141
150
  */
142
151
  export function isSegmentPresent(seg: ISegmentLeaf, seqTime: SeqTime): boolean {
143
152
  const { refSeq, localSeq } = seqTime;
package/src/properties.ts CHANGED
@@ -119,10 +119,9 @@ export function extendIfUndefined<T>(
119
119
  extension: MapLike<T> | undefined,
120
120
  ): MapLike<T> {
121
121
  if (extension !== undefined) {
122
- // eslint-disable-next-line no-restricted-syntax
123
- for (const key in extension) {
122
+ for (const [key, value] of Object.entries(extension)) {
124
123
  if (base[key] === undefined) {
125
- base[key] = extension[key];
124
+ base[key] = value;
126
125
  }
127
126
  }
128
127
  }
@@ -310,6 +310,9 @@ export interface IMoveInfo {
310
310
  * If a segment is moved on insertion, its length is only ever visible to
311
311
  * the client that inserted the segment. This is relevant in partial length
312
312
  * calculations
313
+ *
314
+ * @privateRemarks
315
+ * TODO:AB#29553: This property is not persisted in the summary, but it should be.
313
316
  */
314
317
  wasMovedOnInsert: boolean;
315
318
  }
@@ -140,7 +140,7 @@ export class SnapshotLoader {
140
140
  movedClientIds: spec.movedClientIds.map((id) =>
141
141
  this.client.getOrAddShortClientId(id),
142
142
  ),
143
- // BUG? This isn't persisted
143
+ // TODO:AB#29553: This property should be derived from segment data, not hard-coded.
144
144
  wasMovedOnInsert: false,
145
145
  });
146
146
  }
@@ -28,11 +28,10 @@ export type SortedSegmentSetItem =
28
28
  *
29
29
  * @internal
30
30
  */
31
-
32
31
  export class SortedSegmentSet<
33
32
  T extends SortedSegmentSetItem = ISegmentInternal,
34
- > extends SortedSet<T, string> {
35
- protected getKey(item: T): string {
33
+ > extends SortedSet<T> {
34
+ private getOrdinal(item: T): string {
36
35
  const maybeRef = item as Partial<LocalReferencePosition>;
37
36
  if (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {
38
37
  const lref = maybeRef as LocalReferencePosition;
@@ -49,55 +48,47 @@ export class SortedSegmentSet<
49
48
  return toMergeNodeInfo(item)?.ordinal ?? "";
50
49
  }
51
50
 
52
- protected findItemPosition(item: T): { exists: boolean; index: number } {
53
- if (this.keySortedItems.length === 0) {
54
- return { exists: false, index: 0 };
51
+ private getOffset(item: T): number {
52
+ const maybeRef = item as Partial<LocalReferencePosition>;
53
+ if (maybeRef.getSegment !== undefined && maybeRef.isLeaf?.() === false) {
54
+ const lref = maybeRef as LocalReferencePosition;
55
+ return lref.getOffset();
55
56
  }
56
- let start = 0;
57
- let end = this.keySortedItems.length - 1;
58
- const itemKey = this.getKey(item);
59
- let index = -1;
57
+ return 0;
58
+ }
59
+
60
+ protected compare(a: T, b: T): number {
61
+ const aOrdinal = this.getOrdinal(a);
62
+ const bOrdinal = this.getOrdinal(b);
60
63
 
61
- while (start <= end) {
62
- index = start + Math.floor((end - start) / 2);
63
- const indexKey = this.getKey(this.keySortedItems[index]);
64
- if (indexKey > itemKey) {
65
- if (start === index) {
66
- return { exists: false, index };
67
- }
68
- end = index - 1;
69
- } else if (indexKey < itemKey) {
70
- if (index === end) {
71
- return { exists: false, index: index + 1 };
72
- }
73
- start = index + 1;
74
- } else if (indexKey === itemKey) {
75
- // at this point we've found the key of the item
76
- // so we need to find the index of the item instance
77
- //
78
- if (item === this.keySortedItems[index]) {
79
- return { exists: true, index };
80
- }
81
- for (
82
- let b = index - 1;
83
- b >= 0 && this.getKey(this.keySortedItems[b]) === itemKey;
84
- b--
85
- ) {
86
- if (this.keySortedItems[b] === item) {
87
- return { exists: true, index: b };
88
- }
89
- }
90
- for (
91
- index + 1;
92
- index < this.keySortedItems.length &&
93
- this.getKey(this.keySortedItems[index]) === itemKey;
94
- index++
95
- ) {
96
- if (this.keySortedItems[index] === item) {
97
- return { exists: true, index };
98
- }
99
- }
100
- return { exists: false, index };
64
+ if (aOrdinal < bOrdinal) {
65
+ return -1;
66
+ }
67
+ if (aOrdinal > bOrdinal) {
68
+ return 1;
69
+ }
70
+ return this.getOffset(a) - this.getOffset(b);
71
+ }
72
+
73
+ protected onFindEquivalent(item: T, startIndex: number): { exists: boolean; index: number } {
74
+ // SortedSegmentSet may contain multiple items with the same key (e.g. a local ref at offset 0 and the segment it is on).
75
+ // Items should compare as reference-equal, so we do a linear walk to find the actual item in this case.
76
+ let index = startIndex;
77
+ if (item === this.sortedItems[index]) {
78
+ return { exists: true, index };
79
+ }
80
+ for (let b = index - 1; b >= 0 && this.compare(item, this.sortedItems[b]) === 0; b--) {
81
+ if (this.sortedItems[b] === item) {
82
+ return { exists: true, index: b };
83
+ }
84
+ }
85
+ for (
86
+ index + 1;
87
+ index < this.sortedItems.length && this.compare(item, this.sortedItems[index]) === 0;
88
+ index++
89
+ ) {
90
+ if (this.sortedItems[index] === item) {
91
+ return { exists: true, index };
101
92
  }
102
93
  }
103
94
  return { exists: false, index };
package/src/sortedSet.ts CHANGED
@@ -6,32 +6,38 @@
6
6
  /**
7
7
  * @internal
8
8
  */
9
- export abstract class SortedSet<T, U extends string | number> {
10
- protected abstract getKey(t: T): U;
9
+ export abstract class SortedSet<T> {
10
+ /**
11
+ * Standard comparator semantics:
12
+ * - If a \< b, return a negative number
13
+ * - If a \> b, return a positive number
14
+ * - If a and b are equivalent, return 0
15
+ */
16
+ protected abstract compare(a: T, b: T): number;
11
17
 
12
- protected readonly keySortedItems: T[] = [];
18
+ protected readonly sortedItems: T[] = [];
13
19
 
14
20
  public get size(): number {
15
- return this.keySortedItems.length;
21
+ return this.sortedItems.length;
16
22
  }
17
23
 
18
24
  public get items(): readonly T[] {
19
- return this.keySortedItems;
25
+ return this.sortedItems;
20
26
  }
21
27
 
22
28
  public addOrUpdate(newItem: T, update?: (existingItem: T, newItem: T) => void): void {
23
29
  const position = this.findItemPosition(newItem);
24
30
  if (position.exists) {
25
- update?.(this.keySortedItems[position.index], newItem);
31
+ update?.(this.sortedItems[position.index], newItem);
26
32
  } else {
27
- this.keySortedItems.splice(position.index, 0, newItem);
33
+ this.sortedItems.splice(position.index, 0, newItem);
28
34
  }
29
35
  }
30
36
 
31
37
  public remove(item: T): boolean {
32
38
  const position = this.findItemPosition(item);
33
39
  if (position.exists) {
34
- this.keySortedItems.splice(position.index, 1);
40
+ this.sortedItems.splice(position.index, 1);
35
41
  return true;
36
42
  }
37
43
  return false;
@@ -43,31 +49,41 @@ export abstract class SortedSet<T, U extends string | number> {
43
49
  }
44
50
 
45
51
  protected findItemPosition(item: T): { exists: boolean; index: number } {
46
- if (this.keySortedItems.length === 0) {
52
+ if (this.sortedItems.length === 0) {
47
53
  return { exists: false, index: 0 };
48
54
  }
49
55
  let start = 0;
50
- let end = this.keySortedItems.length - 1;
51
- const itemKey = this.getKey(item);
56
+ let end = this.sortedItems.length - 1;
52
57
  let index = -1;
53
58
 
54
59
  while (start <= end) {
55
60
  index = start + Math.floor((end - start) / 2);
56
- const indexKey = this.getKey(this.keySortedItems[index]);
57
- if (indexKey > itemKey) {
61
+ const compareResult = this.compare(item, this.sortedItems[index]);
62
+ if (compareResult < 0) {
58
63
  if (start === index) {
59
64
  return { exists: false, index };
60
65
  }
61
66
  end = index - 1;
62
- } else if (indexKey < itemKey) {
67
+ } else if (compareResult > 0) {
63
68
  if (index === end) {
64
69
  return { exists: false, index: index + 1 };
65
70
  }
66
71
  start = index + 1;
67
- } else if (indexKey === itemKey) {
68
- return { exists: true, index };
72
+ } else if (compareResult === 0) {
73
+ return this.onFindEquivalent(item, index);
69
74
  }
70
75
  }
71
76
  return { exists: false, index };
72
77
  }
78
+
79
+ /**
80
+ * Invoked when `findItemPosition` finds an equivalent item (i.e. `compare` returns 0 between that item and the search item).
81
+ *
82
+ * By default, `SortedSet` assumes that equivalent items are equal and returns the found index.
83
+ * @param item - The item that is being searched for (argument to `findItemPosition`)
84
+ * @param index - The index of the equivalent item in the sorted set
85
+ */
86
+ protected onFindEquivalent(item: T, index: number): { exists: boolean; index: number } {
87
+ return { exists: true, index };
88
+ }
73
89
  }