@fluidframework/sequence 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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 (69) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/defaultMap.d.ts +3 -2
  3. package/dist/defaultMap.d.ts.map +1 -1
  4. package/dist/defaultMap.js +4 -3
  5. package/dist/defaultMap.js.map +1 -1
  6. package/dist/defaultMapInterfaces.d.ts +12 -1
  7. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  8. package/dist/defaultMapInterfaces.js.map +1 -1
  9. package/dist/index.d.ts +3 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +11 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/intervalCollection.d.ts +199 -46
  14. package/dist/intervalCollection.d.ts.map +1 -1
  15. package/dist/intervalCollection.js +164 -78
  16. package/dist/intervalCollection.js.map +1 -1
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.js +1 -1
  19. package/dist/packageVersion.js.map +1 -1
  20. package/dist/revertibles.d.ts +104 -0
  21. package/dist/revertibles.d.ts.map +1 -0
  22. package/dist/revertibles.js +374 -0
  23. package/dist/revertibles.js.map +1 -0
  24. package/dist/sequence.d.ts +2 -2
  25. package/dist/sequence.d.ts.map +1 -1
  26. package/dist/sequence.js +3 -3
  27. package/dist/sequence.js.map +1 -1
  28. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  29. package/dist/sharedIntervalCollection.js +1 -1
  30. package/dist/sharedIntervalCollection.js.map +1 -1
  31. package/lib/defaultMap.d.ts +3 -2
  32. package/lib/defaultMap.d.ts.map +1 -1
  33. package/lib/defaultMap.js +4 -3
  34. package/lib/defaultMap.js.map +1 -1
  35. package/lib/defaultMapInterfaces.d.ts +12 -1
  36. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  37. package/lib/defaultMapInterfaces.js.map +1 -1
  38. package/lib/index.d.ts +3 -2
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +2 -1
  41. package/lib/index.js.map +1 -1
  42. package/lib/intervalCollection.d.ts +199 -46
  43. package/lib/intervalCollection.d.ts.map +1 -1
  44. package/lib/intervalCollection.js +164 -78
  45. package/lib/intervalCollection.js.map +1 -1
  46. package/lib/packageVersion.d.ts +1 -1
  47. package/lib/packageVersion.js +1 -1
  48. package/lib/packageVersion.js.map +1 -1
  49. package/lib/revertibles.d.ts +104 -0
  50. package/lib/revertibles.d.ts.map +1 -0
  51. package/lib/revertibles.js +364 -0
  52. package/lib/revertibles.js.map +1 -0
  53. package/lib/sequence.d.ts +2 -2
  54. package/lib/sequence.d.ts.map +1 -1
  55. package/lib/sequence.js +3 -3
  56. package/lib/sequence.js.map +1 -1
  57. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  58. package/lib/sharedIntervalCollection.js +1 -1
  59. package/lib/sharedIntervalCollection.js.map +1 -1
  60. package/package.json +38 -14
  61. package/src/defaultMap.ts +4 -1
  62. package/src/defaultMapInterfaces.ts +13 -1
  63. package/src/index.ts +16 -1
  64. package/src/intervalCollection.ts +370 -57
  65. package/src/packageVersion.ts +1 -1
  66. package/src/revertibles.ts +572 -0
  67. package/src/sequence.ts +12 -3
  68. package/src/sharedIntervalCollection.ts +3 -2
  69. package/.vscode/launch.json +0 -16
@@ -28,6 +28,7 @@ import {
28
28
  maxReferencePosition,
29
29
  createDetachedLocalReferencePosition,
30
30
  DetachedReferencePosition,
31
+ SlidingPreference,
31
32
  } from "@fluidframework/merge-tree";
32
33
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
33
34
  import { LoggingError } from "@fluidframework/telemetry-utils";
@@ -39,11 +40,24 @@ import {
39
40
  IValueOperation,
40
41
  IValueType,
41
42
  IValueTypeOperationValue,
43
+ SequenceOptions,
42
44
  } from "./defaultMapInterfaces";
43
45
  import { IInterval, IntervalConflictResolver, IntervalTree, IntervalNode } from "./intervalTree";
44
46
 
45
47
  const reservedIntervalIdKey = "intervalId";
46
48
 
49
+ /**
50
+ * Values are used in persisted formats (ops) and revertibles.
51
+ * @alpha
52
+ */
53
+ export const IntervalOpType = {
54
+ ADD: "add",
55
+ DELETE: "delete",
56
+ CHANGE: "change",
57
+ PROPERTY_CHANGED: "propertyChanged",
58
+ POSITION_REMOVE: "positionRemove",
59
+ } as const;
60
+
47
61
  export enum IntervalType {
48
62
  Simple = 0x0,
49
63
  Nest = 0x1,
@@ -83,6 +97,7 @@ export interface ISerializedInterval {
83
97
  end: number;
84
98
  /** Interval type to create */
85
99
  intervalType: IntervalType;
100
+ stickiness?: IntervalStickiness;
86
101
  /** Any properties the interval has */
87
102
  properties?: PropertySet;
88
103
  }
@@ -101,12 +116,17 @@ export type SerializedIntervalDelta = Omit<ISerializedInterval, "start" | "end"
101
116
  *
102
117
  * Intervals are of the format:
103
118
  *
104
- * [start, end, sequenceNumber, intervalType, properties]
119
+ * [start, end, sequenceNumber, intervalType, properties, stickiness?]
120
+ *
121
+ * @deprecated - Public export was never intended and will be removed.
105
122
  */
106
- export type CompressedSerializedInterval = [number, number, number, IntervalType, PropertySet];
123
+ export type CompressedSerializedInterval =
124
+ | [number, number, number, IntervalType, PropertySet, IntervalStickiness]
125
+ | [number, number, number, IntervalType, PropertySet];
107
126
 
108
127
  /**
109
128
  * @internal
129
+ * @deprecated - Public export will be removed.
110
130
  */
111
131
  export interface ISerializedIntervalCollectionV2 {
112
132
  label: string;
@@ -128,6 +148,7 @@ function decompressInterval(
128
148
  sequenceNumber: interval[2],
129
149
  intervalType: interval[3],
130
150
  properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
151
+ stickiness: interval[5],
131
152
  };
132
153
  }
133
154
 
@@ -138,7 +159,7 @@ function decompressInterval(
138
159
  function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
139
160
  const { start, end, sequenceNumber, intervalType, properties } = interval;
140
161
 
141
- return [
162
+ const base: CompressedSerializedInterval = [
142
163
  start,
143
164
  end,
144
165
  sequenceNumber,
@@ -147,6 +168,26 @@ function compressInterval(interval: ISerializedInterval): CompressedSerializedIn
147
168
  // in the `label` field of the summary
148
169
  { ...properties, [reservedRangeLabelsKey]: undefined },
149
170
  ];
171
+
172
+ if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
173
+ base.push(interval.stickiness);
174
+ }
175
+
176
+ return base;
177
+ }
178
+
179
+ function startReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
180
+ // if any start stickiness, prefer sliding backwards
181
+ return (stickiness & IntervalStickiness.START) !== 0
182
+ ? SlidingPreference.BACKWARD
183
+ : SlidingPreference.FORWARD;
184
+ }
185
+
186
+ function endReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
187
+ // if any end stickiness, prefer sliding forwards
188
+ return (stickiness & IntervalStickiness.END) !== 0
189
+ ? SlidingPreference.FORWARD
190
+ : SlidingPreference.BACKWARD;
150
191
  }
151
192
 
152
193
  export interface ISerializableInterval extends IInterval {
@@ -193,9 +234,50 @@ export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
193
234
  intervalType: IntervalType,
194
235
  op?: ISequencedDocumentMessage,
195
236
  fromSnapshot?: boolean,
237
+ stickiness?: IntervalStickiness,
196
238
  ): TInterval;
197
239
  }
198
240
 
241
+ /**
242
+ * Determines how an interval should expand when segments are inserted adjacent
243
+ * to the range it spans
244
+ *
245
+ * Note that interval stickiness is currently an experimental feature and must
246
+ * be explicitly enabled with the `intervalStickinessEnabled` flag
247
+ */
248
+ export const IntervalStickiness = {
249
+ /**
250
+ * Interval does not expand to include adjacent segments
251
+ */
252
+ NONE: 0b00,
253
+
254
+ /**
255
+ * Interval expands to include segments inserted adjacent to the start
256
+ */
257
+ START: 0b01,
258
+
259
+ /**
260
+ * Interval expands to include segments inserted adjacent to the end
261
+ *
262
+ * This is the default stickiness
263
+ */
264
+ END: 0b10,
265
+
266
+ /**
267
+ * Interval expands to include all segments inserted adjacent to it
268
+ */
269
+ FULL: 0b11,
270
+ } as const;
271
+
272
+ /**
273
+ * Determines how an interval should expand when segments are inserted adjacent
274
+ * to the range it spans
275
+ *
276
+ * Note that interval stickiness is currently an experimental feature and must
277
+ * be explicitly enabled with the `intervalStickinessEnabled` flag
278
+ */
279
+ export type IntervalStickiness = typeof IntervalStickiness[keyof typeof IntervalStickiness];
280
+
199
281
  /**
200
282
  * Serializable interval whose endpoints are plain-old numbers.
201
283
  */
@@ -439,6 +521,7 @@ export class SequenceInterval implements ISerializableInterval {
439
521
  public end: LocalReferencePosition,
440
522
  public intervalType: IntervalType,
441
523
  props?: PropertySet,
524
+ public readonly stickiness: IntervalStickiness = IntervalStickiness.END,
442
525
  ) {
443
526
  this.propertyManager = new PropertiesManager();
444
527
  this.properties = {};
@@ -500,6 +583,9 @@ export class SequenceInterval implements ISerializableInterval {
500
583
  if (this.properties) {
501
584
  serializedInterval.properties = this.properties;
502
585
  }
586
+ if (this.stickiness !== IntervalStickiness.END) {
587
+ serializedInterval.stickiness = this.stickiness;
588
+ }
503
589
 
504
590
  return serializedInterval;
505
591
  }
@@ -514,6 +600,7 @@ export class SequenceInterval implements ISerializableInterval {
514
600
  this.end,
515
601
  this.intervalType,
516
602
  this.properties,
603
+ this.stickiness,
517
604
  );
518
605
  }
519
606
 
@@ -621,6 +708,7 @@ export class SequenceInterval implements ISerializableInterval {
621
708
  end: number,
622
709
  op?: ISequencedDocumentMessage,
623
710
  localSeq?: number,
711
+ stickiness: IntervalStickiness = IntervalStickiness.END,
624
712
  ) {
625
713
  const getRefType = (baseType: ReferenceType): ReferenceType => {
626
714
  let refType = baseType;
@@ -640,6 +728,7 @@ export class SequenceInterval implements ISerializableInterval {
640
728
  op,
641
729
  undefined,
642
730
  localSeq,
731
+ startReferenceSlidingPreference(stickiness),
643
732
  );
644
733
  if (this.start.properties) {
645
734
  startRef.addProperties(this.start.properties);
@@ -655,6 +744,7 @@ export class SequenceInterval implements ISerializableInterval {
655
744
  op,
656
745
  undefined,
657
746
  localSeq,
747
+ endReferenceSlidingPreference(stickiness),
658
748
  );
659
749
  if (this.end.properties) {
660
750
  endRef.addProperties(this.end.properties);
@@ -690,6 +780,7 @@ function createPositionReferenceFromSegoff(
690
780
  op?: ISequencedDocumentMessage,
691
781
  localSeq?: number,
692
782
  fromSnapshot?: boolean,
783
+ slidingPreference?: SlidingPreference,
693
784
  ): LocalReferencePosition {
694
785
  if (segoff.segment) {
695
786
  const ref = client.createLocalReferencePosition(
@@ -697,6 +788,7 @@ function createPositionReferenceFromSegoff(
697
788
  segoff.offset,
698
789
  refType,
699
790
  undefined,
791
+ slidingPreference,
700
792
  );
701
793
  return ref;
702
794
  }
@@ -725,6 +817,7 @@ function createPositionReference(
725
817
  op?: ISequencedDocumentMessage,
726
818
  fromSnapshot?: boolean,
727
819
  localSeq?: number,
820
+ slidingPreference?: SlidingPreference,
728
821
  ): LocalReferencePosition {
729
822
  let segoff;
730
823
  if (op) {
@@ -744,7 +837,16 @@ function createPositionReference(
744
837
  );
745
838
  segoff = client.getContainingSegment(pos, undefined, localSeq);
746
839
  }
747
- return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot);
840
+
841
+ return createPositionReferenceFromSegoff(
842
+ client,
843
+ segoff,
844
+ refType,
845
+ op,
846
+ localSeq,
847
+ fromSnapshot,
848
+ slidingPreference,
849
+ );
748
850
  }
749
851
 
750
852
  export function createSequenceInterval(
@@ -755,6 +857,7 @@ export function createSequenceInterval(
755
857
  intervalType: IntervalType,
756
858
  op?: ISequencedDocumentMessage,
757
859
  fromSnapshot?: boolean,
860
+ stickiness: IntervalStickiness = IntervalStickiness.END,
758
861
  ): SequenceInterval {
759
862
  let beginRefType = ReferenceType.RangeBegin;
760
863
  let endRefType = ReferenceType.RangeEnd;
@@ -778,15 +881,38 @@ export function createSequenceInterval(
778
881
  }
779
882
  }
780
883
 
781
- const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot);
782
- const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot);
884
+ const startLref = createPositionReference(
885
+ client,
886
+ start,
887
+ beginRefType,
888
+ op,
889
+ fromSnapshot,
890
+ undefined,
891
+ startReferenceSlidingPreference(stickiness),
892
+ );
893
+ const endLref = createPositionReference(
894
+ client,
895
+ end,
896
+ endRefType,
897
+ op,
898
+ fromSnapshot,
899
+ undefined,
900
+ endReferenceSlidingPreference(stickiness),
901
+ );
783
902
  const rangeProp = {
784
903
  [reservedRangeLabelsKey]: [label],
785
904
  };
786
905
  startLref.addProperties(rangeProp);
787
906
  endLref.addProperties(rangeProp);
788
907
 
789
- const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp);
908
+ const ival = new SequenceInterval(
909
+ client,
910
+ startLref,
911
+ endLref,
912
+ intervalType,
913
+ rangeProp,
914
+ stickiness,
915
+ );
790
916
  return ival;
791
917
  }
792
918
 
@@ -1041,7 +1167,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1041
1167
  public readonly overlappingIntervalsIndex: OverlappingIntervalsIndex<TInterval>;
1042
1168
  public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
1043
1169
  public readonly endIntervalIndex: EndpointIndex<TInterval>;
1044
- private readonly indexes: IntervalIndex<TInterval>[];
1170
+ private readonly indexes: Set<IntervalIndex<TInterval>>;
1045
1171
 
1046
1172
  constructor(
1047
1173
  private readonly client: Client,
@@ -1056,11 +1182,11 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1056
1182
  this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
1057
1183
  this.idIntervalIndex = new IdIntervalIndex();
1058
1184
  this.endIntervalIndex = new EndpointIndex(client, helpers);
1059
- this.indexes = [
1185
+ this.indexes = new Set([
1060
1186
  this.overlappingIntervalsIndex,
1061
1187
  this.idIntervalIndex,
1062
1188
  this.endIntervalIndex,
1063
- ];
1189
+ ]);
1064
1190
  }
1065
1191
 
1066
1192
  public createLegacyId(start: number, end: number): string {
@@ -1104,6 +1230,14 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1104
1230
  }
1105
1231
  }
1106
1232
 
1233
+ public appendIndex(index: IntervalIndex<TInterval>) {
1234
+ this.indexes.add(index);
1235
+ }
1236
+
1237
+ public removeIndex(index: IntervalIndex<TInterval>): boolean {
1238
+ return this.indexes.delete(index);
1239
+ }
1240
+
1107
1241
  public removeExistingInterval(interval: TInterval) {
1108
1242
  this.removeIntervalFromIndexes(interval);
1109
1243
  this.removeIntervalListeners(interval);
@@ -1114,8 +1248,18 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1114
1248
  end: number,
1115
1249
  intervalType: IntervalType,
1116
1250
  op?: ISequencedDocumentMessage,
1251
+ stickiness: IntervalStickiness = IntervalStickiness.END,
1117
1252
  ): TInterval {
1118
- return this.helpers.create(this.label, start, end, this.client, intervalType, op);
1253
+ return this.helpers.create(
1254
+ this.label,
1255
+ start,
1256
+ end,
1257
+ this.client,
1258
+ intervalType,
1259
+ op,
1260
+ undefined,
1261
+ stickiness,
1262
+ );
1119
1263
  }
1120
1264
 
1121
1265
  public addInterval(
@@ -1124,8 +1268,9 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1124
1268
  intervalType: IntervalType,
1125
1269
  props?: PropertySet,
1126
1270
  op?: ISequencedDocumentMessage,
1271
+ stickiness: IntervalStickiness = IntervalStickiness.END,
1127
1272
  ) {
1128
- const interval: TInterval = this.createInterval(start, end, intervalType, op);
1273
+ const interval: TInterval = this.createInterval(start, end, intervalType, op, stickiness);
1129
1274
  if (interval) {
1130
1275
  if (!interval.properties) {
1131
1276
  interval.properties = createMap<any>();
@@ -1201,6 +1346,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1201
1346
  ref.getOffset(),
1202
1347
  ReferenceType.Transient,
1203
1348
  ref.properties,
1349
+ ref.slidingPreference,
1204
1350
  );
1205
1351
  };
1206
1352
  if (interval instanceof SequenceInterval) {
@@ -1249,12 +1395,13 @@ class SequenceIntervalCollectionFactory
1249
1395
  public load(
1250
1396
  emitter: IValueOpEmitter,
1251
1397
  raw: ISerializedInterval[] | ISerializedIntervalCollectionV2 = [],
1398
+ options?: Partial<SequenceOptions>,
1252
1399
  ): IntervalCollection<SequenceInterval> {
1253
1400
  const helpers: IIntervalHelpers<SequenceInterval> = {
1254
1401
  compareEnds: compareSequenceIntervalEnds,
1255
1402
  create: createSequenceInterval,
1256
1403
  };
1257
- return new IntervalCollection<SequenceInterval>(helpers, true, emitter, raw);
1404
+ return new IntervalCollection<SequenceInterval>(helpers, true, emitter, raw, options);
1258
1405
  }
1259
1406
 
1260
1407
  public store(
@@ -1289,7 +1436,15 @@ export class SequenceIntervalCollectionValueType
1289
1436
 
1290
1437
  const compareIntervalEnds = (a: Interval, b: Interval) => a.end - b.end;
1291
1438
 
1292
- function createInterval(label: string, start: number, end: number, client: Client): Interval {
1439
+ function createInterval(
1440
+ label: string,
1441
+ start: number,
1442
+ end: number,
1443
+ client: Client,
1444
+ intervalType?: IntervalType,
1445
+ op?: ISequencedDocumentMessage,
1446
+ fromSnapshot?: boolean,
1447
+ ): Interval {
1293
1448
  const rangeProp: PropertySet = {};
1294
1449
 
1295
1450
  if (label && label.length > 0) {
@@ -1303,12 +1458,13 @@ class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Inte
1303
1458
  public load(
1304
1459
  emitter: IValueOpEmitter,
1305
1460
  raw: ISerializedInterval[] | ISerializedIntervalCollectionV2 = [],
1461
+ options?: Partial<SequenceOptions>,
1306
1462
  ): IntervalCollection<Interval> {
1307
1463
  const helpers: IIntervalHelpers<Interval> = {
1308
1464
  compareEnds: compareIntervalEnds,
1309
1465
  create: createInterval,
1310
1466
  };
1311
- const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw);
1467
+ const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
1312
1468
  collection.attachGraph(undefined as any as Client, "");
1313
1469
  return collection;
1314
1470
  }
@@ -1355,7 +1511,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
1355
1511
 
1356
1512
  return new Map<string, IValueOperation<IntervalCollection<T>>>([
1357
1513
  [
1358
- "add",
1514
+ IntervalOpType.ADD,
1359
1515
  {
1360
1516
  process: (collection, params, local, op, localOpMetadata) => {
1361
1517
  // if params is undefined, the interval was deleted during
@@ -1370,7 +1526,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
1370
1526
  },
1371
1527
  ],
1372
1528
  [
1373
- "delete",
1529
+ IntervalOpType.DELETE,
1374
1530
  {
1375
1531
  process: (collection, params, local, op) => {
1376
1532
  assert(op !== undefined, 0x3fc /* op should exist here */);
@@ -1383,7 +1539,7 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
1383
1539
  },
1384
1540
  ],
1385
1541
  [
1386
- "change",
1542
+ IntervalOpType.CHANGE,
1387
1543
  {
1388
1544
  process: (collection, params, local, op, localOpMetadata) => {
1389
1545
  // if params is undefined, the interval was deleted during
@@ -1402,6 +1558,10 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<
1402
1558
 
1403
1559
  export type DeserializeCallback = (properties: PropertySet) => void;
1404
1560
 
1561
+ /**
1562
+ * @deprecated - Public export will be removed. Use an appropriate iterator creation method on
1563
+ * {@link IIntervalCollection} to iterate intervals instead.
1564
+ */
1405
1565
  export class IntervalCollectionIterator<TInterval extends ISerializableInterval>
1406
1566
  implements Iterator<TInterval>
1407
1567
  {
@@ -1500,9 +1660,128 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
1500
1660
  * This aligns with its usage in `SharedSegmentSequence`, which allows associating intervals to positions in the
1501
1661
  * sequence DDS which are broadcast to all other clients in an eventually consistent fashion.
1502
1662
  */
1503
- export class IntervalCollection<TInterval extends ISerializableInterval> extends TypedEventEmitter<
1504
- IIntervalCollectionEvent<TInterval>
1505
- > {
1663
+ export interface IIntervalCollection<TInterval extends ISerializableInterval>
1664
+ extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>> {
1665
+ readonly attached: boolean;
1666
+ /**
1667
+ * Attaches an index to this collection.
1668
+ * All intervals which are part of this collection will be added to the index, and the index will automatically
1669
+ * be updated when this collection updates due to local or remote changes.
1670
+ *
1671
+ * @remarks - After attaching an index to an interval collection, applications should typically store this
1672
+ * index somewhere in their in-memory data model for future reference and querying.
1673
+ */
1674
+ attachIndex(index: IntervalIndex<TInterval>): void;
1675
+ /**
1676
+ * Detaches an index from this collection.
1677
+ * All intervals which are part of this collection will be removed from the index, and updates to this collection
1678
+ * due to local or remote changes will no longer incur updates to the index.
1679
+ *
1680
+ * @returns - Return false if the target index cannot be found in the indexes, otherwise remove all intervals in the index and return true
1681
+ */
1682
+ detachIndex(index: IntervalIndex<TInterval>): boolean;
1683
+ /**
1684
+ * @returns the interval in this collection that has the provided `id`.
1685
+ * If no interval in the collection has this `id`, returns `undefined`.
1686
+ */
1687
+ getIntervalById(id: string): TInterval | undefined;
1688
+ /**
1689
+ * Creates a new interval and add it to the collection.
1690
+ * @param start - interval start position (inclusive)
1691
+ * @param end - interval end position (exclusive)
1692
+ * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
1693
+ * @param props - properties of the interval
1694
+ * @returns - the created interval
1695
+ * @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
1696
+ * with how the current half-open behavior is represented.
1697
+ */
1698
+ add(start: number, end: number, intervalType: IntervalType, props?: PropertySet): TInterval;
1699
+ /**
1700
+ * Removes an interval from the collection.
1701
+ * @param id - Id of the interval to remove
1702
+ * @returns the removed interval
1703
+ */
1704
+ removeIntervalById(id: string): TInterval | undefined;
1705
+ /**
1706
+ * Changes the properties on an existing interval.
1707
+ * @param id - Id of the interval whose properties should be changed
1708
+ * @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
1709
+ * and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
1710
+ */
1711
+ changeProperties(id: string, props: PropertySet);
1712
+ /**
1713
+ * Changes the endpoints of an existing interval.
1714
+ * @param id - Id of the interval to change
1715
+ * @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
1716
+ * @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
1717
+ * @returns the interval that was changed, if it existed in the collection.
1718
+ */
1719
+ change(id: string, start?: number, end?: number): TInterval | undefined;
1720
+
1721
+ attachDeserializer(onDeserialize: DeserializeCallback): void;
1722
+ /**
1723
+ * @returns an iterator over all intervals in this collection.
1724
+ */
1725
+ [Symbol.iterator](): Iterator<TInterval>;
1726
+
1727
+ /**
1728
+ * @returns a forward iterator over all intervals in this collection with start point equal to `startPosition`.
1729
+ */
1730
+ CreateForwardIteratorWithStartPosition(startPosition: number): Iterator<TInterval>;
1731
+
1732
+ /**
1733
+ * @returns a backward iterator over all intervals in this collection with start point equal to `startPosition`.
1734
+ */
1735
+ CreateBackwardIteratorWithStartPosition(startPosition: number): Iterator<TInterval>;
1736
+
1737
+ /**
1738
+ * @returns a forward iterator over all intervals in this collection with end point equal to `endPosition`.
1739
+ */
1740
+ CreateForwardIteratorWithEndPosition(endPosition: number): Iterator<TInterval>;
1741
+
1742
+ /**
1743
+ * @returns a backward iterator over all intervals in this collection with end point equal to `endPosition`.
1744
+ */
1745
+ CreateBackwardIteratorWithEndPosition(endPosition: number): Iterator<TInterval>;
1746
+
1747
+ /**
1748
+ * Gathers iteration results that optionally match a start/end criteria into the provided array.
1749
+ * @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
1750
+ * intervals matching the query upon edit.
1751
+ * @param iteratesForward - whether or not iteration should be in the forward direction
1752
+ * @param start - If provided, only match intervals whose start point is equal to `start`.
1753
+ * @param end - If provided, only match intervals whose end point is equal to `end`.
1754
+ */
1755
+ gatherIterationResults(
1756
+ results: TInterval[],
1757
+ iteratesForward: boolean,
1758
+ start?: number,
1759
+ end?: number,
1760
+ ): void;
1761
+
1762
+ /**
1763
+ * @returns an array of all intervals in this collection that overlap with the interval
1764
+ * `[startPosition, endPosition]`.
1765
+ */
1766
+ findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[];
1767
+
1768
+ /**
1769
+ * Applies a function to each interval in this collection.
1770
+ */
1771
+ map(fn: (interval: TInterval) => void): void;
1772
+
1773
+ previousInterval(pos: number): TInterval | undefined;
1774
+
1775
+ nextInterval(pos: number): TInterval | undefined;
1776
+ }
1777
+
1778
+ /**
1779
+ * @deprecated - Use {@link IIntervalCollection} instead.
1780
+ */
1781
+ export class IntervalCollection<TInterval extends ISerializableInterval>
1782
+ extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>>
1783
+ implements IIntervalCollection<TInterval>
1784
+ {
1506
1785
  private savedSerializedIntervals?: ISerializedInterval[];
1507
1786
  private localCollection: LocalIntervalCollection<TInterval> | undefined;
1508
1787
  private onDeserialize: DeserializeCallback | undefined;
@@ -1534,6 +1813,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1534
1813
  private readonly requiresClient: boolean,
1535
1814
  private readonly emitter: IValueOpEmitter,
1536
1815
  serializedIntervals: ISerializedInterval[] | ISerializedIntervalCollectionV2,
1816
+ private readonly options: Partial<SequenceOptions> = {},
1537
1817
  ) {
1538
1818
  super();
1539
1819
 
@@ -1544,6 +1824,40 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1544
1824
  );
1545
1825
  }
1546
1826
 
1827
+ /**
1828
+ * {@inheritdoc IIntervalCollection.attachIndex}
1829
+ */
1830
+ public attachIndex(index: IntervalIndex<TInterval>): void {
1831
+ if (!this.attached) {
1832
+ throw new LoggingError("The local interval collection must exist");
1833
+ }
1834
+ for (const interval of this) {
1835
+ index.add(interval);
1836
+ }
1837
+
1838
+ this.localCollection?.appendIndex(index);
1839
+ }
1840
+
1841
+ /**
1842
+ * {@inheritdoc IIntervalCollection.detachIndex}
1843
+ */
1844
+ public detachIndex(index: IntervalIndex<TInterval>): boolean {
1845
+ if (!this.attached) {
1846
+ throw new LoggingError("The local interval collection must exist");
1847
+ }
1848
+
1849
+ // Avoid removing intervals if the index does not exist
1850
+ if (!this.localCollection?.removeIndex(index)) {
1851
+ return false;
1852
+ }
1853
+
1854
+ for (const interval of this) {
1855
+ index.remove(interval);
1856
+ }
1857
+
1858
+ return true;
1859
+ }
1860
+
1547
1861
  private rebasePositionWithSegmentSlide(
1548
1862
  pos: number,
1549
1863
  seqNumberFrom: number,
@@ -1634,7 +1948,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1634
1948
  if (this.savedSerializedIntervals) {
1635
1949
  for (const serializedInterval of this.savedSerializedIntervals) {
1636
1950
  this.localCollection.ensureSerializedId(serializedInterval);
1637
- const { start, end, intervalType, properties } = serializedInterval;
1951
+ const { start, end, intervalType, properties, stickiness } = serializedInterval;
1638
1952
  const interval = this.helpers.create(
1639
1953
  label,
1640
1954
  start,
@@ -1643,6 +1957,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1643
1957
  intervalType,
1644
1958
  undefined,
1645
1959
  true,
1960
+ stickiness,
1646
1961
  );
1647
1962
  if (properties) {
1648
1963
  interval.addProperties(properties);
@@ -1689,8 +2004,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1689
2004
  }
1690
2005
 
1691
2006
  /**
1692
- * @returns the interval in this collection that has the provided `id`.
1693
- * If no interval in the collection has this `id`, returns `undefined`.
2007
+ * {@inheritdoc IIntervalCollection.getIntervalById}
1694
2008
  */
1695
2009
  public getIntervalById(id: string) {
1696
2010
  if (!this.localCollection) {
@@ -1700,20 +2014,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1700
2014
  }
1701
2015
 
1702
2016
  /**
1703
- * Creates a new interval and add it to the collection.
1704
- * @param start - interval start position (inclusive)
1705
- * @param end - interval end position (exclusive)
1706
- * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
1707
- * @param props - properties of the interval
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.
2017
+ * {@inheritdoc IIntervalCollection.add}
1711
2018
  */
1712
2019
  public add(
1713
2020
  start: number,
1714
2021
  end: number,
1715
2022
  intervalType: IntervalType,
1716
2023
  props?: PropertySet,
2024
+ stickiness: IntervalStickiness = IntervalStickiness.END,
1717
2025
  ): TInterval {
1718
2026
  if (!this.localCollection) {
1719
2027
  throw new LoggingError("attach must be called prior to adding intervals");
@@ -1721,12 +2029,19 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1721
2029
  if (intervalType & IntervalType.Transient) {
1722
2030
  throw new LoggingError("Can not add transient intervals");
1723
2031
  }
2032
+ if (stickiness !== IntervalStickiness.END && !this.options.intervalStickinessEnabled) {
2033
+ throw new UsageError(
2034
+ "attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag",
2035
+ );
2036
+ }
1724
2037
 
1725
2038
  const interval: TInterval = this.localCollection.addInterval(
1726
2039
  start,
1727
2040
  end,
1728
2041
  intervalType,
1729
2042
  props,
2043
+ undefined,
2044
+ stickiness,
1730
2045
  );
1731
2046
 
1732
2047
  if (interval) {
@@ -1736,6 +2051,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1736
2051
  properties: interval.properties,
1737
2052
  sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1738
2053
  start,
2054
+ stickiness,
1739
2055
  };
1740
2056
  const localSeq = this.getNextLocalSeq();
1741
2057
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -1776,9 +2092,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1776
2092
  }
1777
2093
 
1778
2094
  /**
1779
- * Removes an interval from the collection.
1780
- * @param id - Id of the interval to remove
1781
- * @returns the removed interval
2095
+ * {@inheritdoc IIntervalCollection.removeIntervalById}
1782
2096
  */
1783
2097
  public removeIntervalById(id: string) {
1784
2098
  if (!this.localCollection) {
@@ -1792,10 +2106,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1792
2106
  }
1793
2107
 
1794
2108
  /**
1795
- * Changes the properties on an existing interval.
1796
- * @param id - Id of the interval whose properties should be changed
1797
- * @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
1798
- * and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
2109
+ * {@inheritdoc IIntervalCollection.changeProperties}
1799
2110
  */
1800
2111
  public changeProperties(id: string, props: PropertySet) {
1801
2112
  if (!this.attached) {
@@ -1829,11 +2140,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
1829
2140
  }
1830
2141
 
1831
2142
  /**
1832
- * Changes the endpoints of an existing interval.
1833
- * @param id - Id of the interval to change
1834
- * @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
1835
- * @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
1836
- * @returns the interval that was changed, if it existed in the collection.
2143
+ * {@inheritdoc IIntervalCollection.change}
1837
2144
  */
1838
2145
  public change(id: string, start?: number, end?: number): TInterval | undefined {
1839
2146
  if (!this.localCollection) {
@@ -2021,6 +2328,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2021
2328
  }
2022
2329
  }
2023
2330
 
2331
+ /**
2332
+ * {@inheritdoc IIntervalCollection.attachDeserializer}
2333
+ */
2024
2334
  public attachDeserializer(onDeserialize: DeserializeCallback): void {
2025
2335
  // If no deserializer is specified can skip all processing work
2026
2336
  if (!onDeserialize) {
@@ -2188,6 +2498,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2188
2498
  newStart,
2189
2499
  interval.start.refType,
2190
2500
  op,
2501
+ startReferenceSlidingPreference(interval.stickiness),
2191
2502
  );
2192
2503
  if (props) {
2193
2504
  interval.start.addProperties(props);
@@ -2205,6 +2516,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2205
2516
  newEnd,
2206
2517
  interval.end.refType,
2207
2518
  op,
2519
+ endReferenceSlidingPreference(interval.stickiness),
2208
2520
  );
2209
2521
  if (props) {
2210
2522
  interval.end.addProperties(props);
@@ -2253,6 +2565,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2253
2565
  serializedInterval.intervalType,
2254
2566
  serializedInterval.properties,
2255
2567
  op,
2568
+ serializedInterval.stickiness,
2256
2569
  );
2257
2570
 
2258
2571
  if (interval) {
@@ -2310,7 +2623,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2310
2623
  }
2311
2624
 
2312
2625
  /**
2313
- * @returns a forward iterator over all intervals in this collection with start point equal to `startPosition`.
2626
+ * {@inheritdoc IIntervalCollection.CreateForwardIteratorWithStartPosition}
2314
2627
  */
2315
2628
  public CreateForwardIteratorWithStartPosition(
2316
2629
  startPosition: number,
@@ -2320,7 +2633,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2320
2633
  }
2321
2634
 
2322
2635
  /**
2323
- * @returns a backward iterator over all intervals in this collection with start point equal to `startPosition`.
2636
+ * {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithStartPosition}
2324
2637
  */
2325
2638
  public CreateBackwardIteratorWithStartPosition(
2326
2639
  startPosition: number,
@@ -2330,7 +2643,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2330
2643
  }
2331
2644
 
2332
2645
  /**
2333
- * @returns a forward iterator over all intervals in this collection with end point equal to `endPosition`.
2646
+ * {@inheritdoc IIntervalCollection.CreateForwardIteratorWithEndPosition}
2334
2647
  */
2335
2648
  public CreateForwardIteratorWithEndPosition(
2336
2649
  endPosition: number,
@@ -2345,7 +2658,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2345
2658
  }
2346
2659
 
2347
2660
  /**
2348
- * @returns a backward iterator over all intervals in this collection with end point equal to `endPosition`.
2661
+ * {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithEndPosition}
2349
2662
  */
2350
2663
  public CreateBackwardIteratorWithEndPosition(
2351
2664
  endPosition: number,
@@ -2360,12 +2673,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2360
2673
  }
2361
2674
 
2362
2675
  /**
2363
- * Gathers iteration results that optionally match a start/end criteria into the provided array.
2364
- * @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
2365
- * intervals matching the query upon edit.
2366
- * @param iteratesForward - whether or not iteration should be in the forward direction
2367
- * @param start - If provided, only match intervals whose start point is equal to `start`.
2368
- * @param end - If provided, only match intervals whose end point is equal to `end`.
2676
+ * {@inheritdoc IIntervalCollection.gatherIterationResults}
2369
2677
  */
2370
2678
  public gatherIterationResults(
2371
2679
  results: TInterval[],
@@ -2386,8 +2694,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2386
2694
  }
2387
2695
 
2388
2696
  /**
2389
- * @returns an array of all intervals in this collection that overlap with the interval
2390
- * `[startPosition, endPosition]`.
2697
+ * {@inheritdoc IIntervalCollection.findOverlappingIntervals}
2391
2698
  */
2392
2699
  public findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[] {
2393
2700
  if (!this.localCollection) {
@@ -2401,7 +2708,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2401
2708
  }
2402
2709
 
2403
2710
  /**
2404
- * Applies a function to each interval in this collection.
2711
+ * {@inheritdoc IIntervalCollection.map}
2405
2712
  */
2406
2713
  public map(fn: (interval: TInterval) => void) {
2407
2714
  if (!this.localCollection) {
@@ -2413,6 +2720,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2413
2720
  }
2414
2721
  }
2415
2722
 
2723
+ /**
2724
+ * {@inheritdoc IIntervalCollection.previousInterval}
2725
+ */
2416
2726
  public previousInterval(pos: number): TInterval | undefined {
2417
2727
  if (!this.localCollection) {
2418
2728
  throw new LoggingError("attachSequence must be called");
@@ -2421,6 +2731,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval> extends
2421
2731
  return this.localCollection.endIntervalIndex.previousInterval(pos);
2422
2732
  }
2423
2733
 
2734
+ /**
2735
+ * {@inheritdoc IIntervalCollection.nextInterval}
2736
+ */
2424
2737
  public nextInterval(pos: number): TInterval | undefined {
2425
2738
  if (!this.localCollection) {
2426
2739
  throw new LoggingError("attachSequence must be called");