@fluidframework/merge-tree 2.42.0 → 2.43.0-343119

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 (108) hide show
  1. package/dist/client.d.ts +3 -3
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +8 -11
  4. package/dist/client.js.map +1 -1
  5. package/dist/collections/list.d.ts +95 -5
  6. package/dist/collections/list.d.ts.map +1 -1
  7. package/dist/collections/list.js +67 -5
  8. package/dist/collections/list.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/mergeTree.d.ts +9 -9
  14. package/dist/mergeTree.d.ts.map +1 -1
  15. package/dist/mergeTree.js +14 -6
  16. package/dist/mergeTree.js.map +1 -1
  17. package/dist/revertibles.js +1 -1
  18. package/dist/revertibles.js.map +1 -1
  19. package/dist/test/beastTest.spec.js +1 -1
  20. package/dist/test/beastTest.spec.js.map +1 -1
  21. package/dist/test/client.annotateMarker.spec.js +1 -1
  22. package/dist/test/client.annotateMarker.spec.js.map +1 -1
  23. package/dist/test/client.applyMsg.spec.js +21 -21
  24. package/dist/test/client.applyMsg.spec.js.map +1 -1
  25. package/dist/test/client.attributionFarm.spec.js +1 -1
  26. package/dist/test/client.attributionFarm.spec.js.map +1 -1
  27. package/dist/test/client.getPosition.spec.js +1 -0
  28. package/dist/test/client.getPosition.spec.js.map +1 -1
  29. package/dist/test/client.localReference.spec.js +62 -48
  30. package/dist/test/client.localReference.spec.js.map +1 -1
  31. package/dist/test/client.localReferenceFarm.spec.js +1 -0
  32. package/dist/test/client.localReferenceFarm.spec.js.map +1 -1
  33. package/dist/test/client.rollback.spec.js +8 -6
  34. package/dist/test/client.rollback.spec.js.map +1 -1
  35. package/dist/test/mergeTree.annotate.spec.js +25 -25
  36. package/dist/test/mergeTree.annotate.spec.js.map +1 -1
  37. package/dist/test/mergeTree.markRangeRemoved.spec.js +1 -1
  38. package/dist/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
  39. package/dist/test/obliterate.spec.js +4 -4
  40. package/dist/test/obliterate.spec.js.map +1 -1
  41. package/dist/test/resetPendingSegmentsToOp.spec.js +4 -4
  42. package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  43. package/dist/test/snapshot.utils.js +1 -1
  44. package/dist/test/snapshot.utils.js.map +1 -1
  45. package/dist/test/sortedSegmentSet.spec.js +3 -3
  46. package/dist/test/sortedSegmentSet.spec.js.map +1 -1
  47. package/dist/test/testClient.js +3 -3
  48. package/dist/test/testClient.js.map +1 -1
  49. package/dist/test/tracking.spec.js +7 -7
  50. package/dist/test/tracking.spec.js.map +1 -1
  51. package/dist/test/wordUnitTests.spec.js.map +1 -1
  52. package/lib/client.d.ts +3 -3
  53. package/lib/client.d.ts.map +1 -1
  54. package/lib/client.js +8 -11
  55. package/lib/client.js.map +1 -1
  56. package/lib/collections/list.d.ts +95 -5
  57. package/lib/collections/list.d.ts.map +1 -1
  58. package/lib/collections/list.js +67 -5
  59. package/lib/collections/list.js.map +1 -1
  60. package/lib/index.d.ts +1 -1
  61. package/lib/index.d.ts.map +1 -1
  62. package/lib/index.js +1 -1
  63. package/lib/index.js.map +1 -1
  64. package/lib/mergeTree.d.ts +9 -9
  65. package/lib/mergeTree.d.ts.map +1 -1
  66. package/lib/mergeTree.js +14 -6
  67. package/lib/mergeTree.js.map +1 -1
  68. package/lib/revertibles.js +1 -1
  69. package/lib/revertibles.js.map +1 -1
  70. package/lib/test/beastTest.spec.js +1 -1
  71. package/lib/test/beastTest.spec.js.map +1 -1
  72. package/lib/test/client.annotateMarker.spec.js +1 -1
  73. package/lib/test/client.annotateMarker.spec.js.map +1 -1
  74. package/lib/test/client.applyMsg.spec.js +21 -21
  75. package/lib/test/client.applyMsg.spec.js.map +1 -1
  76. package/lib/test/client.attributionFarm.spec.js +1 -1
  77. package/lib/test/client.attributionFarm.spec.js.map +1 -1
  78. package/lib/test/client.getPosition.spec.js +1 -0
  79. package/lib/test/client.getPosition.spec.js.map +1 -1
  80. package/lib/test/client.localReference.spec.js +62 -48
  81. package/lib/test/client.localReference.spec.js.map +1 -1
  82. package/lib/test/client.localReferenceFarm.spec.js +1 -0
  83. package/lib/test/client.localReferenceFarm.spec.js.map +1 -1
  84. package/lib/test/client.rollback.spec.js +8 -6
  85. package/lib/test/client.rollback.spec.js.map +1 -1
  86. package/lib/test/mergeTree.annotate.spec.js +25 -25
  87. package/lib/test/mergeTree.annotate.spec.js.map +1 -1
  88. package/lib/test/mergeTree.markRangeRemoved.spec.js +1 -1
  89. package/lib/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
  90. package/lib/test/obliterate.spec.js +4 -4
  91. package/lib/test/obliterate.spec.js.map +1 -1
  92. package/lib/test/resetPendingSegmentsToOp.spec.js +4 -4
  93. package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  94. package/lib/test/snapshot.utils.js +1 -1
  95. package/lib/test/snapshot.utils.js.map +1 -1
  96. package/lib/test/sortedSegmentSet.spec.js +3 -3
  97. package/lib/test/sortedSegmentSet.spec.js.map +1 -1
  98. package/lib/test/testClient.js +3 -3
  99. package/lib/test/testClient.js.map +1 -1
  100. package/lib/test/tracking.spec.js +7 -7
  101. package/lib/test/tracking.spec.js.map +1 -1
  102. package/lib/test/wordUnitTests.spec.js.map +1 -1
  103. package/package.json +16 -16
  104. package/src/client.ts +22 -20
  105. package/src/collections/list.ts +101 -5
  106. package/src/index.ts +3 -0
  107. package/src/mergeTree.ts +29 -23
  108. package/src/revertibles.ts +1 -1
package/src/client.ts CHANGED
@@ -878,17 +878,20 @@ export class Client extends TypedEventEmitter<IClientEvents> {
878
878
  const useNewSlidingBehavior = true;
879
879
  // Destructuring segment + offset is convenient and segment is reassigned
880
880
  // eslint-disable-next-line prefer-const
881
- let { segment: newSegment, offset: newOffset } = getSlideToSegoff(
881
+ const segOff = getSlideToSegoff(
882
882
  { segment: oldSegment, offset: oldOffset },
883
883
  slidePreference,
884
884
  reconnectingPerspective,
885
885
  useNewSlidingBehavior,
886
886
  );
887
887
 
888
- newSegment ??=
889
- slidePreference === SlidingPreference.FORWARD
890
- ? this._mergeTree.endOfTree
891
- : this._mergeTree.startOfTree;
888
+ const { segment: newSegment, offset: newOffset } = segOff ?? {
889
+ segment:
890
+ slidePreference === SlidingPreference.FORWARD
891
+ ? this._mergeTree.endOfTree
892
+ : this._mergeTree.startOfTree,
893
+ offset: 0,
894
+ };
892
895
 
893
896
  assert(
894
897
  isSegmentLeaf(newSegment) && newOffset !== undefined,
@@ -1612,10 +1615,12 @@ export class Client extends TypedEventEmitter<IClientEvents> {
1612
1615
  pos: number,
1613
1616
  sequenceArgs?: Pick<ISequencedDocumentMessage, "referenceSequenceNumber" | "clientId">,
1614
1617
  localSeq?: number,
1615
- ): {
1616
- segment: T | undefined;
1617
- offset: number | undefined;
1618
- } {
1618
+ ):
1619
+ | {
1620
+ segment: T;
1621
+ offset: number;
1622
+ }
1623
+ | undefined {
1619
1624
  let perspective: Perspective;
1620
1625
  const clientId =
1621
1626
  sequenceArgs === undefined
@@ -1630,20 +1635,17 @@ export class Client extends TypedEventEmitter<IClientEvents> {
1630
1635
  perspective = new PriorPerspective(refSeq, clientId);
1631
1636
  }
1632
1637
 
1633
- return this._mergeTree.getContainingSegment(pos, perspective) as {
1634
- segment: T | undefined;
1635
- offset: number | undefined;
1636
- };
1638
+ return this._mergeTree.getContainingSegment(pos, perspective) as
1639
+ | {
1640
+ segment: T;
1641
+ offset: number;
1642
+ }
1643
+ | undefined;
1637
1644
  }
1638
1645
 
1639
1646
  getPropertiesAtPosition(pos: number): PropertySet | undefined {
1640
- let propertiesAtPosition: PropertySet | undefined;
1641
1647
  const segoff = this.getContainingSegment(pos);
1642
- const seg = segoff.segment;
1643
- if (seg) {
1644
- propertiesAtPosition = seg.properties;
1645
- }
1646
- return propertiesAtPosition;
1648
+ return segoff?.segment?.properties;
1647
1649
  }
1648
1650
 
1649
1651
  getRangeExtentsOfPosition(pos: number): {
@@ -1654,7 +1656,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
1654
1656
  let posAfterEnd: number | undefined;
1655
1657
 
1656
1658
  const segoff = this.getContainingSegment(pos);
1657
- const seg = segoff.segment;
1659
+ const seg = segoff?.segment;
1658
1660
  if (seg) {
1659
1661
  posStart = this.getPosition(seg);
1660
1662
  posAfterEnd = posStart + seg.cachedLength;
@@ -5,15 +5,46 @@
5
5
 
6
6
  import { UsageError } from "@fluidframework/telemetry-utils/internal";
7
7
 
8
+ /**
9
+ * Represents a node in a doubly linked list.
10
+ * @internal
11
+ */
8
12
  export interface ListNode<T> {
13
+ /**
14
+ * The list this node belongs to, or undefined if not attached.
15
+ */
9
16
  readonly list: DoublyLinkedList<T> | undefined;
17
+ /**
18
+ * The data value stored in this node.
19
+ */
10
20
  readonly data: T;
21
+ /**
22
+ * The next node in the list, or undefined if this is the last node.
23
+ */
11
24
  readonly next: ListNode<T> | undefined;
25
+ /**
26
+ * The previous node in the list, or undefined if this is the first node.
27
+ */
12
28
  readonly prev: ListNode<T> | undefined;
29
+ /**
30
+ * Removes this node from its list.
31
+ * @returns The removed node, or undefined if not in a list.
32
+ */
33
+ remove(): ListNode<T> | undefined;
13
34
  }
14
35
 
36
+ /**
37
+ * Represents a range of nodes in a doubly linked list.
38
+ * @internal
39
+ */
15
40
  export interface ListNodeRange<T> {
41
+ /**
42
+ * The first node in the range.
43
+ */
16
44
  first: ListNode<T>;
45
+ /**
46
+ * The last node in the range.
47
+ */
17
48
  last: ListNode<T>;
18
49
  }
19
50
 
@@ -50,6 +81,9 @@ class DataNode<T> extends HeadNode<T> implements ListNode<T> {
50
81
  super(undefined);
51
82
  this.headNode = headNode;
52
83
  }
84
+ remove(): ListNode<T> | undefined {
85
+ return this.list?.remove(this);
86
+ }
53
87
  }
54
88
 
55
89
  function insertAfter<T>(node: DataNode<T> | HeadNode<T>, items: T[]): ListNodeRange<T> {
@@ -80,6 +114,11 @@ function insertAfter<T>(node: DataNode<T> | HeadNode<T>, items: T[]): ListNodeRa
80
114
  return newRange;
81
115
  }
82
116
 
117
+ /**
118
+ * A doubly linked list implementation with array-like methods and node access.
119
+ * @typeParam T - The type of data stored in the list nodes.
120
+ * @internal
121
+ */
83
122
  export class DoublyLinkedList<T>
84
123
  implements
85
124
  Iterable<ListNode<T>>,
@@ -87,12 +126,21 @@ export class DoublyLinkedList<T>
87
126
  // try to match array signature and semantics where possible
88
127
  Pick<ListNode<T>[], "pop" | "shift" | "length" | "includes">
89
128
  {
129
+ /**
130
+ * Creates a new doubly linked list optionally initialized with values.
131
+ * @param values - Optional iterable of values to populate the list.
132
+ */
90
133
  constructor(values?: Iterable<T>) {
91
134
  if (values !== undefined) {
92
135
  this.push(...values);
93
136
  }
94
137
  }
95
138
 
139
+ /**
140
+ * Finds the first node matching the predicate.
141
+ * @param predicate - Function to test each node.
142
+ * @returns The first matching node, or undefined if none found.
143
+ */
96
144
  find(
97
145
  predicate: (value: ListNode<T>, obj: DoublyLinkedList<T>) => unknown,
98
146
  ): ListNode<T> | undefined {
@@ -106,6 +154,10 @@ export class DoublyLinkedList<T>
106
154
  return found;
107
155
  }
108
156
 
157
+ /**
158
+ * Returns an iterable that maps each node to a new value.
159
+ * @param callbackfn - Function to produce a new value for each node.
160
+ */
109
161
  map<U>(callbackfn: (value: ListNode<T>) => U): Iterable<U> {
110
162
  let node = this.first;
111
163
  const iterator: IterableIterator<U> = {
@@ -124,6 +176,12 @@ export class DoublyLinkedList<T>
124
176
  return iterator;
125
177
  }
126
178
 
179
+ /**
180
+ * Inserts items after the specified node.
181
+ * @param preceding - The node to insert after.
182
+ * @param items - Items to insert.
183
+ * @returns The range of newly inserted nodes.
184
+ */
127
185
  insertAfter(preceding: ListNode<T>, ...items: T[]): ListNodeRange<T> {
128
186
  if (!this._includes(preceding)) {
129
187
  throw new Error("preceding not in list");
@@ -132,10 +190,19 @@ export class DoublyLinkedList<T>
132
190
  return insertAfter(preceding, items);
133
191
  }
134
192
 
193
+ /**
194
+ * Removes and returns the last node in the list.
195
+ * @returns The removed node, or undefined if the list is empty.
196
+ */
135
197
  pop(): ListNode<T> | undefined {
136
198
  return this.remove(this.last);
137
199
  }
138
200
 
201
+ /**
202
+ * Appends items to the end of the list.
203
+ * @param items - Items to append.
204
+ * @returns The range of newly inserted nodes.
205
+ */
139
206
  push(...items: T[]): ListNodeRange<T> {
140
207
  this._len += items.length;
141
208
  const start = this.headNode._prev;
@@ -143,14 +210,17 @@ export class DoublyLinkedList<T>
143
210
  }
144
211
 
145
212
  /**
146
- * Remove and return the first element
213
+ * Removes and returns the first node in the list.
214
+ * @returns The removed node, or undefined if the list is empty.
147
215
  */
148
216
  shift(): ListNode<T> | undefined {
149
217
  return this.remove(this.first);
150
218
  }
151
219
 
152
220
  /**
153
- * Insert `items` at start of list
221
+ * Inserts items at the start of the list.
222
+ * @param items - Items to insert.
223
+ * @returns The range of newly inserted nodes.
154
224
  */
155
225
  unshift(...items: T[]): ListNodeRange<T> {
156
226
  this._len += items.length;
@@ -158,9 +228,10 @@ export class DoublyLinkedList<T>
158
228
  }
159
229
 
160
230
  /**
161
- * Remove nodes starting at `start` until either the `end` node is reached
162
- * or until `count` nodes have been removed. Returns the removed nodes as
163
- * a separate linked list
231
+ * Removes nodes starting at `start` until `end` or `count` is reached.
232
+ * @param start - The node to start removing from.
233
+ * @param countOrEnd - The number of nodes to remove or the end node.
234
+ * @returns A new list containing the removed nodes.
164
235
  */
165
236
  splice(start: ListNode<T>, countOrEnd?: ListNode<T> | number): DoublyLinkedList<T> {
166
237
  const newList = new DoublyLinkedList<T>();
@@ -187,6 +258,11 @@ export class DoublyLinkedList<T>
187
258
  return newList;
188
259
  }
189
260
 
261
+ /**
262
+ * Checks if the node is in this list.
263
+ * @param node - The node to check.
264
+ * @returns True if the node is in the list.
265
+ */
190
266
  public includes(node: ListNode<T> | undefined): node is ListNode<T> {
191
267
  return this._includes(node);
192
268
  }
@@ -207,6 +283,11 @@ export class DoublyLinkedList<T>
207
283
  return undefined;
208
284
  }
209
285
 
286
+ /**
287
+ * Removes the specified node from the list.
288
+ * @param node - The node to remove.
289
+ * @returns The removed node, or undefined if not in the list.
290
+ */
210
291
  public remove(node: ListNode<T> | undefined): ListNode<T> | undefined {
211
292
  return this._remove(node);
212
293
  }
@@ -231,16 +312,31 @@ export class DoublyLinkedList<T>
231
312
 
232
313
  private _len: number = 0;
233
314
  private readonly headNode: HeadNode<T> | DataNode<T> = new HeadNode(this);
315
+
316
+ /**
317
+ * The number of nodes in the list.
318
+ */
234
319
  public get length(): number {
235
320
  return this._len;
236
321
  }
322
+
323
+ /**
324
+ * Whether the list is empty.
325
+ */
237
326
  public get empty(): boolean {
238
327
  return this._len === 0;
239
328
  }
329
+
330
+ /**
331
+ * The first node in the list, or undefined if empty.
332
+ */
240
333
  public get first(): ListNode<T> | undefined {
241
334
  return this.headNode.next;
242
335
  }
243
336
 
337
+ /**
338
+ * The last node in the list, or undefined if empty.
339
+ */
244
340
  public get last(): ListNode<T> | undefined {
245
341
  return this.headNode.prev;
246
342
  }
package/src/index.ts CHANGED
@@ -30,6 +30,9 @@ export {
30
30
  RBNodeActions,
31
31
  RedBlackTree,
32
32
  SortedDictionary,
33
+ DoublyLinkedList,
34
+ ListNode,
35
+ ListNodeRange,
33
36
  } from "./collections/index.js";
34
37
  export { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js";
35
38
  export {
package/src/mergeTree.ts CHANGED
@@ -469,15 +469,17 @@ function getSlideToSegment(
469
469
  * @internal
470
470
  */
471
471
  export function getSlideToSegoff(
472
- segoff: { segment: ISegmentInternal | undefined; offset: number | undefined },
472
+ segoff: { segment: ISegmentInternal; offset: number } | undefined,
473
473
  slidingPreference: SlidingPreference = SlidingPreference.FORWARD,
474
474
  perspective: Perspective = allAckedChangesPerspective,
475
475
  useNewSlidingBehavior: boolean = false,
476
- ): {
477
- segment: ISegmentInternal | undefined;
478
- offset: number | undefined;
479
- } {
480
- if (!isSegmentLeaf(segoff.segment)) {
476
+ ):
477
+ | {
478
+ segment: ISegmentInternal;
479
+ offset: number;
480
+ }
481
+ | undefined {
482
+ if (!isSegmentLeaf(segoff?.segment)) {
481
483
  return segoff;
482
484
  }
483
485
  const [segment, _] = getSlideToSegment(
@@ -490,8 +492,11 @@ export function getSlideToSegoff(
490
492
  if (segment === segoff.segment) {
491
493
  return segoff;
492
494
  }
493
- const offset =
494
- segment && segment.ordinal < segoff.segment.ordinal ? segment.cachedLength - 1 : 0;
495
+ if (segment === undefined) {
496
+ return undefined;
497
+ }
498
+
499
+ const offset = segment.ordinal < segoff.segment.ordinal ? segment.cachedLength - 1 : 0;
495
500
  return {
496
501
  segment,
497
502
  offset,
@@ -849,10 +854,12 @@ export class MergeTree {
849
854
  public getContainingSegment(
850
855
  pos: number,
851
856
  perspective: Perspective,
852
- ): {
853
- segment: ISegmentLeaf | undefined;
854
- offset: number | undefined;
855
- } {
857
+ ):
858
+ | {
859
+ segment: ISegmentLeaf;
860
+ offset: number;
861
+ }
862
+ | undefined {
856
863
  assert(
857
864
  perspective.localSeq === undefined ||
858
865
  perspective.clientId === this.collabWindow.clientId,
@@ -868,6 +875,9 @@ export class MergeTree {
868
875
  return false;
869
876
  };
870
877
  this.nodeMap(perspective, leaf, undefined, pos, pos + 1);
878
+ if (segment === undefined || offset === undefined) {
879
+ return undefined;
880
+ }
871
881
  return { segment, offset };
872
882
  }
873
883
 
@@ -1260,10 +1270,11 @@ export class MergeTree {
1260
1270
  ): Marker | undefined {
1261
1271
  let foundMarker: Marker | undefined;
1262
1272
 
1263
- const { segment } = this.getContainingSegment(startPos, this.localPerspective);
1264
- if (!isSegmentLeaf(segment)) {
1273
+ const segoff = this.getContainingSegment(startPos, this.localPerspective);
1274
+ if (!isSegmentLeaf(segoff?.segment)) {
1265
1275
  return undefined;
1266
1276
  }
1277
+ const { segment } = segoff;
1267
1278
 
1268
1279
  depthFirstNodeWalk(
1269
1280
  segment.parent,
@@ -1529,7 +1540,7 @@ export class MergeTree {
1529
1540
 
1530
1541
  if (isSegmentLeaf(segmentInfo?.segment)) {
1531
1542
  const segmentPosition = this.getPosition(segmentInfo.segment, this.localPerspective);
1532
- return segmentPosition + segmentInfo.offset!;
1543
+ return segmentPosition + segmentInfo.offset;
1533
1544
  } else {
1534
1545
  if (remoteClientPosition === this.getLength(remotePerspective)) {
1535
1546
  return this.getLength(this.localPerspective);
@@ -2085,14 +2096,9 @@ export class MergeTree {
2085
2096
  const createRefFromSequencePlace = (
2086
2097
  place: InteriorSequencePlace,
2087
2098
  ): LocalReferencePosition => {
2088
- const { segment: placeSeg, offset: placeOffset } = this.getContainingSegment(
2089
- place.pos,
2090
- perspective,
2091
- );
2092
- assert(
2093
- isSegmentLeaf(placeSeg) && placeOffset !== undefined,
2094
- 0xa3f /* segments cannot be undefined */,
2095
- );
2099
+ const segOff = this.getContainingSegment(place.pos, perspective);
2100
+ assert(isSegmentLeaf(segOff?.segment), 0xa3f /* segments cannot be undefined */);
2101
+ const { segment: placeSeg, offset: placeOffset } = segOff;
2096
2102
  return this.createLocalReferencePosition(
2097
2103
  placeSeg,
2098
2104
  placeOffset,
@@ -309,7 +309,7 @@ function revertLocalRemove(
309
309
  const insertSegment = mergeTreeWithRevert.getContainingSegment(
310
310
  realPos,
311
311
  mergeTreeWithRevert.localPerspective,
312
- ).segment;
312
+ )?.segment;
313
313
  assertSegmentLeaf(insertSegment);
314
314
 
315
315
  const localSlideFilter = (lref: LocalReferencePosition): boolean =>