@fluidframework/merge-tree 2.1.0 → 2.2.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 (152) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +2 -2
  3. package/api-report/merge-tree.legacy.alpha.api.md +47 -11
  4. package/dist/attributionCollection.d.ts.map +1 -1
  5. package/dist/attributionCollection.js +9 -2
  6. package/dist/attributionCollection.js.map +1 -1
  7. package/dist/attributionPolicy.d.ts.map +1 -1
  8. package/dist/attributionPolicy.js +5 -9
  9. package/dist/attributionPolicy.js.map +1 -1
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +18 -19
  12. package/dist/client.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/legacy.d.ts +4 -0
  18. package/dist/localReference.d.ts +6 -0
  19. package/dist/localReference.d.ts.map +1 -1
  20. package/dist/localReference.js.map +1 -1
  21. package/dist/mergeTree.d.ts +11 -1
  22. package/dist/mergeTree.d.ts.map +1 -1
  23. package/dist/mergeTree.js +51 -14
  24. package/dist/mergeTree.js.map +1 -1
  25. package/dist/mergeTreeNodes.d.ts +35 -1
  26. package/dist/mergeTreeNodes.d.ts.map +1 -1
  27. package/dist/mergeTreeNodes.js +35 -12
  28. package/dist/mergeTreeNodes.js.map +1 -1
  29. package/dist/properties.d.ts.map +1 -1
  30. package/dist/properties.js +7 -21
  31. package/dist/properties.js.map +1 -1
  32. package/dist/referencePositions.d.ts +6 -0
  33. package/dist/referencePositions.d.ts.map +1 -1
  34. package/dist/referencePositions.js.map +1 -1
  35. package/dist/segmentGroupCollection.d.ts +3 -0
  36. package/dist/segmentGroupCollection.d.ts.map +1 -1
  37. package/dist/segmentGroupCollection.js +3 -0
  38. package/dist/segmentGroupCollection.js.map +1 -1
  39. package/dist/segmentPropertiesManager.d.ts +8 -0
  40. package/dist/segmentPropertiesManager.d.ts.map +1 -1
  41. package/dist/segmentPropertiesManager.js +11 -8
  42. package/dist/segmentPropertiesManager.js.map +1 -1
  43. package/dist/sequencePlace.d.ts +66 -0
  44. package/dist/sequencePlace.d.ts.map +1 -0
  45. package/dist/sequencePlace.js +41 -0
  46. package/dist/sequencePlace.js.map +1 -0
  47. package/dist/snapshotlegacy.js +1 -1
  48. package/dist/snapshotlegacy.js.map +1 -1
  49. package/dist/test/obliterate.rangeExpansion.spec.d.ts +6 -0
  50. package/dist/test/obliterate.rangeExpansion.spec.d.ts.map +1 -0
  51. package/dist/test/obliterate.rangeExpansion.spec.js +288 -0
  52. package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -0
  53. package/dist/test/obliterate.reconnect.spec.js +5 -5
  54. package/dist/test/obliterate.reconnect.spec.js.map +1 -1
  55. package/dist/test/reconnectHelper.d.ts +3 -3
  56. package/dist/test/reconnectHelper.d.ts.map +1 -1
  57. package/dist/test/reconnectHelper.js +14 -3
  58. package/dist/test/reconnectHelper.js.map +1 -1
  59. package/dist/test/resetPendingSegmentsToOp.spec.js +12 -0
  60. package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  61. package/dist/test/testClient.d.ts +3 -3
  62. package/dist/test/testClient.d.ts.map +1 -1
  63. package/dist/test/testClient.js +4 -16
  64. package/dist/test/testClient.js.map +1 -1
  65. package/dist/test/text.js +1 -1
  66. package/dist/test/text.js.map +1 -1
  67. package/dist/textSegment.d.ts +2 -2
  68. package/dist/textSegment.d.ts.map +1 -1
  69. package/dist/textSegment.js +3 -7
  70. package/dist/textSegment.js.map +1 -1
  71. package/lib/attributionCollection.d.ts.map +1 -1
  72. package/lib/attributionCollection.js +9 -2
  73. package/lib/attributionCollection.js.map +1 -1
  74. package/lib/attributionPolicy.d.ts.map +1 -1
  75. package/lib/attributionPolicy.js +5 -9
  76. package/lib/attributionPolicy.js.map +1 -1
  77. package/lib/client.d.ts.map +1 -1
  78. package/lib/client.js +21 -22
  79. package/lib/client.js.map +1 -1
  80. package/lib/index.d.ts +1 -0
  81. package/lib/index.d.ts.map +1 -1
  82. package/lib/index.js +1 -0
  83. package/lib/index.js.map +1 -1
  84. package/lib/legacy.d.ts +4 -0
  85. package/lib/localReference.d.ts +6 -0
  86. package/lib/localReference.d.ts.map +1 -1
  87. package/lib/localReference.js.map +1 -1
  88. package/lib/mergeTree.d.ts +11 -1
  89. package/lib/mergeTree.d.ts.map +1 -1
  90. package/lib/mergeTree.js +49 -13
  91. package/lib/mergeTree.js.map +1 -1
  92. package/lib/mergeTreeNodes.d.ts +35 -1
  93. package/lib/mergeTreeNodes.d.ts.map +1 -1
  94. package/lib/mergeTreeNodes.js +35 -12
  95. package/lib/mergeTreeNodes.js.map +1 -1
  96. package/lib/properties.d.ts.map +1 -1
  97. package/lib/properties.js +7 -21
  98. package/lib/properties.js.map +1 -1
  99. package/lib/referencePositions.d.ts +6 -0
  100. package/lib/referencePositions.d.ts.map +1 -1
  101. package/lib/referencePositions.js.map +1 -1
  102. package/lib/segmentGroupCollection.d.ts +3 -0
  103. package/lib/segmentGroupCollection.d.ts.map +1 -1
  104. package/lib/segmentGroupCollection.js +3 -0
  105. package/lib/segmentGroupCollection.js.map +1 -1
  106. package/lib/segmentPropertiesManager.d.ts +8 -0
  107. package/lib/segmentPropertiesManager.d.ts.map +1 -1
  108. package/lib/segmentPropertiesManager.js +12 -9
  109. package/lib/segmentPropertiesManager.js.map +1 -1
  110. package/lib/sequencePlace.d.ts +66 -0
  111. package/lib/sequencePlace.d.ts.map +1 -0
  112. package/lib/sequencePlace.js +37 -0
  113. package/lib/sequencePlace.js.map +1 -0
  114. package/lib/snapshotlegacy.js +1 -1
  115. package/lib/snapshotlegacy.js.map +1 -1
  116. package/lib/test/obliterate.rangeExpansion.spec.d.ts +6 -0
  117. package/lib/test/obliterate.rangeExpansion.spec.d.ts.map +1 -0
  118. package/lib/test/obliterate.rangeExpansion.spec.js +286 -0
  119. package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -0
  120. package/lib/test/obliterate.reconnect.spec.js +5 -5
  121. package/lib/test/obliterate.reconnect.spec.js.map +1 -1
  122. package/lib/test/reconnectHelper.d.ts +3 -3
  123. package/lib/test/reconnectHelper.d.ts.map +1 -1
  124. package/lib/test/reconnectHelper.js +14 -3
  125. package/lib/test/reconnectHelper.js.map +1 -1
  126. package/lib/test/resetPendingSegmentsToOp.spec.js +12 -0
  127. package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  128. package/lib/test/testClient.d.ts +3 -3
  129. package/lib/test/testClient.d.ts.map +1 -1
  130. package/lib/test/testClient.js +4 -16
  131. package/lib/test/testClient.js.map +1 -1
  132. package/lib/test/text.js +1 -1
  133. package/lib/test/text.js.map +1 -1
  134. package/lib/textSegment.d.ts +2 -2
  135. package/lib/textSegment.d.ts.map +1 -1
  136. package/lib/textSegment.js +3 -7
  137. package/lib/textSegment.js.map +1 -1
  138. package/package.json +19 -19
  139. package/src/attributionCollection.ts +12 -2
  140. package/src/attributionPolicy.ts +6 -7
  141. package/src/client.ts +28 -23
  142. package/src/index.ts +6 -0
  143. package/src/localReference.ts +8 -1
  144. package/src/mergeTree.ts +74 -17
  145. package/src/mergeTreeNodes.ts +66 -14
  146. package/src/properties.ts +7 -22
  147. package/src/referencePositions.ts +6 -0
  148. package/src/segmentGroupCollection.ts +3 -0
  149. package/src/segmentPropertiesManager.ts +13 -9
  150. package/src/sequencePlace.ts +89 -0
  151. package/src/snapshotlegacy.ts +1 -1
  152. package/src/textSegment.ts +8 -9
@@ -136,25 +136,22 @@ function createPropertyTrackingMergeTreeCallbacks(
136
136
  ): AttributionCallbacks {
137
137
  const toTrack = propNames.map((entry) => ({ propName: entry, channelName: entry }));
138
138
  const attributeAnnotateOnSegments = (
139
+ isLocal: boolean,
139
140
  deltaSegments: IMergeTreeSegmentDelta[],
140
- { op, sequencedMessage }: IMergeTreeDeltaOpArgs,
141
+ { op }: IMergeTreeDeltaOpArgs,
141
142
  key: AttributionKey,
142
143
  ): void => {
143
- for (const { segment } of deltaSegments) {
144
+ for (const { segment, propertyDeltas } of deltaSegments) {
144
145
  for (const { propName, channelName } of toTrack) {
145
146
  const shouldAttributeInsert =
146
147
  op.type === MergeTreeDeltaType.INSERT &&
147
148
  segment.properties?.[propName] !== undefined;
148
149
 
149
- const isLocal = sequencedMessage === undefined;
150
150
  const shouldAttributeAnnotate =
151
151
  op.type === MergeTreeDeltaType.ANNOTATE &&
152
152
  // Only attribute annotations which change the tracked property
153
153
  op.props[propName] !== undefined &&
154
- // Local changes to the tracked property always take effect
155
- (isLocal ||
156
- // Acked changes only take effect if there isn't a pending local change
157
- (!isLocal && !segment.propertyManager?.hasPendingProperty(propName)));
154
+ (isLocal || (propertyDeltas !== undefined && propName in propertyDeltas));
158
155
 
159
156
  if (shouldAttributeInsert || shouldAttributeAnnotate) {
160
157
  segment.attribution?.update(
@@ -170,6 +167,7 @@ function createPropertyTrackingMergeTreeCallbacks(
170
167
  const { op, sequencedMessage } = opArgs;
171
168
  if (op.type === MergeTreeDeltaType.ANNOTATE || op.type === MergeTreeDeltaType.INSERT) {
172
169
  attributeAnnotateOnSegments(
170
+ sequencedMessage === undefined,
173
171
  deltaSegments,
174
172
  opArgs,
175
173
  getAttributionKey(client, sequencedMessage),
@@ -179,6 +177,7 @@ function createPropertyTrackingMergeTreeCallbacks(
179
177
  maintenance: ({ deltaSegments, operation }, opArgs, client): void => {
180
178
  if (operation === MergeTreeMaintenanceType.ACKNOWLEDGED && opArgs !== undefined) {
181
179
  attributeAnnotateOnSegments(
180
+ true,
182
181
  deltaSegments,
183
182
  opArgs,
184
183
  getAttributionKey(client, opArgs.sequencedMessage),
package/src/client.ts CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
9
9
  import { type IEventThisPlaceHolder, IFluidHandle } from "@fluidframework/core-interfaces";
10
- import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
10
+ import { assert, unreachableCase, isObject } from "@fluidframework/core-utils/internal";
11
11
  import {
12
12
  IFluidDataStoreRuntime,
13
13
  IChannelStorageService,
@@ -29,7 +29,7 @@ import { MergeTreeTextHelper } from "./MergeTreeTextHelper.js";
29
29
  import { DoublyLinkedList, RedBlackTree } from "./collections/index.js";
30
30
  import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js";
31
31
  import { LocalReferencePosition, SlidingPreference } from "./localReference.js";
32
- import { IMergeTreeOptions, MergeTree } from "./mergeTree.js";
32
+ import { IMergeTreeOptions, MergeTree, errorIfOptionNotTrue } from "./mergeTree.js";
33
33
  import type {
34
34
  IMergeTreeClientSequenceArgs,
35
35
  IMergeTreeDeltaCallbackArgs,
@@ -40,7 +40,6 @@ import { walkAllChildSegments } from "./mergeTreeNodeWalk.js";
40
40
  import {
41
41
  // eslint-disable-next-line import/no-deprecated
42
42
  CollaborationWindow,
43
- IMoveInfo,
44
43
  ISegment,
45
44
  ISegmentAction,
46
45
  ISegmentLeaf,
@@ -48,6 +47,7 @@ import {
48
47
  // eslint-disable-next-line import/no-deprecated
49
48
  SegmentGroup,
50
49
  compareStrings,
50
+ toMoveInfo,
51
51
  } from "./mergeTreeNodes.js";
52
52
  import {
53
53
  createAnnotateMarkerOp,
@@ -73,7 +73,7 @@ import {
73
73
  MergeTreeDeltaType,
74
74
  ReferenceType,
75
75
  } from "./ops.js";
76
- import { PropertySet, createMap } from "./properties.js";
76
+ import { PropertySet } from "./properties.js";
77
77
  import { DetachedReferencePosition, ReferencePosition } from "./referencePositions.js";
78
78
  import { SnapshotLoader } from "./snapshotLoader.js";
79
79
  import { SnapshotV1 } from "./snapshotV1.js";
@@ -84,14 +84,6 @@ import { IMergeTreeTextHelper } from "./textSegment.js";
84
84
  type IMergeTreeDeltaRemoteOpArgs = Omit<IMergeTreeDeltaOpArgs, "sequencedMessage"> &
85
85
  Required<Pick<IMergeTreeDeltaOpArgs, "sequencedMessage">>;
86
86
 
87
- function removeMoveInfo(segment: Partial<IMoveInfo>): void {
88
- delete segment.movedSeq;
89
- delete segment.movedSeqs;
90
- delete segment.localMovedSeq;
91
- delete segment.movedClientIds;
92
- delete segment.wasMovedOnInsert;
93
- }
94
-
95
87
  /**
96
88
  * A range [start, end)
97
89
  * @internal
@@ -825,18 +817,30 @@ export class Client extends TypedEventEmitter<IClientEvents> {
825
817
  segment.seq === UnassignedSequenceNumber,
826
818
  0x037 /* "Segment already has assigned sequence number" */,
827
819
  );
828
- let segInsertOp = segment;
829
- // The suppression is needed because the segment needs to have a type of any.
830
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
831
- if (typeof resetOp.seg === "object" && resetOp.seg.props !== undefined) {
832
- segInsertOp = segment.clone();
833
- segInsertOp.properties = createMap();
834
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
835
- segInsertOp.addProperties(resetOp.seg.props);
836
- }
837
- if (segment.movedSeq !== UnassignedSequenceNumber) {
838
- removeMoveInfo(segment);
820
+ const moveInfo = toMoveInfo(segment);
821
+
822
+ if (moveInfo !== undefined) {
823
+ errorIfOptionNotTrue(
824
+ this._mergeTree.options,
825
+ "mergeTreeEnableObliterateReconnect",
826
+ );
827
+ if (moveInfo.movedSeq !== UnassignedSequenceNumber) {
828
+ // the segment was remotely obliterated, so is considered removed
829
+ // we set the seq to the universal seq and remove the local seq,
830
+ // so its length is not considered for subsequent local changes
831
+ // this allows us to not send the op as even the local client will ignore the segment
832
+ segment.seq = UniversalSequenceNumber;
833
+ segment.localSeq = undefined;
834
+ break;
835
+ }
839
836
  }
837
+
838
+ const segInsertOp: ISegment = segment.clone();
839
+ const opProps =
840
+ isObject(resetOp.seg) && "props" in resetOp.seg && isObject(resetOp.seg.props)
841
+ ? { ...resetOp.seg.props }
842
+ : undefined;
843
+ segInsertOp.properties = opProps;
840
844
  newOp = createInsertSegmentOp(segmentPosition, segInsertOp);
841
845
  break;
842
846
  }
@@ -857,6 +861,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
857
861
  break;
858
862
  }
859
863
  case MergeTreeDeltaType.OBLITERATE: {
864
+ errorIfOptionNotTrue(this._mergeTree.options, "mergeTreeEnableObliterateReconnect");
860
865
  if (
861
866
  segment.localMovedSeq !== undefined &&
862
867
  segment.movedSeq === UnassignedSequenceNumber &&
package/src/index.ts CHANGED
@@ -122,6 +122,12 @@ export {
122
122
  } from "./referencePositions.js";
123
123
  export { SegmentGroupCollection } from "./segmentGroupCollection.js";
124
124
  export { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js";
125
+ export {
126
+ InteriorSequencePlace,
127
+ Side,
128
+ SequencePlace,
129
+ endpointPosAndSide,
130
+ } from "./sequencePlace.js";
125
131
  export { SortedSet } from "./sortedSet.js";
126
132
  export { SortedSegmentSet, SortedSegmentSetItem } from "./sortedSegmentSet.js";
127
133
  export { IJSONTextSegment, IMergeTreeTextHelper, TextSegment } from "./textSegment.js";
@@ -70,6 +70,13 @@ export interface LocalReferencePosition extends ReferencePosition {
70
70
  * special segments representing the position before or after the tree
71
71
  */
72
72
  readonly canSlideToEndpoint?: boolean;
73
+
74
+ /**
75
+ * @param newProps - Properties to add to this reference.
76
+ * @remarks Note that merge-tree does not broadcast changes to other clients. It is up to the consumer
77
+ * to ensure broadcast happens if that is desired.
78
+ */
79
+ addProperties(newProps: PropertySet): void;
73
80
  }
74
81
 
75
82
  /**
@@ -125,7 +132,7 @@ class LocalReference implements LocalReferencePosition {
125
132
  this.offset = offset;
126
133
  }
127
134
 
128
- public isLeaf(): boolean {
135
+ public isLeaf(): this is ISegment {
129
136
  return false;
130
137
  }
131
138
 
package/src/mergeTree.ts CHANGED
@@ -85,7 +85,9 @@ import {
85
85
  refHasTileLabel,
86
86
  refTypeIncludesFlag,
87
87
  } from "./referencePositions.js";
88
+ // eslint-disable-next-line import/no-deprecated
88
89
  import { PropertiesRollback } from "./segmentPropertiesManager.js";
90
+ import { endpointPosAndSide, type SequencePlace } from "./sequencePlace.js";
89
91
  import { zamboniSegments } from "./zamboni.js";
90
92
 
91
93
  function wasRemovedAfter(seg: ISegment, seq: number): boolean {
@@ -190,6 +192,23 @@ export interface IMergeTreeOptions {
190
192
  * Default value: false
191
193
  */
192
194
  mergeTreeEnableObliterate?: boolean;
195
+
196
+ /**
197
+ * Enables support for reconnecting when obliterate operations are present
198
+ *
199
+ * Obliterate is currently experimental and may not work in all scenarios.
200
+ *
201
+ * @defaultValue `false`
202
+ */
203
+ mergeTreeEnableObliterateReconnect?: boolean;
204
+ }
205
+ export function errorIfOptionNotTrue(
206
+ options: IMergeTreeOptions | undefined,
207
+ option: keyof IMergeTreeOptions,
208
+ ): void {
209
+ if (options?.[option] !== true) {
210
+ throw new Error(`${option} is not enabled.`);
211
+ }
193
212
  }
194
213
 
195
214
  /**
@@ -1636,7 +1655,11 @@ export class MergeTree {
1636
1655
  return { next };
1637
1656
  };
1638
1657
 
1639
- private ensureIntervalBoundary(pos: number, refSeq: number, clientId: number): void {
1658
+ private ensureIntervalBoundary(
1659
+ pos: number | "start" | "end",
1660
+ refSeq: number,
1661
+ clientId: number,
1662
+ ): void {
1640
1663
  const splitNode = this.insertingWalk(
1641
1664
  this.root,
1642
1665
  pos,
@@ -1684,14 +1707,22 @@ export class MergeTree {
1684
1707
 
1685
1708
  private insertingWalk(
1686
1709
  block: MergeBlock,
1687
- pos: number,
1710
+ pos: number | "start" | "end",
1688
1711
  refSeq: number,
1689
1712
  clientId: number,
1690
1713
  seq: number,
1691
1714
  context: InsertContext,
1692
1715
  isLastChildBlock: boolean = true,
1693
1716
  ): MergeBlock | undefined {
1694
- let _pos = pos;
1717
+ let _pos: number;
1718
+ if (pos === "start") {
1719
+ _pos = 0;
1720
+ } else if (pos === "end") {
1721
+ _pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
1722
+ } else {
1723
+ _pos = pos;
1724
+ }
1725
+
1695
1726
  const children = block.children;
1696
1727
  let childIndex: number;
1697
1728
  let child: IMergeNode;
@@ -1849,6 +1880,7 @@ export class MergeTree {
1849
1880
  clientId: number,
1850
1881
  seq: number,
1851
1882
  opArgs: IMergeTreeDeltaOpArgs,
1883
+ // eslint-disable-next-line import/no-deprecated
1852
1884
  rollback: PropertiesRollback = PropertiesRollback.None,
1853
1885
  ): void {
1854
1886
  this.ensureIntervalBoundary(start, refSeq, clientId);
@@ -1910,20 +1942,30 @@ export class MergeTree {
1910
1942
  }
1911
1943
 
1912
1944
  public obliterateRange(
1913
- start: number,
1914
- end: number,
1945
+ start: SequencePlace,
1946
+ end: SequencePlace,
1915
1947
  refSeq: number,
1916
1948
  clientId: number,
1917
1949
  seq: number,
1918
1950
  overwrite: boolean = false,
1919
1951
  opArgs: IMergeTreeDeltaOpArgs,
1920
1952
  ): void {
1921
- if (!this.options?.mergeTreeEnableObliterate) {
1922
- throw new UsageError("Attempted to send obliterate op without enabling feature flag.");
1923
- }
1953
+ errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
1924
1954
 
1925
- this.ensureIntervalBoundary(start, refSeq, clientId);
1926
- this.ensureIntervalBoundary(end, refSeq, clientId);
1955
+ const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
1956
+
1957
+ assert(
1958
+ startPos !== undefined &&
1959
+ endPos !== undefined &&
1960
+ startSide !== undefined &&
1961
+ endSide !== undefined &&
1962
+ startPos !== "end" &&
1963
+ endPos !== "start",
1964
+ 0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */,
1965
+ );
1966
+
1967
+ this.ensureIntervalBoundary(startPos, refSeq, clientId);
1968
+ this.ensureIntervalBoundary(endPos, refSeq, clientId);
1927
1969
 
1928
1970
  let _overwrite = overwrite;
1929
1971
  const localOverlapWithRefs: ISegment[] = [];
@@ -1944,6 +1986,8 @@ export class MergeTree {
1944
1986
  _end: number,
1945
1987
  ): boolean => {
1946
1988
  const existingMoveInfo = toMoveInfo(segment);
1989
+ if (startSide) segment.startSide = startSide;
1990
+ if (endSide) segment.endSide = endSide;
1947
1991
 
1948
1992
  if (
1949
1993
  clientId !== segment.clientId &&
@@ -2259,6 +2303,7 @@ export class MergeTree {
2259
2303
  this.collabWindow.clientId,
2260
2304
  UniversalSequenceNumber,
2261
2305
  { op: annotateOp },
2306
+ // eslint-disable-next-line import/no-deprecated
2262
2307
  PropertiesRollback.Rollback,
2263
2308
  );
2264
2309
  i++;
@@ -2662,18 +2707,28 @@ export class MergeTree {
2662
2707
  leaf: ISegmentAction<TClientData>,
2663
2708
  accum: TClientData,
2664
2709
  post?: BlockAction<TClientData>,
2665
- start: number = 0,
2666
- end?: number,
2710
+ start: SequencePlace = 0,
2711
+ end?: SequencePlace,
2667
2712
  localSeq?: number,
2668
2713
  visibilitySeq: number = refSeq,
2669
2714
  ): void {
2670
- const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
2671
- if (endPos === start) {
2715
+ const maybeEndPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
2716
+ if (maybeEndPos === start) {
2672
2717
  return;
2673
2718
  }
2674
2719
 
2675
2720
  let pos = 0;
2721
+ let { startPos, endPos } = endpointPosAndSide(start, end);
2676
2722
 
2723
+ startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
2724
+ endPos =
2725
+ endPos === "end" || endPos === undefined
2726
+ ? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
2727
+ : endPos;
2728
+ assert(
2729
+ startPos !== "end" && endPos !== "start",
2730
+ 0x9e3 /* start cannot be 'end' and end cannot be 'start' */,
2731
+ );
2677
2732
  depthFirstNodeWalk(
2678
2733
  this.root,
2679
2734
  this.root.children[0],
@@ -2701,13 +2756,15 @@ export class MergeTree {
2701
2756
 
2702
2757
  const nextPos = pos + lenAtRefSeq;
2703
2758
  // start is beyond the current node, so we can skip it
2704
- if (start >= nextPos) {
2759
+ if (typeof startPos === "number" && startPos >= nextPos) {
2705
2760
  pos = nextPos;
2706
2761
  return NodeAction.Skip;
2707
2762
  }
2708
2763
 
2709
2764
  if (node.isLeaf()) {
2710
- if (leaf(node, pos, refSeq, clientId, start - pos, endPos - pos, accum) === false) {
2765
+ if (
2766
+ leaf(node, pos, refSeq, clientId, startPos - pos, endPos - pos, accum) === false
2767
+ ) {
2711
2768
  return NodeAction.Exit;
2712
2769
  }
2713
2770
  pos = nextPos;
@@ -2717,7 +2774,7 @@ export class MergeTree {
2717
2774
  post === undefined
2718
2775
  ? undefined
2719
2776
  : (block): boolean =>
2720
- post(block, pos, refSeq, clientId, start - pos, endPos - pos, accum),
2777
+ post(block, pos, refSeq, clientId, startPos - pos, endPos - pos, accum),
2721
2778
  );
2722
2779
  }
2723
2780
  }
@@ -26,8 +26,11 @@ import {
26
26
  refGetTileLabels,
27
27
  refTypeIncludesFlag,
28
28
  } from "./referencePositions.js";
29
+ // eslint-disable-next-line import/no-deprecated
29
30
  import { SegmentGroupCollection } from "./segmentGroupCollection.js";
31
+ // eslint-disable-next-line import/no-deprecated
30
32
  import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js";
33
+ import { Side } from "./sequencePlace.js";
31
34
 
32
35
  /**
33
36
  * Common properties for a node in a merge tree.
@@ -181,6 +184,10 @@ export function toMoveInfo(maybe: Partial<IMoveInfo> | undefined): IMoveInfo | u
181
184
  */
182
185
  export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Partial<IMoveInfo> {
183
186
  readonly type: string;
187
+ /**
188
+ * @deprecated - This property should not be used externally and will be removed in a subsequent release.
189
+ */
190
+ // eslint-disable-next-line import/no-deprecated
184
191
  readonly segmentGroups: SegmentGroupCollection;
185
192
  readonly trackingCollection: TrackingGroupCollection;
186
193
  /**
@@ -217,7 +224,10 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
217
224
 
218
225
  /**
219
226
  * Manages pending local state for properties on this segment.
227
+ *
228
+ * @deprecated - This property should not be used externally and will be removed in a subsequent release.
220
229
  */
230
+ // eslint-disable-next-line import/no-deprecated
221
231
  propertyManager?: PropertiesManager;
222
232
  /**
223
233
  * Local seq at which this segment was inserted.
@@ -253,10 +263,20 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
253
263
  * Properties that have been added to this segment via annotation.
254
264
  */
255
265
  properties?: PropertySet;
266
+ /**
267
+ * Stores side information passed to obliterate for the start of a range.
268
+ */
269
+ startSide?: Side.Before | Side.After;
270
+ /**
271
+ * Stores side information passed to obliterate for the end of a range.
272
+ */
273
+ endSide?: Side.Before | Side.After;
256
274
 
257
275
  /**
258
276
  * Add properties to this segment via annotation.
259
277
  *
278
+ * @deprecated - This function should not be used externally and will be removed in a subsequent release.
279
+ *
260
280
  * @remarks This function should not be called directly. Properties should
261
281
  * be added through the `annotateRange` functions.
262
282
  */
@@ -264,6 +284,7 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
264
284
  newProps: PropertySet,
265
285
  seq?: number,
266
286
  collaborating?: boolean,
287
+ // eslint-disable-next-line import/no-deprecated
267
288
  rollback?: PropertiesRollback,
268
289
  ): PropertySet;
269
290
  clone(): ISegment;
@@ -274,6 +295,7 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
274
295
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
296
  toJSONObject(): any;
276
297
  /**
298
+ * @deprecated - This function should not be used externally and will be removed in a subsequent release.
277
299
  * Acks the current segment against the segment group, op, and merge tree.
278
300
  *
279
301
  * @param segmentGroup - Pending segment group associated with this op.
@@ -499,12 +521,22 @@ export abstract class BaseSegment implements ISegment {
499
521
  public ordinal: string = "";
500
522
  public cachedLength: number = 0;
501
523
 
524
+ /**
525
+ * {@inheritdoc ISegment.segmentGroups}
526
+ * @deprecated - This property should not be used externally and will be removed in a subsequent release.
527
+ */
528
+ // eslint-disable-next-line import/no-deprecated
502
529
  public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
503
530
  public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(
504
531
  this,
505
532
  );
506
533
  /***/
507
534
  public attribution?: IAttributionCollection<AttributionKey>;
535
+ /**
536
+ * {@inheritdoc ISegment.propertyManager}
537
+ * @deprecated - This property should not be used externally and will be removed in a subsequent release.
538
+ */
539
+ // eslint-disable-next-line import/no-deprecated
508
540
  public propertyManager?: PropertiesManager;
509
541
  public properties?: PropertySet;
510
542
  public localRefs?: LocalReferenceCollection;
@@ -513,12 +545,24 @@ export abstract class BaseSegment implements ISegment {
513
545
  public localRemovedSeq?: number;
514
546
  public localMovedSeq?: number;
515
547
 
548
+ public constructor(properties?: PropertySet) {
549
+ if (properties !== undefined) {
550
+ this.properties = clone(properties);
551
+ }
552
+ }
553
+
554
+ /**
555
+ * {@inheritdoc ISegment.addProperties}
556
+ * @deprecated - This function should not be used externally and will be removed in a subsequent release.
557
+ */
516
558
  public addProperties(
517
559
  newProps: PropertySet,
518
560
  seq?: number,
519
561
  collaborating?: boolean,
562
+ // eslint-disable-next-line import/no-deprecated
520
563
  rollback: PropertiesRollback = PropertiesRollback.None,
521
564
  ): PropertySet {
565
+ // eslint-disable-next-line import/no-deprecated
522
566
  this.propertyManager ??= new PropertiesManager();
523
567
  // A property set must be able to hold properties of any type, so the any is needed.
524
568
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -569,6 +613,10 @@ export abstract class BaseSegment implements ISegment {
569
613
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
570
614
  public abstract toJSONObject(): any;
571
615
 
616
+ /**
617
+ * {@inheritdoc ISegment.ack}
618
+ * @deprecated - This function should not be used externally and will be removed in a subsequent release.
619
+ */
572
620
  public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean {
573
621
  const currentSegmentGroup = this.segmentGroups.dequeue();
574
622
  assert(
@@ -676,13 +724,18 @@ export abstract class BaseSegment implements ISegment {
676
724
  }
677
725
 
678
726
  private copyPropertiesTo(other: ISegment): void {
679
- if (this.propertyManager && this.properties) {
680
- other.propertyManager = new PropertiesManager();
681
- other.properties = this.propertyManager.copyTo(
682
- this.properties,
683
- other.properties,
684
- other.propertyManager,
685
- );
727
+ if (this.properties !== undefined) {
728
+ if (this.propertyManager) {
729
+ // eslint-disable-next-line import/no-deprecated
730
+ other.propertyManager = new PropertiesManager();
731
+ other.properties = this.propertyManager.copyTo(
732
+ this.properties,
733
+ other.properties,
734
+ other.propertyManager,
735
+ );
736
+ } else {
737
+ other.properties = clone(this.properties);
738
+ }
686
739
  }
687
740
  }
688
741
 
@@ -755,15 +808,14 @@ export class Marker extends BaseSegment implements ReferencePosition, ISegment {
755
808
  public readonly type = Marker.type;
756
809
 
757
810
  public static make(refType: ReferenceType, props?: PropertySet): Marker {
758
- const marker = new Marker(refType);
759
- if (props) {
760
- marker.addProperties(props);
761
- }
762
- return marker;
811
+ return new Marker(refType, props);
763
812
  }
764
813
 
765
- constructor(public refType: ReferenceType) {
766
- super();
814
+ constructor(
815
+ public refType: ReferenceType,
816
+ props?: PropertySet,
817
+ ) {
818
+ super(props);
767
819
  this.cachedLength = 1;
768
820
  }
769
821
 
package/src/properties.ts CHANGED
@@ -69,17 +69,14 @@ export function matchProperties(
69
69
  */
70
70
  export function extend<T>(base: MapLike<T>, extension: MapLike<T> | undefined): MapLike<T> {
71
71
  if (extension !== undefined) {
72
- // eslint-disable-next-line guard-for-in, no-restricted-syntax
73
- for (const key in extension) {
74
- const v = extension[key];
75
- // TODO Non null asserting, why is this not null?
76
- if (v === null) {
72
+ for (const [key, v] of Object.entries(extension)) {
73
+ if (v === undefined) {
74
+ continue;
75
+ } else if (v === null) {
77
76
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
78
77
  delete base[key];
79
78
  } else {
80
- // Non null aseerting here since we are checking if v is not null
81
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
82
- base[key] = v!;
79
+ base[key] = v;
83
80
  }
84
81
  }
85
82
  }
@@ -96,16 +93,7 @@ export function clone<T>(extension: MapLike<T> | undefined): MapLike<T> | undefi
96
93
  return undefined;
97
94
  }
98
95
  const cloneMap = createMap<T>();
99
- // eslint-disable-next-line guard-for-in, no-restricted-syntax
100
- for (const key in extension) {
101
- const v = extension[key];
102
- if (v !== null) {
103
- // If `v` is undefined, undefined must have been assignable to `T`.
104
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
105
- cloneMap[key] = v!;
106
- }
107
- }
108
- return cloneMap;
96
+ return extend(cloneMap, extension);
109
97
  }
110
98
 
111
99
  /**
@@ -118,10 +106,7 @@ export function addProperties(
118
106
  oldProps: PropertySet | undefined,
119
107
  newProps: PropertySet,
120
108
  ): PropertySet {
121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- const _oldProps = oldProps ?? createMap<any>();
123
- extend(_oldProps, newProps);
124
- return { ..._oldProps };
109
+ return extend(oldProps ?? createMap<unknown>(), newProps);
125
110
  }
126
111
 
127
112
  /**
@@ -101,6 +101,12 @@ export interface ReferencePosition {
101
101
  * @param newProps - Properties to add to this reference.
102
102
  * @remarks Note that merge-tree does not broadcast changes to other clients. It is up to the consumer
103
103
  * to ensure broadcast happens if that is desired.
104
+ *
105
+ * @deprecated - This function should not be used externally and will be removed in a subsequent release.
106
+ *
107
+ * @privateRemarks This interface is used by both marker segments and local reference positions. We will remove
108
+ * this function from segments, but keep it on local reference positions for now. So it has been added to local reference
109
+ * positions, and must be removed here to not apply to marker segments.
104
110
  */
105
111
  addProperties(newProps: PropertySet): void;
106
112
  isLeaf(): this is ISegment;
@@ -8,8 +8,11 @@ import { DoublyLinkedList, walkList } from "./collections/index.js";
8
8
  import { ISegment, SegmentGroup } from "./mergeTreeNodes.js";
9
9
 
10
10
  /**
11
+ * @deprecated - This class should not be used externally and will be removed in a subsequent release.
11
12
  * @legacy
12
13
  * @alpha
14
+ *
15
+ * @privateRemarks After the deprecation period this class should be remove from this package's exports, and only be used internally
13
16
  */
14
17
  export class SegmentGroupCollection {
15
18
  // eslint-disable-next-line import/no-deprecated
@@ -9,11 +9,15 @@ import { assert } from "@fluidframework/core-utils/internal";
9
9
 
10
10
  import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js";
11
11
  import { IMergeTreeAnnotateMsg } from "./ops.js";
12
- import { MapLike, PropertySet, createMap } from "./properties.js";
12
+ import { MapLike, PropertySet, clone, createMap, extend } from "./properties.js";
13
13
 
14
14
  /**
15
15
  * @legacy
16
16
  * @alpha
17
+ *
18
+ * @deprecated - This enum should not be used externally and will be removed in a subsequent release.
19
+ *
20
+ * @privateRemarks This enum should be made internal after the deprecation period
17
21
  */
18
22
  export enum PropertiesRollback {
19
23
  /**
@@ -30,6 +34,10 @@ export enum PropertiesRollback {
30
34
  /**
31
35
  * @legacy
32
36
  * @alpha
37
+ *
38
+ * @deprecated - This class should not be used externally and will be removed in a subsequent release.
39
+ *
40
+ * @privateRemarks This class should be made internal after the deprecation period
33
41
  */
34
42
  export class PropertiesManager {
35
43
  private pendingKeyUpdateCount: MapLike<number> | undefined;
@@ -125,14 +133,10 @@ export class PropertiesManager {
125
133
  if (!newManager) {
126
134
  throw new Error("Must provide new PropertyManager");
127
135
  }
128
- for (const key of Object.keys(oldProps)) {
129
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
130
- newProps[key] = oldProps[key];
131
- }
132
- newManager.pendingKeyUpdateCount = createMap<number>();
133
- for (const key of Object.keys(this.pendingKeyUpdateCount!)) {
134
- // TODO Non null asserting, why is this not null?
135
- newManager.pendingKeyUpdateCount[key] = this.pendingKeyUpdateCount![key]!;
136
+ extend(newProps, oldProps);
137
+
138
+ if (this.pendingKeyUpdateCount) {
139
+ newManager.pendingKeyUpdateCount = clone(this.pendingKeyUpdateCount);
136
140
  }
137
141
  }
138
142
  return newProps;