@fluidframework/sequence 2.0.0-dev.4.1.0.148229 → 2.0.0-dev.4.3.0.157531

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.
@@ -12,7 +12,6 @@ import {
12
12
  addProperties,
13
13
  Client,
14
14
  compareReferencePositions,
15
- ConflictAction,
16
15
  createMap,
17
16
  ICombiningOp,
18
17
  ISegment,
@@ -78,9 +77,9 @@ export interface ISerializedInterval {
78
77
  * At the time of writing, it's not plumbed through to the reconnect/rebase code, however, which does need it.
79
78
  */
80
79
  sequenceNumber: number;
81
- /** Start position of the interval (inclusive) */
80
+ /** Start position of the interval */
82
81
  start: number;
83
- /** End position of the interval (inclusive) */
82
+ /** End position of the interval */
84
83
  end: number;
85
84
  /** Interval type to create */
86
85
  intervalType: IntervalType;
@@ -224,11 +223,9 @@ export class Interval implements ISerializableInterval {
224
223
  /**
225
224
  * {@inheritDoc ISerializableInterval.getIntervalId}
226
225
  */
227
- public getIntervalId(): string | undefined {
226
+ public getIntervalId(): string {
228
227
  const id = this.properties?.[reservedIntervalIdKey];
229
- if (id === undefined) {
230
- return undefined;
231
- }
228
+ assert(id !== undefined, 0x5e1 /* interval ID should not be undefined */);
232
229
  return `${id}`;
233
230
  }
234
231
 
@@ -400,6 +397,22 @@ export class Interval implements ISerializableInterval {
400
397
  * Interval impelmentation whose ends are associated with positions in a mutatable sequence.
401
398
  * As such, when content is inserted into the middle of the interval, the interval expands to
402
399
  * include that content.
400
+ *
401
+ * @remarks - The endpoint's position should be treated exclusively to get reasonable behavior--i.e.
402
+ * an interval referring to "hello" in "hello world" should have a start position of 0 and an end
403
+ * position of 5.
404
+ *
405
+ * To see why, consider what happens if "llo wor" is removed from the string to make "held".
406
+ * The interval's startpoint remains on the "h" (it isn't altered), but the interval's endpoint
407
+ * slides forward to the next unremoved position, which is the "l" in "held".
408
+ * Users would generally expect the interval to now refer to "he" (as it is the subset of content
409
+ * remaining after the removal), hence the "l" should be excluded.
410
+ * If the interval endpoint was treated inclusively, the interval would now refer to "hel", which
411
+ * is undesirable.
412
+ *
413
+ * Since the end of an interval is treated exclusively but cannot be greater than or equal to the
414
+ * length of the associated sequence, application models which leverage interval collections should
415
+ * consider inserting a marker at the end of the sequence to represent the end of the content.
403
416
  */
404
417
  export class SequenceInterval implements ISerializableInterval {
405
418
  /**
@@ -556,11 +569,9 @@ export class SequenceInterval implements ISerializableInterval {
556
569
  /**
557
570
  * {@inheritDoc ISerializableInterval.getIntervalId}
558
571
  */
559
- public getIntervalId(): string | undefined {
572
+ public getIntervalId(): string {
560
573
  const id = this.properties?.[reservedIntervalIdKey];
561
- if (id === undefined) {
562
- return undefined;
563
- }
574
+ assert(id !== undefined, 0x5e2 /* interval ID should not be undefined */);
564
575
  return `${id}`;
565
576
  }
566
577
 
@@ -593,7 +604,6 @@ export class SequenceInterval implements ISerializableInterval {
593
604
 
594
605
  /**
595
606
  * @returns whether this interval overlaps two numerical positions.
596
- * @remarks - this is currently strict overlap, which doesn't align with the endpoint treatment of`.overlaps()`
597
607
  */
598
608
  public overlapsPos(bstart: number, bend: number) {
599
609
  const startPos = this.client.localReferencePositionToPosition(this.start);
@@ -679,6 +689,7 @@ function createPositionReferenceFromSegoff(
679
689
  refType: ReferenceType,
680
690
  op?: ISequencedDocumentMessage,
681
691
  localSeq?: number,
692
+ fromSnapshot?: boolean,
682
693
  ): LocalReferencePosition {
683
694
  if (segoff.segment) {
684
695
  const ref = client.createLocalReferencePosition(
@@ -695,7 +706,12 @@ function createPositionReferenceFromSegoff(
695
706
  // - References coming from a remote client (location may have been concurrently removed)
696
707
  // - References being rebased to a new sequence number
697
708
  // (segment they originally referred to may have been removed with no suitable replacement)
698
- if (!op && !localSeq && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
709
+ if (
710
+ !op &&
711
+ !localSeq &&
712
+ !fromSnapshot &&
713
+ !refTypeIncludesFlag(refType, ReferenceType.Transient)
714
+ ) {
699
715
  throw new UsageError("Non-transient references need segment");
700
716
  }
701
717
 
@@ -728,7 +744,7 @@ function createPositionReference(
728
744
  );
729
745
  segoff = client.getContainingSegment(pos, undefined, localSeq);
730
746
  }
731
- return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq);
747
+ return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot);
732
748
  }
733
749
 
734
750
  export function createSequenceInterval(
@@ -774,97 +790,54 @@ export function createSequenceInterval(
774
790
  return ival;
775
791
  }
776
792
 
777
- export function defaultIntervalConflictResolver(a: Interval, b: Interval) {
778
- a.addPropertySet(b.properties);
779
- return a;
780
- }
781
-
782
- export function createIntervalIndex(conflict?: IntervalConflictResolver<Interval>) {
793
+ export function createIntervalIndex() {
783
794
  const helpers: IIntervalHelpers<Interval> = {
784
795
  compareEnds: compareIntervalEnds,
785
796
  create: createInterval,
786
797
  };
787
798
  const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
788
- if (conflict) {
789
- lc.addConflictResolver(conflict);
790
- } else {
791
- lc.addConflictResolver(defaultIntervalConflictResolver);
792
- }
793
799
  return lc;
794
800
  }
795
801
 
796
- export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
797
- private readonly intervalTree = new IntervalTree<TInterval>();
798
- private readonly endIntervalTree: RedBlackTree<TInterval, TInterval>;
799
- private readonly intervalIdMap: Map<string, TInterval> = new Map();
800
- private conflictResolver: IntervalConflictResolver<TInterval> | undefined;
801
- private endConflictResolver: ConflictAction<TInterval, TInterval> | undefined;
802
+ /**
803
+ * Collection of intervals.
804
+ *
805
+ * Implementers of this interface will typically implement additional APIs to support efficiently querying a collection
806
+ * of intervals in some manner, for example:
807
+ * - "find all intervals with start endpoint between these two points"
808
+ * - "find all intervals which overlap this range"
809
+ * etc.
810
+ */
811
+ export interface IntervalIndex<TInterval extends ISerializableInterval> {
812
+ /**
813
+ * Adds an interval to the index.
814
+ * @remarks - Application code should never need to invoke this method on their index for production scenarios:
815
+ * Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
816
+ */
817
+ add(interval: TInterval): void;
802
818
 
803
- private static readonly legacyIdPrefix = "legacy";
819
+ /**
820
+ * Removes an interval from the index.
821
+ * @remarks - Application code should never need to invoke this method on their index for production scenarios:
822
+ * Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
823
+ */
824
+ remove(interval: TInterval): void;
825
+ }
826
+
827
+ class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
828
+ implements IntervalIndex<TInterval>
829
+ {
830
+ private readonly intervalTree = new IntervalTree<TInterval>();
804
831
 
805
832
  constructor(
806
833
  private readonly client: Client,
807
- private readonly label: string,
808
834
  private readonly helpers: IIntervalHelpers<TInterval>,
809
- /** Callback invoked each time one of the endpoints of an interval slides. */
810
- private readonly onPositionChange?: (
811
- interval: TInterval,
812
- previousInterval: TInterval,
813
- ) => void,
814
- ) {
815
- // eslint-disable-next-line @typescript-eslint/unbound-method
816
- this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
817
- }
818
-
819
- public addConflictResolver(conflictResolver: IntervalConflictResolver<TInterval>) {
820
- this.conflictResolver = conflictResolver;
821
- this.endConflictResolver = (key: TInterval, currentKey: TInterval) => {
822
- const ival = conflictResolver(key, currentKey);
823
- return {
824
- data: ival,
825
- key: ival,
826
- };
827
- };
828
- }
835
+ ) {}
829
836
 
830
837
  public map(fn: (interval: TInterval) => void) {
831
838
  this.intervalTree.map(fn);
832
839
  }
833
840
 
834
- public createLegacyId(start: number, end: number): string {
835
- // Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
836
- // without ID's.
837
- return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
838
- }
839
-
840
- /**
841
- * Validates that a serialized interval has the ID property. Creates an ID
842
- * if one does not already exist
843
- *
844
- * @param serializedInterval - The interval to be checked
845
- * @returns The interval's existing or newly created id
846
- */
847
- public ensureSerializedId(serializedInterval: ISerializedInterval): string {
848
- let id: string | undefined = serializedInterval.properties?.[reservedIntervalIdKey];
849
- if (id === undefined) {
850
- // An interval came over the wire without an ID, so create a non-unique one based on start/end.
851
- // This will allow all clients to refer to this interval consistently.
852
- id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
853
- const newProps = {
854
- [reservedIntervalIdKey]: id,
855
- };
856
- serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
857
- }
858
- // Make the ID immutable for safety's sake.
859
- Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
860
- configurable: false,
861
- enumerable: true,
862
- writable: false,
863
- });
864
-
865
- return id;
866
- }
867
-
868
841
  public mapUntil(fn: (interval: TInterval) => boolean) {
869
842
  this.intervalTree.mapUntil(fn);
870
843
  }
@@ -953,7 +926,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
953
926
 
954
927
  /**
955
928
  * @returns an array of all intervals contained in this collection that overlap the range
956
- * `[startPosition, endPosition]`.
929
+ * `[startPosition, endPosition)`.
957
930
  */
958
931
  public findOverlappingIntervals(startPosition: number, endPosition: number) {
959
932
  if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
@@ -971,6 +944,61 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
971
944
  return overlappingIntervalNodes.map((node) => node.key);
972
945
  }
973
946
 
947
+ public remove(interval: TInterval) {
948
+ this.intervalTree.removeExisting(interval);
949
+ }
950
+
951
+ public add(interval: TInterval) {
952
+ this.intervalTree.put(interval);
953
+ }
954
+ }
955
+
956
+ class IdIntervalIndex<TInterval extends ISerializableInterval>
957
+ implements IntervalIndex<TInterval>, Iterable<TInterval>
958
+ {
959
+ private readonly intervalIdMap: Map<string, TInterval> = new Map();
960
+
961
+ public add(interval: TInterval) {
962
+ const id = interval.getIntervalId();
963
+ assert(
964
+ id !== undefined,
965
+ 0x2c0 /* "ID must be created before adding interval to collection" */,
966
+ );
967
+ // Make the ID immutable.
968
+ Object.defineProperty(interval.properties, reservedIntervalIdKey, {
969
+ configurable: false,
970
+ enumerable: true,
971
+ writable: false,
972
+ });
973
+ this.intervalIdMap.set(id, interval);
974
+ }
975
+
976
+ public remove(interval: TInterval) {
977
+ const id = interval.getIntervalId();
978
+ assert(id !== undefined, 0x311 /* expected id to exist on interval */);
979
+ this.intervalIdMap.delete(id);
980
+ }
981
+
982
+ public getIntervalById(id: string) {
983
+ return this.intervalIdMap.get(id);
984
+ }
985
+
986
+ public [Symbol.iterator]() {
987
+ return this.intervalIdMap.values();
988
+ }
989
+ }
990
+
991
+ class EndpointIndex<TInterval extends ISerializableInterval> implements IntervalIndex<TInterval> {
992
+ private readonly endIntervalTree: RedBlackTree<TInterval, TInterval>;
993
+
994
+ constructor(
995
+ private readonly client: Client,
996
+ private readonly helpers: IIntervalHelpers<TInterval>,
997
+ ) {
998
+ // eslint-disable-next-line @typescript-eslint/unbound-method
999
+ this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
1000
+ }
1001
+
974
1002
  public previousInterval(pos: number) {
975
1003
  const transientInterval = this.helpers.create(
976
1004
  "transient",
@@ -999,32 +1027,85 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
999
1027
  }
1000
1028
  }
1001
1029
 
1002
- public removeInterval(startPosition: number, endPosition: number) {
1003
- const transientInterval = this.helpers.create(
1004
- "transient",
1005
- startPosition,
1006
- endPosition,
1007
- this.client,
1008
- IntervalType.Transient,
1009
- );
1010
- this.intervalTree.remove(transientInterval);
1011
- this.endIntervalTree.remove(transientInterval);
1012
- return transientInterval;
1030
+ public add(interval: TInterval): void {
1031
+ this.endIntervalTree.put(interval, interval);
1013
1032
  }
1014
1033
 
1015
- private removeIntervalFromIndex(interval: TInterval) {
1016
- this.intervalTree.removeExisting(interval);
1034
+ public remove(interval: TInterval): void {
1017
1035
  this.endIntervalTree.remove(interval);
1036
+ }
1037
+ }
1018
1038
 
1019
- const id = interval.getIntervalId();
1039
+ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1040
+ private static readonly legacyIdPrefix = "legacy";
1041
+ public readonly overlappingIntervalsIndex: OverlappingIntervalsIndex<TInterval>;
1042
+ public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
1043
+ public readonly endIntervalIndex: EndpointIndex<TInterval>;
1044
+ private readonly indexes: IntervalIndex<TInterval>[];
1020
1045
 
1021
- assert(id !== undefined, 0x311 /* expected id to exist on interval */);
1046
+ constructor(
1047
+ private readonly client: Client,
1048
+ private readonly label: string,
1049
+ private readonly helpers: IIntervalHelpers<TInterval>,
1050
+ /** Callback invoked each time one of the endpoints of an interval slides. */
1051
+ private readonly onPositionChange?: (
1052
+ interval: TInterval,
1053
+ previousInterval: TInterval,
1054
+ ) => void,
1055
+ ) {
1056
+ this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
1057
+ this.idIntervalIndex = new IdIntervalIndex();
1058
+ this.endIntervalIndex = new EndpointIndex(client, helpers);
1059
+ this.indexes = [
1060
+ this.overlappingIntervalsIndex,
1061
+ this.idIntervalIndex,
1062
+ this.endIntervalIndex,
1063
+ ];
1064
+ }
1022
1065
 
1023
- this.intervalIdMap.delete(id);
1066
+ public createLegacyId(start: number, end: number): string {
1067
+ // Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
1068
+ // without ID's.
1069
+ return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
1070
+ }
1071
+
1072
+ /**
1073
+ * Validates that a serialized interval has the ID property. Creates an ID
1074
+ * if one does not already exist
1075
+ *
1076
+ * @param serializedInterval - The interval to be checked
1077
+ * @returns The interval's existing or newly created id
1078
+ */
1079
+ public ensureSerializedId(serializedInterval: ISerializedInterval): string {
1080
+ let id: string | undefined = serializedInterval.properties?.[reservedIntervalIdKey];
1081
+ if (id === undefined) {
1082
+ // Back-compat: 0.39 and earlier did not have IDs on intervals. If an interval from such a client
1083
+ // comes over the wire, create a non-unique one based on start/end.
1084
+ // This will allow all clients to refer to this interval consistently.
1085
+ id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
1086
+ const newProps = {
1087
+ [reservedIntervalIdKey]: id,
1088
+ };
1089
+ serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
1090
+ }
1091
+ // Make the ID immutable for safety's sake.
1092
+ Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
1093
+ configurable: false,
1094
+ enumerable: true,
1095
+ writable: false,
1096
+ });
1097
+
1098
+ return id;
1099
+ }
1100
+
1101
+ private removeIntervalFromIndexes(interval: TInterval) {
1102
+ for (const index of this.indexes) {
1103
+ index.remove(interval);
1104
+ }
1024
1105
  }
1025
1106
 
1026
1107
  public removeExistingInterval(interval: TInterval) {
1027
- this.removeIntervalFromIndex(interval);
1108
+ this.removeIntervalFromIndexes(interval);
1028
1109
  this.removeIntervalListeners(interval);
1029
1110
  }
1030
1111
 
@@ -1066,33 +1147,18 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1066
1147
  }
1067
1148
  }
1068
1149
 
1069
- private addIntervalToIndex(interval: TInterval) {
1070
- const id = interval.getIntervalId();
1071
- assert(
1072
- id !== undefined,
1073
- 0x2c0 /* "ID must be created before adding interval to collection" */,
1074
- );
1075
- // Make the ID immutable.
1076
- Object.defineProperty(interval.properties, reservedIntervalIdKey, {
1077
- configurable: false,
1078
- enumerable: true,
1079
- writable: false,
1080
- });
1081
- this.intervalTree.put(interval, this.conflictResolver);
1082
- this.endIntervalTree.put(interval, interval, this.endConflictResolver);
1083
- this.intervalIdMap.set(id, interval);
1150
+ private addIntervalToIndexes(interval: TInterval) {
1151
+ for (const index of this.indexes) {
1152
+ index.add(interval);
1153
+ }
1084
1154
  }
1085
1155
 
1086
1156
  public add(interval: TInterval): void {
1087
1157
  this.linkEndpointsToInterval(interval);
1088
- this.addIntervalToIndex(interval);
1158
+ this.addIntervalToIndexes(interval);
1089
1159
  this.addIntervalListeners(interval);
1090
1160
  }
1091
1161
 
1092
- public getIntervalById(id: string) {
1093
- return this.intervalIdMap.get(id);
1094
- }
1095
-
1096
1162
  public changeInterval(
1097
1163
  interval: TInterval,
1098
1164
  start: number | undefined,
@@ -1111,10 +1177,11 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1111
1177
  }
1112
1178
 
1113
1179
  public serialize(): ISerializedIntervalCollectionV2 {
1114
- const intervals = this.intervalTree.intervals.keys();
1115
1180
  return {
1116
1181
  label: this.label,
1117
- intervals: intervals.map((interval) => compressInterval(interval.serialize())),
1182
+ intervals: Array.from(this.idIntervalIndex, (interval) =>
1183
+ compressInterval(interval.serialize()),
1184
+ ),
1118
1185
  version: 2,
1119
1186
  };
1120
1187
  }
@@ -1147,7 +1214,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1147
1214
  previousInterval = interval.clone() as TInterval & SequenceInterval;
1148
1215
  previousInterval.start = cloneRef(previousInterval.start);
1149
1216
  previousInterval.end = cloneRef(previousInterval.end);
1150
- this.removeIntervalFromIndex(interval);
1217
+ this.removeIntervalFromIndexes(interval);
1151
1218
  }
1152
1219
  },
1153
1220
  () => {
@@ -1157,7 +1224,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1157
1224
  );
1158
1225
  pendingChanges--;
1159
1226
  if (pendingChanges === 0) {
1160
- this.addIntervalToIndex(interval);
1227
+ this.addIntervalToIndexes(interval);
1161
1228
  this.onPositionChange?.(interval, previousInterval);
1162
1229
  previousInterval = undefined;
1163
1230
  }
@@ -1629,16 +1696,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1629
1696
  if (!this.localCollection) {
1630
1697
  throw new LoggingError("attach must be called before accessing intervals");
1631
1698
  }
1632
- return this.localCollection.getIntervalById(id);
1699
+ return this.localCollection.idIntervalIndex.getIntervalById(id);
1633
1700
  }
1634
1701
 
1635
1702
  /**
1636
1703
  * Creates a new interval and add it to the collection.
1637
- * @param start - interval start position
1638
- * @param end - interval end position
1704
+ * @param start - interval start position (inclusive)
1705
+ * @param end - interval end position (exclusive)
1639
1706
  * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
1640
1707
  * @param props - properties of the interval
1641
1708
  * @returns - the created interval
1709
+ * @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
1710
+ * with how the current half-open behavior is represented.
1642
1711
  */
1643
1712
  public add(
1644
1713
  start: number,
@@ -1715,7 +1784,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1715
1784
  if (!this.localCollection) {
1716
1785
  throw new LoggingError("Attach must be called before accessing intervals");
1717
1786
  }
1718
- const interval = this.localCollection.getIntervalById(id);
1787
+ const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
1719
1788
  if (interval) {
1720
1789
  this.deleteExistingInterval(interval, true, undefined);
1721
1790
  }
@@ -1939,11 +2008,17 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1939
2008
  }
1940
2009
  }
1941
2010
 
1942
- public addConflictResolver(conflictResolver: IntervalConflictResolver<TInterval>): void {
2011
+ /**
2012
+ * @deprecated - This functionality was useful when adding two intervals at the same start/end positions resulted
2013
+ * in a conflict. This is no longer the case (as of PR#6407), as interval collections support multiple intervals
2014
+ * at the same location and gives each interval a unique id.
2015
+ *
2016
+ * As such, the conflict resolver is never invoked and unnecessary. This API will be removed in an upcoming release.
2017
+ */
2018
+ public addConflictResolver(_: IntervalConflictResolver<TInterval>): void {
1943
2019
  if (!this.localCollection) {
1944
2020
  throw new LoggingError("attachSequence must be called");
1945
2021
  }
1946
- this.localCollection.addConflictResolver(conflictResolver);
1947
2022
  }
1948
2023
 
1949
2024
  public attachDeserializer(onDeserialize: DeserializeCallback): void {
@@ -1956,9 +2031,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1956
2031
  this.onDeserialize = onDeserialize;
1957
2032
 
1958
2033
  // Trigger the async prepare work across all values in the collection
1959
- this.localCollection?.map((interval) => {
1960
- onDeserialize(interval);
1961
- });
2034
+ if (this.attached) {
2035
+ this.map(onDeserialize);
2036
+ }
1962
2037
  }
1963
2038
 
1964
2039
  /**
@@ -1987,7 +2062,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1987
2062
  this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
1988
2063
 
1989
2064
  const intervalId = properties?.[reservedIntervalIdKey];
1990
- const localInterval = this.localCollection?.getIntervalById(intervalId);
2065
+ const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(intervalId);
1991
2066
 
1992
2067
  const rebased: SerializedIntervalDelta = {
1993
2068
  start: startRebased,
@@ -2209,7 +2284,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2209
2284
  }
2210
2285
 
2211
2286
  const id = this.localCollection.ensureSerializedId(serializedInterval);
2212
- const interval = this.localCollection.getIntervalById(id);
2287
+ const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
2213
2288
  if (interval) {
2214
2289
  this.deleteExistingInterval(interval, local, op);
2215
2290
  }
@@ -2302,7 +2377,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2302
2377
  return;
2303
2378
  }
2304
2379
 
2305
- this.localCollection.gatherIterationResults(results, iteratesForward, start, end);
2380
+ this.localCollection.overlappingIntervalsIndex.gatherIterationResults(
2381
+ results,
2382
+ iteratesForward,
2383
+ start,
2384
+ end,
2385
+ );
2306
2386
  }
2307
2387
 
2308
2388
  /**
@@ -2314,7 +2394,10 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2314
2394
  throw new LoggingError("attachSequence must be called");
2315
2395
  }
2316
2396
 
2317
- return this.localCollection.findOverlappingIntervals(startPosition, endPosition);
2397
+ return this.localCollection.overlappingIntervalsIndex.findOverlappingIntervals(
2398
+ startPosition,
2399
+ endPosition,
2400
+ );
2318
2401
  }
2319
2402
 
2320
2403
  /**
@@ -2325,7 +2408,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2325
2408
  throw new LoggingError("attachSequence must be called");
2326
2409
  }
2327
2410
 
2328
- this.localCollection.map(fn);
2411
+ for (const interval of this.localCollection.idIntervalIndex) {
2412
+ fn(interval);
2413
+ }
2329
2414
  }
2330
2415
 
2331
2416
  public previousInterval(pos: number): TInterval | undefined {
@@ -2333,7 +2418,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2333
2418
  throw new LoggingError("attachSequence must be called");
2334
2419
  }
2335
2420
 
2336
- return this.localCollection.previousInterval(pos);
2421
+ return this.localCollection.endIntervalIndex.previousInterval(pos);
2337
2422
  }
2338
2423
 
2339
2424
  public nextInterval(pos: number): TInterval | undefined {
@@ -2341,7 +2426,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2341
2426
  throw new LoggingError("attachSequence must be called");
2342
2427
  }
2343
2428
 
2344
- return this.localCollection.nextInterval(pos);
2429
+ return this.localCollection.endIntervalIndex.nextInterval(pos);
2345
2430
  }
2346
2431
  }
2347
2432
 
@@ -61,7 +61,7 @@ export interface IInterval {
61
61
  ): IInterval | undefined;
62
62
  /**
63
63
  * @returns whether this interval overlaps with `b`.
64
- * Since intervals are inclusive, this includes cases where endpoints are equal.
64
+ * Intervals are considered to overlap if their intersection is non-empty.
65
65
  */
66
66
  overlaps(b: IInterval): boolean;
67
67
  /**
@@ -77,6 +77,13 @@ const intervalComparer = (a: IInterval, b: IInterval) => a.compare(b);
77
77
 
78
78
  export type IntervalNode<T extends IInterval> = RBNode<T, AugmentedIntervalNode>;
79
79
 
80
+ /**
81
+ * @deprecated - This functionality was useful when adding two intervals at the same start/end positions resulted
82
+ * in a conflict. This is no longer the case (as of PR#6407), as interval collections support multiple intervals
83
+ * at the same location and gives each interval a unique id.
84
+ *
85
+ * As such, conflict resolvers are never invoked and unnecessary. They will be removed in an upcoming release.
86
+ */
80
87
  export type IntervalConflictResolver<TInterval> = (a: TInterval, b: TInterval) => TInterval;
81
88
 
82
89
  export class IntervalTree<T extends IInterval>
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/sequence";
9
- export const pkgVersion = "2.0.0-dev.4.1.0.148229";
9
+ export const pkgVersion = "2.0.0-dev.4.3.0.157531";