@fluidframework/sequence 0.59.4002 → 1.0.2

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.
@@ -15,14 +15,25 @@ var __rest = (this && this.__rest) || function (s, e) {
15
15
  };
16
16
  /* eslint-disable no-bitwise */
17
17
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
18
- import { addProperties, createMap, IntervalTree, LocalReference, PropertiesManager, RedBlackTree, ReferenceType, reservedRangeLabelsKey, UnassignedSequenceNumber, } from "@fluidframework/merge-tree";
18
+ import { UsageError } from "@fluidframework/container-utils";
19
+ import { addProperties, createMap, IntervalTree, LocalReference, MergeTreeDeltaType, PropertiesManager, RedBlackTree, ReferenceType, refTypeIncludesFlag, reservedRangeLabelsKey, UnassignedSequenceNumber, } from "@fluidframework/merge-tree";
19
20
  import { v4 as uuid } from "uuid";
20
21
  const reservedIntervalIdKey = "intervalId";
21
22
  export var IntervalType;
22
23
  (function (IntervalType) {
23
24
  IntervalType[IntervalType["Simple"] = 0] = "Simple";
24
25
  IntervalType[IntervalType["Nest"] = 1] = "Nest";
26
+ /**
27
+ * SlideOnRemove indicates that the ends of the interval will slide if the segment
28
+ * they reference is removed and acked.
29
+ * See `packages\dds\merge-tree\REFERENCEPOSITIONS.md` for details
30
+ * SlideOnRemove is the default interval behavior and does not need to be specified.
31
+ */
25
32
  IntervalType[IntervalType["SlideOnRemove"] = 2] = "SlideOnRemove";
33
+ /**
34
+ * @internal
35
+ * A temporary interval, used internally
36
+ */
26
37
  IntervalType[IntervalType["Transient"] = 4] = "Transient";
27
38
  })(IntervalType || (IntervalType = {}));
28
39
  export class Interval {
@@ -229,46 +240,66 @@ export class SequenceInterval {
229
240
  return newInterval;
230
241
  }
231
242
  }
232
- function createPositionReference(client, pos, refType, op) {
233
- const segoff = client.getContainingSegment(pos, op);
234
- if (segoff === null || segoff === void 0 ? void 0 : segoff.segment) {
235
- const lref = new LocalReference(client, segoff.segment, segoff.offset, refType);
236
- if (refType !== ReferenceType.Transient) {
237
- client.addLocalReference(lref);
243
+ function createPositionReferenceFromSegoff(client, segoff, refType, op) {
244
+ if (segoff.segment) {
245
+ const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
246
+ return ref;
247
+ }
248
+ else {
249
+ if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
250
+ throw new UsageError("Non-transient references need segment");
238
251
  }
239
- return lref;
252
+ return new LocalReference(client, undefined, 0, refType);
253
+ }
254
+ }
255
+ function createPositionReference(client, pos, refType, op, fromSnapshot) {
256
+ let segoff;
257
+ if (op) {
258
+ assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
259
+ segoff = client.getContainingSegment(pos, op);
260
+ segoff = client.getSlideToSegment(segoff);
240
261
  }
241
- return new LocalReference(client, undefined);
262
+ else {
263
+ assert((refType & ReferenceType.SlideOnRemove) === 0 || fromSnapshot, 0x2f6 /* SlideOnRemove references must be op created */);
264
+ segoff = client.getContainingSegment(pos);
265
+ }
266
+ return createPositionReferenceFromSegoff(client, segoff, refType, op);
242
267
  }
243
- function createSequenceInterval(label, start, end, client, intervalType, op) {
268
+ function createSequenceInterval(label, start, end, client, intervalType, op, fromSnapshot) {
244
269
  let beginRefType = ReferenceType.RangeBegin;
245
270
  let endRefType = ReferenceType.RangeEnd;
246
- if (intervalType === IntervalType.Nest) {
247
- beginRefType = ReferenceType.NestBegin;
248
- endRefType = ReferenceType.NestEnd;
249
- }
250
- else if (intervalType === IntervalType.Transient) {
271
+ if (intervalType === IntervalType.Transient) {
251
272
  beginRefType = ReferenceType.Transient;
252
273
  endRefType = ReferenceType.Transient;
253
274
  }
254
- // TODO: Should SlideOnRemove be the default behavior?
255
- if (intervalType & IntervalType.SlideOnRemove) {
256
- beginRefType |= ReferenceType.SlideOnRemove;
257
- endRefType |= ReferenceType.SlideOnRemove;
258
- }
259
- const startLref = createPositionReference(client, start, beginRefType, op);
260
- const endLref = createPositionReference(client, end, endRefType, op);
261
- if (startLref && endLref) {
262
- startLref.pairedRef = endLref;
263
- endLref.pairedRef = startLref;
264
- const rangeProp = {
265
- [reservedRangeLabelsKey]: [label],
266
- };
267
- startLref.addProperties(rangeProp);
268
- endLref.addProperties(rangeProp);
269
- const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
270
- return ival;
275
+ else {
276
+ if (intervalType === IntervalType.Nest) {
277
+ beginRefType = ReferenceType.NestBegin;
278
+ endRefType = ReferenceType.NestEnd;
279
+ }
280
+ // All non-transient interval references must eventually be SlideOnRemove
281
+ // To ensure eventual consistency, they must start as StayOnRemove when
282
+ // pending (created locally and creation op is not acked)
283
+ if (op || fromSnapshot) {
284
+ beginRefType |= ReferenceType.SlideOnRemove;
285
+ endRefType |= ReferenceType.SlideOnRemove;
286
+ }
287
+ else {
288
+ beginRefType |= ReferenceType.StayOnRemove;
289
+ endRefType |= ReferenceType.StayOnRemove;
290
+ }
271
291
  }
292
+ const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot);
293
+ const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot);
294
+ startLref.pairedRef = endLref;
295
+ endLref.pairedRef = startLref;
296
+ const rangeProp = {
297
+ [reservedRangeLabelsKey]: [label],
298
+ };
299
+ startLref.addProperties(rangeProp);
300
+ endLref.addProperties(rangeProp);
301
+ const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
302
+ return ival;
272
303
  }
273
304
  export function defaultIntervalConflictResolver(a, b) {
274
305
  a.addPropertySet(b.properties);
@@ -398,14 +429,12 @@ export class LocalIntervalCollection {
398
429
  }
399
430
  }
400
431
  findOverlappingIntervals(startPosition, endPosition) {
401
- if (!this.intervalTree.intervals.isEmpty()) {
402
- const transientInterval = this.helpers.create("transient", startPosition, endPosition, this.client, IntervalType.Transient);
403
- const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
404
- return overlappingIntervalNodes.map((node) => node.key);
405
- }
406
- else {
432
+ if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
407
433
  return [];
408
434
  }
435
+ const transientInterval = this.helpers.create("transient", startPosition, endPosition, this.client, IntervalType.Transient);
436
+ const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
437
+ return overlappingIntervalNodes.map((node) => node.key);
409
438
  }
410
439
  previousInterval(pos) {
411
440
  const transientInterval = this.helpers.create("transient", pos, pos, this.client, IntervalType.Transient);
@@ -642,7 +671,10 @@ export class IntervalCollection extends TypedEventEmitter {
642
671
  if (this.savedSerializedIntervals) {
643
672
  for (const serializedInterval of this.savedSerializedIntervals) {
644
673
  this.localCollection.ensureSerializedId(serializedInterval);
645
- this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties);
674
+ const { start, end, intervalType, properties } = serializedInterval;
675
+ const interval = this.helpers.create(label, start, end, client, intervalType, undefined, true);
676
+ interval.addProperties(properties);
677
+ this.localCollection.add(interval);
646
678
  }
647
679
  }
648
680
  this.savedSerializedIntervals = undefined;
@@ -653,11 +685,22 @@ export class IntervalCollection extends TypedEventEmitter {
653
685
  }
654
686
  return this.localCollection.getIntervalById(id);
655
687
  }
688
+ /**
689
+ * Create a new interval and add it to the collection
690
+ * @param start - interval start position
691
+ * @param end - interval end position
692
+ * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
693
+ * @param props - properties of the interval
694
+ * @returns - the created interval
695
+ */
656
696
  add(start, end, intervalType, props) {
657
697
  var _a, _b;
658
698
  if (!this.attached) {
659
699
  throw new Error("attach must be called prior to adding intervals");
660
700
  }
701
+ if (intervalType & IntervalType.Transient) {
702
+ throw new Error("Can not add transient intervals");
703
+ }
661
704
  const interval = this.localCollection.addInterval(start, end, intervalType, props);
662
705
  if (interval) {
663
706
  const serializedInterval = {
@@ -816,13 +859,15 @@ export class IntervalCollection extends TypedEventEmitter {
816
859
  // This is an ack from the server. Remove the pending change.
817
860
  this.removePendingChange(serializedInterval);
818
861
  const id = serializedInterval.properties[reservedIntervalIdKey];
862
+ // Could store the interval in the localOpMetadata to avoid the getIntervalById call
819
863
  interval = this.getIntervalById(id);
820
864
  if (interval) {
821
865
  // Let the propertyManager prune its pending change-properties set.
822
866
  (_a = interval.propertyManager) === null || _a === void 0 ? void 0 : _a.ackPendingProperties({
823
- type: 2 /* ANNOTATE */,
867
+ type: MergeTreeDeltaType.ANNOTATE,
824
868
  props: serializedInterval.properties,
825
869
  });
870
+ this.ackInterval(interval, op);
826
871
  }
827
872
  }
828
873
  else {
@@ -877,6 +922,46 @@ export class IntervalCollection extends TypedEventEmitter {
877
922
  this.onDeserialize(interval);
878
923
  });
879
924
  }
925
+ getSlideToSegment(lref) {
926
+ const segoff = { segment: lref.segment, offset: lref.offset };
927
+ const newSegoff = this.client.getSlideToSegment(segoff);
928
+ const value = (segoff === newSegoff) ? undefined : newSegoff;
929
+ return value;
930
+ }
931
+ setSlideOnRemove(lref) {
932
+ let refType = lref.refType;
933
+ refType = refType & ~ReferenceType.StayOnRemove;
934
+ refType = refType | ReferenceType.SlideOnRemove;
935
+ lref.refType = refType;
936
+ }
937
+ ackInterval(interval, op) {
938
+ // in current usage, interval is always a SequenceInterval
939
+ if (!(interval instanceof SequenceInterval)) {
940
+ return;
941
+ }
942
+ if (!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove)) {
943
+ return;
944
+ }
945
+ assert(refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove), 0x2f7 /* start and end must both be StayOnRemove */);
946
+ const newStart = this.getSlideToSegment(interval.start);
947
+ const newEnd = this.getSlideToSegment(interval.end);
948
+ this.setSlideOnRemove(interval.start);
949
+ this.setSlideOnRemove(interval.end);
950
+ if (newStart || newEnd) {
951
+ this.localCollection.removeExistingInterval(interval);
952
+ if (newStart) {
953
+ const props = interval.start.properties;
954
+ interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
955
+ interval.start.addProperties(props);
956
+ }
957
+ if (newEnd) {
958
+ const props = interval.end.properties;
959
+ interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
960
+ interval.end.addProperties(props);
961
+ }
962
+ this.localCollection.add(interval);
963
+ }
964
+ }
880
965
  /** @deprecated - use ackAdd */
881
966
  addInternal(serializedInterval, local, op) {
882
967
  return this.ackAdd(serializedInterval, local, op);
@@ -884,8 +969,12 @@ export class IntervalCollection extends TypedEventEmitter {
884
969
  /** @internal */
885
970
  ackAdd(serializedInterval, local, op) {
886
971
  if (local) {
887
- // Local ops were applied when the message was created and there's no "pending add"
888
- // state to bookkeep
972
+ const id = serializedInterval.properties[reservedIntervalIdKey];
973
+ // Could store the interval in the localOpMetadata to avoid the getIntervalById call
974
+ const localInterval = this.getIntervalById(id);
975
+ if (localInterval) {
976
+ this.ackInterval(localInterval, op);
977
+ }
889
978
  return;
890
979
  }
891
980
  if (!this.attached) {