@fluidframework/sequence 2.0.0-dev.4.4.0.162253 → 2.0.0-dev.5.2.0.169897

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 (71) hide show
  1. package/CHANGELOG.md +20 -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 +14 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/intervalCollection.d.ts +231 -59
  14. package/dist/intervalCollection.d.ts.map +1 -1
  15. package/dist/intervalCollection.js +288 -86
  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 +4 -4
  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 +3 -3
  29. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  30. package/dist/sharedIntervalCollection.js +1 -1
  31. package/dist/sharedIntervalCollection.js.map +1 -1
  32. package/dist/tsdoc-metadata.json +11 -0
  33. package/lib/defaultMap.d.ts +3 -2
  34. package/lib/defaultMap.d.ts.map +1 -1
  35. package/lib/defaultMap.js +4 -3
  36. package/lib/defaultMap.js.map +1 -1
  37. package/lib/defaultMapInterfaces.d.ts +12 -1
  38. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  39. package/lib/defaultMapInterfaces.js.map +1 -1
  40. package/lib/index.d.ts +3 -2
  41. package/lib/index.d.ts.map +1 -1
  42. package/lib/index.js +2 -1
  43. package/lib/index.js.map +1 -1
  44. package/lib/intervalCollection.d.ts +231 -59
  45. package/lib/intervalCollection.d.ts.map +1 -1
  46. package/lib/intervalCollection.js +286 -86
  47. package/lib/intervalCollection.js.map +1 -1
  48. package/lib/packageVersion.d.ts +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/lib/revertibles.d.ts +104 -0
  52. package/lib/revertibles.d.ts.map +1 -0
  53. package/lib/revertibles.js +364 -0
  54. package/lib/revertibles.js.map +1 -0
  55. package/lib/sequence.d.ts +4 -4
  56. package/lib/sequence.d.ts.map +1 -1
  57. package/lib/sequence.js +3 -3
  58. package/lib/sequence.js.map +1 -1
  59. package/lib/sharedIntervalCollection.d.ts +3 -3
  60. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  61. package/lib/sharedIntervalCollection.js +1 -1
  62. package/lib/sharedIntervalCollection.js.map +1 -1
  63. package/package.json +20 -22
  64. package/src/defaultMap.ts +4 -1
  65. package/src/defaultMapInterfaces.ts +13 -1
  66. package/src/index.ts +21 -5
  67. package/src/intervalCollection.ts +619 -73
  68. package/src/packageVersion.ts +1 -1
  69. package/src/revertibles.ts +572 -0
  70. package/src/sequence.ts +12 -2
  71. package/src/sharedIntervalCollection.ts +4 -2
@@ -16,11 +16,22 @@ var __rest = (this && this.__rest) || function (s, e) {
16
16
  /* eslint-disable no-bitwise */
17
17
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
18
18
  import { UsageError } from "@fluidframework/container-utils";
19
- import { addProperties, compareReferencePositions, createMap, MergeTreeDeltaType, minReferencePosition, PropertiesManager, RedBlackTree, ReferenceType, refTypeIncludesFlag, reservedRangeLabelsKey, UnassignedSequenceNumber, maxReferencePosition, createDetachedLocalReferencePosition, DetachedReferencePosition, } from "@fluidframework/merge-tree";
19
+ import { addProperties, compareReferencePositions, createMap, MergeTreeDeltaType, minReferencePosition, PropertiesManager, RedBlackTree, ReferenceType, refTypeIncludesFlag, reservedRangeLabelsKey, UnassignedSequenceNumber, maxReferencePosition, createDetachedLocalReferencePosition, DetachedReferencePosition, SlidingPreference, } from "@fluidframework/merge-tree";
20
20
  import { LoggingError } from "@fluidframework/telemetry-utils";
21
21
  import { v4 as uuid } from "uuid";
22
22
  import { IntervalTree } from "./intervalTree";
23
23
  const reservedIntervalIdKey = "intervalId";
24
+ /**
25
+ * Values are used in persisted formats (ops) and revertibles.
26
+ * @alpha
27
+ */
28
+ export const IntervalOpType = {
29
+ ADD: "add",
30
+ DELETE: "delete",
31
+ CHANGE: "change",
32
+ PROPERTY_CHANGED: "propertyChanged",
33
+ POSITION_REMOVE: "positionRemove",
34
+ };
24
35
  export var IntervalType;
25
36
  (function (IntervalType) {
26
37
  IntervalType[IntervalType["Simple"] = 0] = "Simple";
@@ -28,7 +39,7 @@ export var IntervalType;
28
39
  /**
29
40
  * SlideOnRemove indicates that the ends of the interval will slide if the segment
30
41
  * they reference is removed and acked.
31
- * See `packages\dds\merge-tree\REFERENCEPOSITIONS.md` for details
42
+ * See `packages\dds\merge-tree\docs\REFERENCEPOSITIONS.md` for details
32
43
  * SlideOnRemove is the default interval behavior and does not need to be specified.
33
44
  */
34
45
  IntervalType[IntervalType["SlideOnRemove"] = 2] = "SlideOnRemove";
@@ -49,6 +60,7 @@ function decompressInterval(interval, label) {
49
60
  sequenceNumber: interval[2],
50
61
  intervalType: interval[3],
51
62
  properties: Object.assign(Object.assign({}, interval[4]), { [reservedRangeLabelsKey]: [label] }),
63
+ stickiness: interval[5],
52
64
  };
53
65
  }
54
66
  /**
@@ -57,14 +69,57 @@ function decompressInterval(interval, label) {
57
69
  */
58
70
  function compressInterval(interval) {
59
71
  const { start, end, sequenceNumber, intervalType, properties } = interval;
60
- return [
72
+ const base = [
61
73
  start,
62
74
  end,
63
75
  sequenceNumber,
64
76
  intervalType,
65
77
  Object.assign(Object.assign({}, properties), { [reservedRangeLabelsKey]: undefined }),
66
78
  ];
79
+ if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
80
+ base.push(interval.stickiness);
81
+ }
82
+ return base;
83
+ }
84
+ function startReferenceSlidingPreference(stickiness) {
85
+ // if any start stickiness, prefer sliding backwards
86
+ return (stickiness & IntervalStickiness.START) !== 0
87
+ ? SlidingPreference.BACKWARD
88
+ : SlidingPreference.FORWARD;
89
+ }
90
+ function endReferenceSlidingPreference(stickiness) {
91
+ // if any end stickiness, prefer sliding forwards
92
+ return (stickiness & IntervalStickiness.END) !== 0
93
+ ? SlidingPreference.FORWARD
94
+ : SlidingPreference.BACKWARD;
67
95
  }
96
+ /**
97
+ * Determines how an interval should expand when segments are inserted adjacent
98
+ * to the range it spans
99
+ *
100
+ * Note that interval stickiness is currently an experimental feature and must
101
+ * be explicitly enabled with the `intervalStickinessEnabled` flag
102
+ */
103
+ export const IntervalStickiness = {
104
+ /**
105
+ * Interval does not expand to include adjacent segments
106
+ */
107
+ NONE: 0b00,
108
+ /**
109
+ * Interval expands to include segments inserted adjacent to the start
110
+ */
111
+ START: 0b01,
112
+ /**
113
+ * Interval expands to include segments inserted adjacent to the end
114
+ *
115
+ * This is the default stickiness
116
+ */
117
+ END: 0b10,
118
+ /**
119
+ * Interval expands to include all segments inserted adjacent to it
120
+ */
121
+ FULL: 0b11,
122
+ };
68
123
  /**
69
124
  * Serializable interval whose endpoints are plain-old numbers.
70
125
  */
@@ -98,7 +153,7 @@ export class Interval {
98
153
  * Adds an auxiliary set of properties to this interval.
99
154
  * These properties can be recovered using `getAdditionalPropertySets`
100
155
  * @param props - set of properties to add
101
- * @remarks - This gets called as part of the default conflict resolver for `IntervalCollection<Interval>`
156
+ * @remarks - This gets called as part of the default conflict resolver for `IIntervalCollection<Interval>`
102
157
  * (i.e. non-sequence-based interval collections). However, the additional properties don't get serialized.
103
158
  * This functionality seems half-baked.
104
159
  */
@@ -223,7 +278,7 @@ export class Interval {
223
278
  }
224
279
  }
225
280
  /**
226
- * Interval impelmentation whose ends are associated with positions in a mutatable sequence.
281
+ * Interval implementation whose ends are associated with positions in a mutatable sequence.
227
282
  * As such, when content is inserted into the middle of the interval, the interval expands to
228
283
  * include that content.
229
284
  *
@@ -254,11 +309,12 @@ export class SequenceInterval {
254
309
  * End endpoint of this interval.
255
310
  * @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
256
311
  */
257
- end, intervalType, props) {
312
+ end, intervalType, props, stickiness = IntervalStickiness.END) {
258
313
  this.client = client;
259
314
  this.start = start;
260
315
  this.end = end;
261
316
  this.intervalType = intervalType;
317
+ this.stickiness = stickiness;
262
318
  this.propertyManager = new PropertiesManager();
263
319
  this.properties = {};
264
320
  if (props) {
@@ -310,13 +366,16 @@ export class SequenceInterval {
310
366
  if (this.properties) {
311
367
  serializedInterval.properties = this.properties;
312
368
  }
369
+ if (this.stickiness !== IntervalStickiness.END) {
370
+ serializedInterval.stickiness = this.stickiness;
371
+ }
313
372
  return serializedInterval;
314
373
  }
315
374
  /**
316
375
  * {@inheritDoc IInterval.clone}
317
376
  */
318
377
  clone() {
319
- return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties);
378
+ return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties, this.stickiness);
320
379
  }
321
380
  /**
322
381
  * {@inheritDoc IInterval.compare}
@@ -400,7 +459,7 @@ export class SequenceInterval {
400
459
  * {@inheritDoc IInterval.modify}
401
460
  * @deprecated - This API was never intended to be public and will be marked internal in a future release.
402
461
  */
403
- modify(label, start, end, op, localSeq) {
462
+ modify(label, start, end, op, localSeq, stickiness = IntervalStickiness.END) {
404
463
  const getRefType = (baseType) => {
405
464
  let refType = baseType;
406
465
  if (op === undefined) {
@@ -411,14 +470,14 @@ export class SequenceInterval {
411
470
  };
412
471
  let startRef = this.start;
413
472
  if (start !== undefined) {
414
- startRef = createPositionReference(this.client, start, getRefType(this.start.refType), op, undefined, localSeq);
473
+ startRef = createPositionReference(this.client, start, getRefType(this.start.refType), op, undefined, localSeq, startReferenceSlidingPreference(stickiness));
415
474
  if (this.start.properties) {
416
475
  startRef.addProperties(this.start.properties);
417
476
  }
418
477
  }
419
478
  let endRef = this.end;
420
479
  if (end !== undefined) {
421
- endRef = createPositionReference(this.client, end, getRefType(this.end.refType), op, undefined, localSeq);
480
+ endRef = createPositionReference(this.client, end, getRefType(this.end.refType), op, undefined, localSeq, endReferenceSlidingPreference(stickiness));
422
481
  if (this.end.properties) {
423
482
  endRef.addProperties(this.end.properties);
424
483
  }
@@ -439,9 +498,9 @@ export class SequenceInterval {
439
498
  }
440
499
  }
441
500
  }
442
- function createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot) {
501
+ function createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot, slidingPreference) {
443
502
  if (segoff.segment) {
444
- const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
503
+ const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined, slidingPreference);
445
504
  return ref;
446
505
  }
447
506
  // Creating references on detached segments is allowed for:
@@ -457,7 +516,7 @@ function createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq
457
516
  }
458
517
  return createDetachedLocalReferencePosition(refType);
459
518
  }
460
- function createPositionReference(client, pos, refType, op, fromSnapshot, localSeq) {
519
+ function createPositionReference(client, pos, refType, op, fromSnapshot, localSeq, slidingPreference) {
461
520
  let segoff;
462
521
  if (op) {
463
522
  assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
@@ -471,9 +530,9 @@ function createPositionReference(client, pos, refType, op, fromSnapshot, localSe
471
530
  assert((refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot, 0x2f6 /* SlideOnRemove references must be op created */);
472
531
  segoff = client.getContainingSegment(pos, undefined, localSeq);
473
532
  }
474
- return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot);
533
+ return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq, fromSnapshot, slidingPreference);
475
534
  }
476
- export function createSequenceInterval(label, start, end, client, intervalType, op, fromSnapshot) {
535
+ export function createSequenceInterval(label, start, end, client, intervalType, op, fromSnapshot, stickiness = IntervalStickiness.END) {
477
536
  let beginRefType = ReferenceType.RangeBegin;
478
537
  let endRefType = ReferenceType.RangeEnd;
479
538
  if (intervalType === IntervalType.Transient) {
@@ -497,14 +556,14 @@ export function createSequenceInterval(label, start, end, client, intervalType,
497
556
  endRefType |= ReferenceType.StayOnRemove;
498
557
  }
499
558
  }
500
- const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot);
501
- const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot);
559
+ const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot, undefined, startReferenceSlidingPreference(stickiness));
560
+ const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot, undefined, endReferenceSlidingPreference(stickiness));
502
561
  const rangeProp = {
503
562
  [reservedRangeLabelsKey]: [label],
504
563
  };
505
564
  startLref.addProperties(rangeProp);
506
565
  endLref.addProperties(rangeProp);
507
- const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp);
566
+ const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp, stickiness);
508
567
  return ival;
509
568
  }
510
569
  export function createIntervalIndex() {
@@ -662,6 +721,117 @@ class EndpointIndex {
662
721
  this.endIntervalTree.remove(interval);
663
722
  }
664
723
  }
724
+ /**
725
+ * Interface for intervals that have comparison override properties.
726
+ */
727
+ const forceCompare = Symbol();
728
+ /**
729
+ * Compares two objects based on their comparison override properties.
730
+ * @returns A number indicating the order of the intervals (negative for a is lower than b, 0 for tie, positive for a is greater than b).
731
+ */
732
+ function compareOverrideables(a, b) {
733
+ var _a, _b;
734
+ const forceCompareA = (_a = a[forceCompare]) !== null && _a !== void 0 ? _a : 0;
735
+ const forceCompareB = (_b = b[forceCompare]) !== null && _b !== void 0 ? _b : 0;
736
+ return forceCompareA - forceCompareB;
737
+ }
738
+ class EndpointInRangeIndex {
739
+ constructor(helpers, client) {
740
+ this.helpers = helpers;
741
+ this.client = client;
742
+ this.intervalTree = new RedBlackTree((a, b) => {
743
+ const compareEndsResult = helpers.compareEnds(a, b);
744
+ if (compareEndsResult !== 0) {
745
+ return compareEndsResult;
746
+ }
747
+ const overrideablesComparison = compareOverrideables(a, b);
748
+ if (overrideablesComparison !== 0) {
749
+ return overrideablesComparison;
750
+ }
751
+ const aId = a.getIntervalId();
752
+ const bId = b.getIntervalId();
753
+ if (aId !== undefined && bId !== undefined) {
754
+ return aId.localeCompare(bId);
755
+ }
756
+ return 0;
757
+ });
758
+ }
759
+ add(interval) {
760
+ this.intervalTree.put(interval, interval);
761
+ }
762
+ remove(interval) {
763
+ this.intervalTree.remove(interval);
764
+ }
765
+ findIntervalsWithEndpointInRange(start, end) {
766
+ if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
767
+ return [];
768
+ }
769
+ const results = [];
770
+ const action = (node) => {
771
+ results.push(node.data);
772
+ return true;
773
+ };
774
+ const transientStartInterval = this.helpers.create("transient", start, start, this.client, IntervalType.Transient);
775
+ const transientEndInterval = this.helpers.create("transient", end, end, this.client, IntervalType.Transient);
776
+ // Add comparison overrides to the transient intervals
777
+ transientStartInterval[forceCompare] = -1;
778
+ transientEndInterval[forceCompare] = 1;
779
+ this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
780
+ return results;
781
+ }
782
+ }
783
+ class StartpointInRangeIndex {
784
+ constructor(helpers, client) {
785
+ this.helpers = helpers;
786
+ this.client = client;
787
+ this.intervalTree = new RedBlackTree((a, b) => {
788
+ assert(typeof helpers.compareStarts === "function", 0x6d1 /* compareStarts does not exist in the helpers */);
789
+ const compareStartsResult = helpers.compareStarts(a, b);
790
+ if (compareStartsResult !== 0) {
791
+ return compareStartsResult;
792
+ }
793
+ const overrideablesComparison = compareOverrideables(a, b);
794
+ if (overrideablesComparison !== 0) {
795
+ return overrideablesComparison;
796
+ }
797
+ const aId = a.getIntervalId();
798
+ const bId = b.getIntervalId();
799
+ if (aId !== undefined && bId !== undefined) {
800
+ return aId.localeCompare(bId);
801
+ }
802
+ return 0;
803
+ });
804
+ }
805
+ add(interval) {
806
+ this.intervalTree.put(interval, interval);
807
+ }
808
+ remove(interval) {
809
+ this.intervalTree.remove(interval);
810
+ }
811
+ findIntervalsWithStartpointInRange(start, end) {
812
+ if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
813
+ return [];
814
+ }
815
+ const results = [];
816
+ const action = (node) => {
817
+ results.push(node.data);
818
+ return true;
819
+ };
820
+ const transientStartInterval = this.helpers.create("transient", start, start, this.client, IntervalType.Transient);
821
+ const transientEndInterval = this.helpers.create("transient", end, end, this.client, IntervalType.Transient);
822
+ // Add comparison overrides to the transient intervals
823
+ transientStartInterval[forceCompare] = -1;
824
+ transientEndInterval[forceCompare] = 1;
825
+ this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
826
+ return results;
827
+ }
828
+ }
829
+ export function createEndpointInRangeIndex(helpers, client) {
830
+ return new EndpointInRangeIndex(helpers, client);
831
+ }
832
+ export function createStartpointInRangeIndex(helpers, client) {
833
+ return new StartpointInRangeIndex(helpers, client);
834
+ }
665
835
  export class LocalIntervalCollection {
666
836
  constructor(client, label, helpers,
667
837
  /** Callback invoked each time one of the endpoints of an interval slides. */
@@ -673,11 +843,11 @@ export class LocalIntervalCollection {
673
843
  this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
674
844
  this.idIntervalIndex = new IdIntervalIndex();
675
845
  this.endIntervalIndex = new EndpointIndex(client, helpers);
676
- this.indexes = [
846
+ this.indexes = new Set([
677
847
  this.overlappingIntervalsIndex,
678
848
  this.idIntervalIndex,
679
849
  this.endIntervalIndex,
680
- ];
850
+ ]);
681
851
  }
682
852
  createLegacyId(start, end) {
683
853
  // Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
@@ -717,17 +887,23 @@ export class LocalIntervalCollection {
717
887
  index.remove(interval);
718
888
  }
719
889
  }
890
+ appendIndex(index) {
891
+ this.indexes.add(index);
892
+ }
893
+ removeIndex(index) {
894
+ return this.indexes.delete(index);
895
+ }
720
896
  removeExistingInterval(interval) {
721
897
  this.removeIntervalFromIndexes(interval);
722
898
  this.removeIntervalListeners(interval);
723
899
  }
724
- createInterval(start, end, intervalType, op) {
725
- return this.helpers.create(this.label, start, end, this.client, intervalType, op);
900
+ createInterval(start, end, intervalType, op, stickiness = IntervalStickiness.END) {
901
+ return this.helpers.create(this.label, start, end, this.client, intervalType, op, undefined, stickiness);
726
902
  }
727
- addInterval(start, end, intervalType, props, op) {
903
+ addInterval(start, end, intervalType, props, op, stickiness = IntervalStickiness.END) {
728
904
  var _a;
729
905
  var _b;
730
- const interval = this.createInterval(start, end, intervalType, op);
906
+ const interval = this.createInterval(start, end, intervalType, op, stickiness);
731
907
  if (interval) {
732
908
  if (!interval.properties) {
733
909
  interval.properties = createMap();
@@ -780,7 +956,7 @@ export class LocalIntervalCollection {
780
956
  // either, so this must be special-cased.
781
957
  return ref;
782
958
  }
783
- return this.client.createLocalReferencePosition(segment, ref.getOffset(), ReferenceType.Transient, ref.properties);
959
+ return this.client.createLocalReferencePosition(segment, ref.getOffset(), ReferenceType.Transient, ref.properties, ref.slidingPreference);
784
960
  };
785
961
  if (interval instanceof SequenceInterval) {
786
962
  let previousInterval;
@@ -814,13 +990,20 @@ export class LocalIntervalCollection {
814
990
  }
815
991
  LocalIntervalCollection.legacyIdPrefix = "legacy";
816
992
  export const compareSequenceIntervalEnds = (a, b) => compareReferencePositions(a.end, b.end);
993
+ export const compareSequenceIntervalStarts = (a, b) => compareReferencePositions(a.start, b.start);
994
+ export const sequenceIntervalHelpers = {
995
+ compareEnds: compareSequenceIntervalEnds,
996
+ compareStarts: compareSequenceIntervalStarts,
997
+ create: createSequenceInterval,
998
+ };
999
+ export const intervalHelpers = {
1000
+ compareEnds: (a, b) => a.end - b.end,
1001
+ compareStarts: (a, b) => a.start - b.start,
1002
+ create: createInterval,
1003
+ };
817
1004
  class SequenceIntervalCollectionFactory {
818
- load(emitter, raw = []) {
819
- const helpers = {
820
- compareEnds: compareSequenceIntervalEnds,
821
- create: createSequenceInterval,
822
- };
823
- return new IntervalCollection(helpers, true, emitter, raw);
1005
+ load(emitter, raw = [], options) {
1006
+ return new IntervalCollection(sequenceIntervalHelpers, true, emitter, raw, options);
824
1007
  }
825
1008
  store(value) {
826
1009
  return value.serializeInternal();
@@ -841,7 +1024,7 @@ SequenceIntervalCollectionValueType.Name = "sharedStringIntervalCollection";
841
1024
  SequenceIntervalCollectionValueType._factory = new SequenceIntervalCollectionFactory();
842
1025
  SequenceIntervalCollectionValueType._ops = makeOpsMap();
843
1026
  const compareIntervalEnds = (a, b) => a.end - b.end;
844
- function createInterval(label, start, end, client) {
1027
+ function createInterval(label, start, end, client, intervalType, op, fromSnapshot) {
845
1028
  const rangeProp = {};
846
1029
  if (label && label.length > 0) {
847
1030
  rangeProp[reservedRangeLabelsKey] = [label];
@@ -849,12 +1032,12 @@ function createInterval(label, start, end, client) {
849
1032
  return new Interval(start, end, rangeProp);
850
1033
  }
851
1034
  class IntervalCollectionFactory {
852
- load(emitter, raw = []) {
1035
+ load(emitter, raw = [], options) {
853
1036
  const helpers = {
854
1037
  compareEnds: compareIntervalEnds,
855
1038
  create: createInterval,
856
1039
  };
857
- const collection = new IntervalCollection(helpers, false, emitter, raw);
1040
+ const collection = new IntervalCollection(helpers, false, emitter, raw, options);
858
1041
  collection.attachGraph(undefined, "");
859
1042
  return collection;
860
1043
  }
@@ -885,7 +1068,7 @@ export function makeOpsMap() {
885
1068
  };
886
1069
  return new Map([
887
1070
  [
888
- "add",
1071
+ IntervalOpType.ADD,
889
1072
  {
890
1073
  process: (collection, params, local, op, localOpMetadata) => {
891
1074
  // if params is undefined, the interval was deleted during
@@ -900,7 +1083,7 @@ export function makeOpsMap() {
900
1083
  },
901
1084
  ],
902
1085
  [
903
- "delete",
1086
+ IntervalOpType.DELETE,
904
1087
  {
905
1088
  process: (collection, params, local, op) => {
906
1089
  assert(op !== undefined, 0x3fc /* op should exist here */);
@@ -913,7 +1096,7 @@ export function makeOpsMap() {
913
1096
  },
914
1097
  ],
915
1098
  [
916
- "change",
1099
+ IntervalOpType.CHANGE,
917
1100
  {
918
1101
  process: (collection, params, local, op, localOpMetadata) => {
919
1102
  // if params is undefined, the interval was deleted during
@@ -929,7 +1112,7 @@ export function makeOpsMap() {
929
1112
  ],
930
1113
  ]);
931
1114
  }
932
- export class IntervalCollectionIterator {
1115
+ class IntervalCollectionIterator {
933
1116
  constructor(collection, iteratesForward = true, start, end) {
934
1117
  this.results = [];
935
1118
  this.index = 0;
@@ -949,19 +1132,16 @@ export class IntervalCollectionIterator {
949
1132
  }
950
1133
  }
951
1134
  /**
952
- * Collection of intervals that supports addition, modification, removal, and efficient spatial querying.
953
- * This class is not a DDS in its own right, but emits events on mutating operations such that it's possible to
954
- * integrate into a DDS.
955
- * This aligns with its usage in `SharedSegmentSequence`, which allows associating intervals to positions in the
956
- * sequence DDS which are broadcast to all other clients in an eventually consistent fashion.
1135
+ * {@inheritdoc IIntervalCollection}
957
1136
  */
958
1137
  export class IntervalCollection extends TypedEventEmitter {
959
1138
  /** @internal */
960
- constructor(helpers, requiresClient, emitter, serializedIntervals) {
1139
+ constructor(helpers, requiresClient, emitter, serializedIntervals, options = {}) {
961
1140
  super();
962
1141
  this.helpers = helpers;
963
1142
  this.requiresClient = requiresClient;
964
1143
  this.emitter = emitter;
1144
+ this.options = options;
965
1145
  this.localSeqToSerializedInterval = new Map();
966
1146
  this.localSeqToRebasedInterval = new Map();
967
1147
  this.pendingChangesStart = new Map();
@@ -973,6 +1153,36 @@ export class IntervalCollection extends TypedEventEmitter {
973
1153
  get attached() {
974
1154
  return !!this.localCollection;
975
1155
  }
1156
+ /**
1157
+ * {@inheritdoc IIntervalCollection.attachIndex}
1158
+ */
1159
+ attachIndex(index) {
1160
+ var _a;
1161
+ if (!this.attached) {
1162
+ throw new LoggingError("The local interval collection must exist");
1163
+ }
1164
+ for (const interval of this) {
1165
+ index.add(interval);
1166
+ }
1167
+ (_a = this.localCollection) === null || _a === void 0 ? void 0 : _a.appendIndex(index);
1168
+ }
1169
+ /**
1170
+ * {@inheritdoc IIntervalCollection.detachIndex}
1171
+ */
1172
+ detachIndex(index) {
1173
+ var _a;
1174
+ if (!this.attached) {
1175
+ throw new LoggingError("The local interval collection must exist");
1176
+ }
1177
+ // Avoid removing intervals if the index does not exist
1178
+ if (!((_a = this.localCollection) === null || _a === void 0 ? void 0 : _a.removeIndex(index))) {
1179
+ return false;
1180
+ }
1181
+ for (const interval of this) {
1182
+ index.remove(interval);
1183
+ }
1184
+ return true;
1185
+ }
976
1186
  rebasePositionWithSegmentSlide(pos, seqNumberFrom, localSeq) {
977
1187
  var _a;
978
1188
  if (!this.client) {
@@ -1028,8 +1238,8 @@ export class IntervalCollection extends TypedEventEmitter {
1028
1238
  if (this.savedSerializedIntervals) {
1029
1239
  for (const serializedInterval of this.savedSerializedIntervals) {
1030
1240
  this.localCollection.ensureSerializedId(serializedInterval);
1031
- const { start, end, intervalType, properties } = serializedInterval;
1032
- const interval = this.helpers.create(label, start, end, client, intervalType, undefined, true);
1241
+ const { start, end, intervalType, properties, stickiness } = serializedInterval;
1242
+ const interval = this.helpers.create(label, start, end, client, intervalType, undefined, true, stickiness);
1033
1243
  if (properties) {
1034
1244
  interval.addProperties(properties);
1035
1245
  }
@@ -1067,8 +1277,7 @@ export class IntervalCollection extends TypedEventEmitter {
1067
1277
  }
1068
1278
  }
1069
1279
  /**
1070
- * @returns the interval in this collection that has the provided `id`.
1071
- * If no interval in the collection has this `id`, returns `undefined`.
1280
+ * {@inheritdoc IIntervalCollection.getIntervalById}
1072
1281
  */
1073
1282
  getIntervalById(id) {
1074
1283
  if (!this.localCollection) {
@@ -1077,16 +1286,9 @@ export class IntervalCollection extends TypedEventEmitter {
1077
1286
  return this.localCollection.idIntervalIndex.getIntervalById(id);
1078
1287
  }
1079
1288
  /**
1080
- * Creates a new interval and add it to the collection.
1081
- * @param start - interval start position (inclusive)
1082
- * @param end - interval end position (exclusive)
1083
- * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
1084
- * @param props - properties of the interval
1085
- * @returns - the created interval
1086
- * @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
1087
- * with how the current half-open behavior is represented.
1289
+ * {@inheritdoc IIntervalCollection.add}
1088
1290
  */
1089
- add(start, end, intervalType, props) {
1291
+ add(start, end, intervalType, props, stickiness = IntervalStickiness.END) {
1090
1292
  var _a, _b;
1091
1293
  if (!this.localCollection) {
1092
1294
  throw new LoggingError("attach must be called prior to adding intervals");
@@ -1094,7 +1296,10 @@ export class IntervalCollection extends TypedEventEmitter {
1094
1296
  if (intervalType & IntervalType.Transient) {
1095
1297
  throw new LoggingError("Can not add transient intervals");
1096
1298
  }
1097
- const interval = this.localCollection.addInterval(start, end, intervalType, props);
1299
+ if (stickiness !== IntervalStickiness.END && !this.options.intervalStickinessEnabled) {
1300
+ throw new UsageError("attempted to set interval stickiness without enabling `intervalStickinessEnabled` feature flag");
1301
+ }
1302
+ const interval = this.localCollection.addInterval(start, end, intervalType, props, undefined, stickiness);
1098
1303
  if (interval) {
1099
1304
  const serializedInterval = {
1100
1305
  end,
@@ -1102,6 +1307,7 @@ export class IntervalCollection extends TypedEventEmitter {
1102
1307
  properties: interval.properties,
1103
1308
  sequenceNumber: (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.getCurrentSeq()) !== null && _b !== void 0 ? _b : 0,
1104
1309
  start,
1310
+ stickiness,
1105
1311
  };
1106
1312
  const localSeq = this.getNextLocalSeq();
1107
1313
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -1133,9 +1339,7 @@ export class IntervalCollection extends TypedEventEmitter {
1133
1339
  this.emit("deleteInterval", interval, local, op);
1134
1340
  }
1135
1341
  /**
1136
- * Removes an interval from the collection.
1137
- * @param id - Id of the interval to remove
1138
- * @returns the removed interval
1342
+ * {@inheritdoc IIntervalCollection.removeIntervalById}
1139
1343
  */
1140
1344
  removeIntervalById(id) {
1141
1345
  if (!this.localCollection) {
@@ -1148,10 +1352,7 @@ export class IntervalCollection extends TypedEventEmitter {
1148
1352
  return interval;
1149
1353
  }
1150
1354
  /**
1151
- * Changes the properties on an existing interval.
1152
- * @param id - Id of the interval whose properties should be changed
1153
- * @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
1154
- * and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
1355
+ * {@inheritdoc IIntervalCollection.changeProperties}
1155
1356
  */
1156
1357
  changeProperties(id, props) {
1157
1358
  if (!this.attached) {
@@ -1181,11 +1382,7 @@ export class IntervalCollection extends TypedEventEmitter {
1181
1382
  }
1182
1383
  }
1183
1384
  /**
1184
- * Changes the endpoints of an existing interval.
1185
- * @param id - Id of the interval to change
1186
- * @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
1187
- * @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
1188
- * @returns the interval that was changed, if it existed in the collection.
1385
+ * {@inheritdoc IIntervalCollection.change}
1189
1386
  */
1190
1387
  change(id, start, end) {
1191
1388
  if (!this.localCollection) {
@@ -1340,6 +1537,9 @@ export class IntervalCollection extends TypedEventEmitter {
1340
1537
  throw new LoggingError("attachSequence must be called");
1341
1538
  }
1342
1539
  }
1540
+ /**
1541
+ * {@inheritdoc IIntervalCollection.attachDeserializer}
1542
+ */
1343
1543
  attachDeserializer(onDeserialize) {
1344
1544
  // If no deserializer is specified can skip all processing work
1345
1545
  if (!onDeserialize) {
@@ -1460,7 +1660,7 @@ export class IntervalCollection extends TypedEventEmitter {
1460
1660
  }
1461
1661
  if (needsStartUpdate) {
1462
1662
  const props = interval.start.properties;
1463
- interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
1663
+ interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op, startReferenceSlidingPreference(interval.stickiness));
1464
1664
  if (props) {
1465
1665
  interval.start.addProperties(props);
1466
1666
  }
@@ -1472,7 +1672,7 @@ export class IntervalCollection extends TypedEventEmitter {
1472
1672
  }
1473
1673
  if (needsEndUpdate) {
1474
1674
  const props = interval.end.properties;
1475
- interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
1675
+ interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op, endReferenceSlidingPreference(interval.stickiness));
1476
1676
  if (props) {
1477
1677
  interval.end.addProperties(props);
1478
1678
  }
@@ -1503,7 +1703,7 @@ export class IntervalCollection extends TypedEventEmitter {
1503
1703
  throw new LoggingError("attachSequence must be called");
1504
1704
  }
1505
1705
  this.localCollection.ensureSerializedId(serializedInterval);
1506
- const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op);
1706
+ const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op, serializedInterval.stickiness);
1507
1707
  if (interval) {
1508
1708
  if (this.onDeserialize) {
1509
1709
  this.onDeserialize(interval);
@@ -1546,40 +1746,35 @@ export class IntervalCollection extends TypedEventEmitter {
1546
1746
  return iterator;
1547
1747
  }
1548
1748
  /**
1549
- * @returns a forward iterator over all intervals in this collection with start point equal to `startPosition`.
1749
+ * {@inheritdoc IIntervalCollection.CreateForwardIteratorWithStartPosition}
1550
1750
  */
1551
1751
  CreateForwardIteratorWithStartPosition(startPosition) {
1552
1752
  const iterator = new IntervalCollectionIterator(this, true, startPosition);
1553
1753
  return iterator;
1554
1754
  }
1555
1755
  /**
1556
- * @returns a backward iterator over all intervals in this collection with start point equal to `startPosition`.
1756
+ * {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithStartPosition}
1557
1757
  */
1558
1758
  CreateBackwardIteratorWithStartPosition(startPosition) {
1559
1759
  const iterator = new IntervalCollectionIterator(this, false, startPosition);
1560
1760
  return iterator;
1561
1761
  }
1562
1762
  /**
1563
- * @returns a forward iterator over all intervals in this collection with end point equal to `endPosition`.
1763
+ * {@inheritdoc IIntervalCollection.CreateForwardIteratorWithEndPosition}
1564
1764
  */
1565
1765
  CreateForwardIteratorWithEndPosition(endPosition) {
1566
1766
  const iterator = new IntervalCollectionIterator(this, true, undefined, endPosition);
1567
1767
  return iterator;
1568
1768
  }
1569
1769
  /**
1570
- * @returns a backward iterator over all intervals in this collection with end point equal to `endPosition`.
1770
+ * {@inheritdoc IIntervalCollection.CreateBackwardIteratorWithEndPosition}
1571
1771
  */
1572
1772
  CreateBackwardIteratorWithEndPosition(endPosition) {
1573
1773
  const iterator = new IntervalCollectionIterator(this, false, undefined, endPosition);
1574
1774
  return iterator;
1575
1775
  }
1576
1776
  /**
1577
- * Gathers iteration results that optionally match a start/end criteria into the provided array.
1578
- * @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
1579
- * intervals matching the query upon edit.
1580
- * @param iteratesForward - whether or not iteration should be in the forward direction
1581
- * @param start - If provided, only match intervals whose start point is equal to `start`.
1582
- * @param end - If provided, only match intervals whose end point is equal to `end`.
1777
+ * {@inheritdoc IIntervalCollection.gatherIterationResults}
1583
1778
  */
1584
1779
  gatherIterationResults(results, iteratesForward, start, end) {
1585
1780
  if (!this.localCollection) {
@@ -1588,8 +1783,7 @@ export class IntervalCollection extends TypedEventEmitter {
1588
1783
  this.localCollection.overlappingIntervalsIndex.gatherIterationResults(results, iteratesForward, start, end);
1589
1784
  }
1590
1785
  /**
1591
- * @returns an array of all intervals in this collection that overlap with the interval
1592
- * `[startPosition, endPosition]`.
1786
+ * {@inheritdoc IIntervalCollection.findOverlappingIntervals}
1593
1787
  */
1594
1788
  findOverlappingIntervals(startPosition, endPosition) {
1595
1789
  if (!this.localCollection) {
@@ -1598,7 +1792,7 @@ export class IntervalCollection extends TypedEventEmitter {
1598
1792
  return this.localCollection.overlappingIntervalsIndex.findOverlappingIntervals(startPosition, endPosition);
1599
1793
  }
1600
1794
  /**
1601
- * Applies a function to each interval in this collection.
1795
+ * {@inheritdoc IIntervalCollection.map}
1602
1796
  */
1603
1797
  map(fn) {
1604
1798
  if (!this.localCollection) {
@@ -1608,12 +1802,18 @@ export class IntervalCollection extends TypedEventEmitter {
1608
1802
  fn(interval);
1609
1803
  }
1610
1804
  }
1805
+ /**
1806
+ * {@inheritdoc IIntervalCollection.previousInterval}
1807
+ */
1611
1808
  previousInterval(pos) {
1612
1809
  if (!this.localCollection) {
1613
1810
  throw new LoggingError("attachSequence must be called");
1614
1811
  }
1615
1812
  return this.localCollection.endIntervalIndex.previousInterval(pos);
1616
1813
  }
1814
+ /**
1815
+ * {@inheritdoc IIntervalCollection.nextInterval}
1816
+ */
1617
1817
  nextInterval(pos) {
1618
1818
  if (!this.localCollection) {
1619
1819
  throw new LoggingError("attachSequence must be called");