@fluidframework/sequence 1.3.0 → 2.0.0-dev.1.4.5.105745

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 (73) hide show
  1. package/.mocharc.js +12 -0
  2. package/README.md +19 -18
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +3 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/intervalCollection.d.ts +51 -18
  8. package/dist/intervalCollection.d.ts.map +1 -1
  9. package/dist/intervalCollection.js +117 -73
  10. package/dist/intervalCollection.js.map +1 -1
  11. package/dist/packageVersion.d.ts +1 -1
  12. package/dist/packageVersion.d.ts.map +1 -1
  13. package/dist/packageVersion.js +1 -1
  14. package/dist/packageVersion.js.map +1 -1
  15. package/dist/sequence.d.ts +13 -22
  16. package/dist/sequence.d.ts.map +1 -1
  17. package/dist/sequence.js +11 -32
  18. package/dist/sequence.js.map +1 -1
  19. package/dist/sequenceDeltaEvent.d.ts +0 -6
  20. package/dist/sequenceDeltaEvent.d.ts.map +1 -1
  21. package/dist/sequenceDeltaEvent.js +0 -1
  22. package/dist/sequenceDeltaEvent.js.map +1 -1
  23. package/dist/sharedIntervalCollection.d.ts +5 -5
  24. package/dist/sharedIntervalCollection.js +5 -5
  25. package/dist/sharedIntervalCollection.js.map +1 -1
  26. package/dist/sharedString.d.ts +30 -1
  27. package/dist/sharedString.d.ts.map +1 -1
  28. package/dist/sharedString.js +40 -5
  29. package/dist/sharedString.js.map +1 -1
  30. package/dist/sparsematrix.d.ts +28 -15
  31. package/dist/sparsematrix.d.ts.map +1 -1
  32. package/dist/sparsematrix.js +24 -13
  33. package/dist/sparsematrix.js.map +1 -1
  34. package/lib/index.d.ts +2 -2
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +2 -2
  37. package/lib/index.js.map +1 -1
  38. package/lib/intervalCollection.d.ts +51 -18
  39. package/lib/intervalCollection.d.ts.map +1 -1
  40. package/lib/intervalCollection.js +116 -73
  41. package/lib/intervalCollection.js.map +1 -1
  42. package/lib/packageVersion.d.ts +1 -1
  43. package/lib/packageVersion.d.ts.map +1 -1
  44. package/lib/packageVersion.js +1 -1
  45. package/lib/packageVersion.js.map +1 -1
  46. package/lib/sequence.d.ts +13 -22
  47. package/lib/sequence.d.ts.map +1 -1
  48. package/lib/sequence.js +12 -33
  49. package/lib/sequence.js.map +1 -1
  50. package/lib/sequenceDeltaEvent.d.ts +0 -6
  51. package/lib/sequenceDeltaEvent.d.ts.map +1 -1
  52. package/lib/sequenceDeltaEvent.js +0 -1
  53. package/lib/sequenceDeltaEvent.js.map +1 -1
  54. package/lib/sharedIntervalCollection.d.ts +5 -5
  55. package/lib/sharedIntervalCollection.js +5 -5
  56. package/lib/sharedIntervalCollection.js.map +1 -1
  57. package/lib/sharedString.d.ts +30 -1
  58. package/lib/sharedString.d.ts.map +1 -1
  59. package/lib/sharedString.js +38 -4
  60. package/lib/sharedString.js.map +1 -1
  61. package/lib/sparsematrix.d.ts +28 -15
  62. package/lib/sparsematrix.d.ts.map +1 -1
  63. package/lib/sparsematrix.js +24 -13
  64. package/lib/sparsematrix.js.map +1 -1
  65. package/package.json +70 -25
  66. package/src/index.ts +3 -1
  67. package/src/intervalCollection.ts +209 -97
  68. package/src/packageVersion.ts +1 -1
  69. package/src/sequence.ts +12 -41
  70. package/src/sequenceDeltaEvent.ts +0 -7
  71. package/src/sharedIntervalCollection.ts +5 -5
  72. package/src/sharedString.ts +44 -6
  73. package/src/sparsematrix.ts +48 -35
@@ -11,6 +11,7 @@ import { UsageError } from "@fluidframework/container-utils";
11
11
  import {
12
12
  addProperties,
13
13
  Client,
14
+ compareReferencePositions,
14
15
  ConflictAction,
15
16
  createMap,
16
17
  ICombiningOp,
@@ -19,15 +20,19 @@ import {
19
20
  IntervalNode,
20
21
  IntervalTree,
21
22
  ISegment,
22
- LocalReference,
23
23
  MergeTreeDeltaType,
24
+ minReferencePosition,
24
25
  PropertiesManager,
25
26
  PropertySet,
26
27
  RedBlackTree,
28
+ LocalReferencePosition,
27
29
  ReferenceType,
28
30
  refTypeIncludesFlag,
29
31
  reservedRangeLabelsKey,
30
32
  UnassignedSequenceNumber,
33
+ maxReferencePosition,
34
+ createDetachedLocalReferencePosition,
35
+ DetachedReferencePosition,
31
36
  } from "@fluidframework/merge-tree";
32
37
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
33
38
  import { LoggingError } from "@fluidframework/telemetry-utils";
@@ -46,6 +51,7 @@ const reservedIntervalIdKey = "intervalId";
46
51
  export enum IntervalType {
47
52
  Simple = 0x0,
48
53
  Nest = 0x1,
54
+
49
55
  /**
50
56
  * SlideOnRemove indicates that the ends of the interval will slide if the segment
51
57
  * they reference is removed and acked.
@@ -53,9 +59,10 @@ export enum IntervalType {
53
59
  * SlideOnRemove is the default interval behavior and does not need to be specified.
54
60
  */
55
61
  SlideOnRemove = 0x2, // SlideOnRemove is default behavior - all intervals are SlideOnRemove
62
+
56
63
  /**
57
- * @internal
58
64
  * A temporary interval, used internally
65
+ * @internal
59
66
  */
60
67
  Transient = 0x4,
61
68
  }
@@ -95,7 +102,7 @@ function decompressInterval(interval: CompressedSerializedInterval, label?: stri
95
102
  end: interval[1],
96
103
  sequenceNumber: interval[2],
97
104
  intervalType: interval[3],
98
- properties: { ...interval[4], [reservedRangeLabelsKey]: label },
105
+ properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
99
106
  };
100
107
  }
101
108
 
@@ -128,8 +135,26 @@ export interface ISerializableInterval extends IInterval {
128
135
 
129
136
  export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
130
137
  compareEnds(a: TInterval, b: TInterval): number;
131
- create(label: string, start: number, end: number,
132
- client: Client, intervalType?: IntervalType, op?: ISequencedDocumentMessage): TInterval;
138
+ /**
139
+ *
140
+ * @param label - label of the interval collection this interval is being added to. This parameter is
141
+ * irrelevant for transient intervals.
142
+ * @param start - numerical start position of the interval
143
+ * @param end - numberical end position of the interval
144
+ * @param client - client creating the interval
145
+ * @param intervalType - Type of interval to create. Default is SlideOnRemove
146
+ * @param op - If this create came from a remote client, op that created it. Default is undefined (i.e. local)
147
+ * @param fromSnapshot - If this create came from loading a snapshot. Default is false.
148
+ */
149
+ create(
150
+ label: string,
151
+ start: number,
152
+ end: number,
153
+ client: Client,
154
+ intervalType?: IntervalType,
155
+ op?: ISequencedDocumentMessage,
156
+ fromSnapshot?: boolean,
157
+ ): TInterval;
133
158
  }
134
159
 
135
160
  export class Interval implements ISerializableInterval {
@@ -169,11 +194,7 @@ export class Interval implements ISerializableInterval {
169
194
  }
170
195
 
171
196
  public serialize(client: Client): ISerializedInterval {
172
- let seq = 0;
173
- if (client) {
174
- seq = client.getCurrentSeq();
175
- }
176
-
197
+ const seq = client?.getCurrentSeq() ?? 0;
177
198
  const serializedInterval: ISerializedInterval = {
178
199
  end: this.end,
179
200
  intervalType: 0,
@@ -277,8 +298,9 @@ export class SequenceInterval implements ISerializableInterval {
277
298
  public propertyManager: PropertiesManager;
278
299
 
279
300
  constructor(
280
- public start: LocalReference,
281
- public end: LocalReference,
301
+ private readonly client: Client,
302
+ public start: LocalReferencePosition,
303
+ public end: LocalReferencePosition,
282
304
  public intervalType: IntervalType,
283
305
  props?: PropertySet,
284
306
  ) {
@@ -293,8 +315,8 @@ export class SequenceInterval implements ISerializableInterval {
293
315
  private callbacks?: Record<"beforePositionChange" | "afterPositionChange", () => void>;
294
316
 
295
317
  /**
296
- * @internal
297
318
  * Subscribes to position change events on this interval if there are no current listeners.
319
+ * @internal
298
320
  */
299
321
  public addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void {
300
322
  if (this.callbacks === undefined) {
@@ -311,8 +333,8 @@ export class SequenceInterval implements ISerializableInterval {
311
333
  }
312
334
 
313
335
  /**
314
- * @internal
315
336
  * Removes the currently subscribed position change listeners.
337
+ * @internal
316
338
  */
317
339
  public removePositionChangeListeners(): void {
318
340
  if (this.callbacks) {
@@ -323,8 +345,8 @@ export class SequenceInterval implements ISerializableInterval {
323
345
  }
324
346
 
325
347
  public serialize(client: Client): ISerializedInterval {
326
- const startPosition = this.start.toPosition();
327
- const endPosition = this.end.toPosition();
348
+ const startPosition = client.localReferencePositionToPosition(this.start);
349
+ const endPosition = client.localReferencePositionToPosition(this.end);
328
350
  const serializedInterval: ISerializedInterval = {
329
351
  end: endPosition,
330
352
  intervalType: this.intervalType,
@@ -340,7 +362,7 @@ export class SequenceInterval implements ISerializableInterval {
340
362
  }
341
363
 
342
364
  public clone() {
343
- return new SequenceInterval(this.start, this.end, this.intervalType, this.properties);
365
+ return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties);
344
366
  }
345
367
 
346
368
  public compare(b: SequenceInterval) {
@@ -366,16 +388,16 @@ export class SequenceInterval implements ISerializableInterval {
366
388
  }
367
389
 
368
390
  public compareStart(b: SequenceInterval) {
369
- return this.start.compare(b.start);
391
+ return compareReferencePositions(this.start, b.start);
370
392
  }
371
393
 
372
394
  public compareEnd(b: SequenceInterval) {
373
- return this.end.compare(b.end);
395
+ return compareReferencePositions(this.end, b.end);
374
396
  }
375
397
 
376
398
  public overlaps(b: SequenceInterval) {
377
- const result = (this.start.compare(b.end) <= 0) &&
378
- (this.end.compare(b.start) >= 0);
399
+ const result = (compareReferencePositions(this.start, b.end) <= 0) &&
400
+ (compareReferencePositions(this.end, b.start) >= 0);
379
401
  return result;
380
402
  }
381
403
 
@@ -388,8 +410,8 @@ export class SequenceInterval implements ISerializableInterval {
388
410
  }
389
411
 
390
412
  public union(b: SequenceInterval) {
391
- return new SequenceInterval(this.start.min(b.start),
392
- this.end.max(b.end), this.intervalType);
413
+ return new SequenceInterval(this.client, minReferencePosition(this.start, b.start),
414
+ maxReferencePosition(this.end, b.end), this.intervalType);
393
415
  }
394
416
 
395
417
  public addProperties(
@@ -403,12 +425,12 @@ export class SequenceInterval implements ISerializableInterval {
403
425
  }
404
426
 
405
427
  public overlapsPos(bstart: number, bend: number) {
406
- const startPos = this.start.toPosition();
407
- const endPos = this.start.toPosition();
428
+ const startPos = this.client.localReferencePositionToPosition(this.start);
429
+ const endPos = this.client.localReferencePositionToPosition(this.end);
408
430
  return (endPos > bstart) && (startPos < bend);
409
431
  }
410
432
 
411
- public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
433
+ public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage, localSeq?: number) {
412
434
  const getRefType = (baseType: ReferenceType): ReferenceType => {
413
435
  let refType = baseType;
414
436
  if (op === undefined) {
@@ -420,20 +442,21 @@ export class SequenceInterval implements ISerializableInterval {
420
442
 
421
443
  let startRef = this.start;
422
444
  if (start !== undefined) {
423
- startRef = createPositionReference(this.start.getClient(), start, getRefType(this.start.refType), op);
445
+ startRef = createPositionReference(
446
+ this.client, start, getRefType(this.start.refType), op, undefined, localSeq,
447
+ );
424
448
  startRef.addProperties(this.start.properties);
425
449
  }
426
450
 
427
451
  let endRef = this.end;
428
452
  if (end !== undefined) {
429
- endRef = createPositionReference(this.end.getClient(), end, getRefType(this.end.refType), op);
453
+ endRef = createPositionReference(
454
+ this.client, end, getRefType(this.end.refType), op, undefined, localSeq,
455
+ );
430
456
  endRef.addProperties(this.end.properties);
431
457
  }
432
458
 
433
- startRef.pairedRef = endRef;
434
- endRef.pairedRef = startRef;
435
-
436
- const newInterval = new SequenceInterval(startRef, endRef, this.intervalType);
459
+ const newInterval = new SequenceInterval(this.client, startRef, endRef, this.intervalType);
437
460
  if (this.properties) {
438
461
  newInterval.initializeProperties();
439
462
  this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
@@ -455,31 +478,37 @@ function createPositionReferenceFromSegoff(
455
478
  client: Client,
456
479
  segoff: { segment: ISegment | undefined; offset: number | undefined; },
457
480
  refType: ReferenceType,
458
- op?: ISequencedDocumentMessage): LocalReference {
481
+ op?: ISequencedDocumentMessage): LocalReferencePosition {
459
482
  if (segoff.segment) {
460
483
  const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
461
- return ref as LocalReference;
462
- } else {
463
- if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
464
- throw new UsageError("Non-transient references need segment");
465
- }
466
- return new LocalReference(client, undefined, 0, refType);
484
+ return ref;
467
485
  }
486
+
487
+ if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
488
+ // reference to segment that dne locally
489
+ throw new UsageError("Non-transient references need segment");
490
+ }
491
+
492
+ return createDetachedLocalReferencePosition(refType);
468
493
  }
469
494
 
470
495
  function createPositionReference(
471
496
  client: Client,
472
497
  pos: number,
473
498
  refType: ReferenceType,
474
- op?: ISequencedDocumentMessage): LocalReference {
499
+ op?: ISequencedDocumentMessage,
500
+ fromSnapshot?: boolean,
501
+ localSeq?: number,
502
+ ): LocalReferencePosition {
475
503
  let segoff;
476
504
  if (op) {
477
505
  assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
478
506
  segoff = client.getContainingSegment(pos, op);
479
507
  segoff = client.getSlideToSegment(segoff);
480
508
  } else {
481
- assert((refType & ReferenceType.SlideOnRemove) === 0, 0x2f6 /* SlideOnRemove references must be op created */);
482
- segoff = client.getContainingSegment(pos);
509
+ assert((refType & ReferenceType.SlideOnRemove) === 0 || fromSnapshot,
510
+ 0x2f6 /* SlideOnRemove references must be op created */);
511
+ segoff = client.getContainingSegment(pos, undefined, localSeq);
483
512
  }
484
513
  return createPositionReferenceFromSegoff(client, segoff, refType, op);
485
514
  }
@@ -490,7 +519,8 @@ function createSequenceInterval(
490
519
  end: number,
491
520
  client: Client,
492
521
  intervalType?: IntervalType,
493
- op?: ISequencedDocumentMessage): SequenceInterval {
522
+ op?: ISequencedDocumentMessage,
523
+ fromSnapshot?: boolean): SequenceInterval {
494
524
  let beginRefType = ReferenceType.RangeBegin;
495
525
  let endRefType = ReferenceType.RangeEnd;
496
526
  if (intervalType === IntervalType.Transient) {
@@ -504,7 +534,7 @@ function createSequenceInterval(
504
534
  // All non-transient interval references must eventually be SlideOnRemove
505
535
  // To ensure eventual consistency, they must start as StayOnRemove when
506
536
  // pending (created locally and creation op is not acked)
507
- if (op) {
537
+ if (op || fromSnapshot) {
508
538
  beginRefType |= ReferenceType.SlideOnRemove;
509
539
  endRefType |= ReferenceType.SlideOnRemove;
510
540
  } else {
@@ -513,17 +543,15 @@ function createSequenceInterval(
513
543
  }
514
544
  }
515
545
 
516
- const startLref = createPositionReference(client, start, beginRefType, op);
517
- const endLref = createPositionReference(client, end, endRefType, op);
518
- startLref.pairedRef = endLref;
519
- endLref.pairedRef = startLref;
546
+ const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot);
547
+ const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot);
520
548
  const rangeProp = {
521
549
  [reservedRangeLabelsKey]: [label],
522
550
  };
523
551
  startLref.addProperties(rangeProp);
524
552
  endLref.addProperties(rangeProp);
525
553
 
526
- const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
554
+ const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp);
527
555
  return ival;
528
556
  }
529
557
 
@@ -772,18 +800,23 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
772
800
  if (!interval.properties) {
773
801
  interval.properties = createMap<any>();
774
802
  }
803
+
775
804
  if (props) {
776
805
  interval.addProperties(props);
777
806
  }
778
- if (interval.properties[reservedIntervalIdKey] === undefined) {
779
- // Create a new ID.
780
- interval.properties[reservedIntervalIdKey] = uuid();
781
- }
807
+ interval.properties[reservedIntervalIdKey] ??= uuid();
782
808
  this.add(interval);
783
809
  }
784
810
  return interval;
785
811
  }
786
812
 
813
+ private linkEndpointsToInterval(interval: TInterval): void {
814
+ if (interval instanceof SequenceInterval) {
815
+ interval.start.addProperties({ interval });
816
+ interval.end.addProperties({ interval });
817
+ }
818
+ }
819
+
787
820
  private addIntervalToIndex(interval: TInterval) {
788
821
  const id = interval.getIntervalId();
789
822
  assert(id !== undefined, 0x2c0 /* "ID must be created before adding interval to collection" */);
@@ -799,6 +832,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
799
832
  }
800
833
 
801
834
  public add(interval: TInterval) {
835
+ this.linkEndpointsToInterval(interval);
802
836
  this.addIntervalToIndex(interval);
803
837
  this.addIntervalListeners(interval);
804
838
  }
@@ -807,8 +841,14 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
807
841
  return this.intervalIdMap.get(id);
808
842
  }
809
843
 
810
- public changeInterval(interval: TInterval, start: number, end: number, op?: ISequencedDocumentMessage) {
811
- const newInterval = interval.modify(this.label, start, end, op) as TInterval | undefined;
844
+ public changeInterval(
845
+ interval: TInterval,
846
+ start: number,
847
+ end: number,
848
+ op?: ISequencedDocumentMessage,
849
+ localSeq?: number,
850
+ ) {
851
+ const newInterval = interval.modify(this.label, start, end, op, localSeq) as TInterval | undefined;
812
852
  if (newInterval) {
813
853
  this.removeExistingInterval(interval);
814
854
  this.add(newInterval);
@@ -846,7 +886,8 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
846
886
  }
847
887
  }
848
888
 
849
- const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number => a.end.compare(b.end);
889
+ const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
890
+ compareReferencePositions(a.end, b.end);
850
891
 
851
892
  class SequenceIntervalCollectionFactory
852
893
  implements IValueFactory<IntervalCollection<SequenceInterval>> {
@@ -958,6 +999,11 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
958
999
  "add",
959
1000
  {
960
1001
  process: (collection, params, local, op) => {
1002
+ // if params is undefined, the interval was deleted during
1003
+ // rebasing
1004
+ if (!params) {
1005
+ return;
1006
+ }
961
1007
  collection.ackAdd(params, local, op);
962
1008
  },
963
1009
  rebase,
@@ -979,6 +1025,11 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
979
1025
  "change",
980
1026
  {
981
1027
  process: (collection, params, local, op) => {
1028
+ // if params is undefined, the interval was deleted during
1029
+ // rebasing
1030
+ if (!params) {
1031
+ return;
1032
+ }
982
1033
  collection.ackChange(params, local, op);
983
1034
  },
984
1035
  rebase,
@@ -1041,7 +1092,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1041
1092
  private savedSerializedIntervals?: ISerializedInterval[];
1042
1093
  private localCollection: LocalIntervalCollection<TInterval>;
1043
1094
  private onDeserialize: DeserializeCallback | undefined;
1044
- private client: Client;
1095
+ private client: Client | undefined;
1045
1096
  private readonly pendingChangesStart: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
1046
1097
  private readonly pendingChangesEnd: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
1047
1098
 
@@ -1058,12 +1109,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1058
1109
  ) {
1059
1110
  super();
1060
1111
 
1061
- if (Array.isArray(serializedIntervals)) {
1062
- this.savedSerializedIntervals = serializedIntervals;
1063
- } else {
1064
- this.savedSerializedIntervals =
1065
- serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
1066
- }
1112
+ this.savedSerializedIntervals = Array.isArray(serializedIntervals)
1113
+ ? serializedIntervals
1114
+ : serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
1067
1115
  }
1068
1116
 
1069
1117
  public attachGraph(client: Client, label: string) {
@@ -1086,11 +1134,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1086
1134
  if (this.savedSerializedIntervals) {
1087
1135
  for (const serializedInterval of this.savedSerializedIntervals) {
1088
1136
  this.localCollection.ensureSerializedId(serializedInterval);
1089
- this.localCollection.addInterval(
1090
- serializedInterval.start,
1091
- serializedInterval.end,
1092
- serializedInterval.intervalType,
1093
- serializedInterval.properties);
1137
+ const { start, end, intervalType, properties } = serializedInterval;
1138
+ const interval = this.helpers.create(
1139
+ label,
1140
+ start,
1141
+ end,
1142
+ client,
1143
+ intervalType,
1144
+ undefined,
1145
+ true,
1146
+ );
1147
+ interval.addProperties(properties);
1148
+ this.localCollection.add(interval);
1094
1149
  }
1095
1150
  }
1096
1151
  this.savedSerializedIntervals = undefined;
@@ -1100,7 +1155,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1100
1155
  * Gets the next local sequence number, modifying this client's collab window in doing so.
1101
1156
  */
1102
1157
  private getNextLocalSeq(): number {
1103
- return ++this.client.getCollabWindow().localSeq;
1158
+ if (this.client) {
1159
+ return ++this.client.getCollabWindow().localSeq;
1160
+ }
1161
+
1162
+ return 0;
1104
1163
  }
1105
1164
 
1106
1165
  public getIntervalById(id: string) {
@@ -1302,11 +1361,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1302
1361
  return entries && entries.length !== 0;
1303
1362
  }
1304
1363
 
1305
- /** @deprecated - use ackChange */
1306
- public changeInterval(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
1307
- return this.ackChange(serializedInterval, local, op);
1308
- }
1309
-
1310
1364
  /** @internal */
1311
1365
  public ackChange(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
1312
1366
  if (!this.attached) {
@@ -1387,12 +1441,22 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1387
1441
  });
1388
1442
  }
1389
1443
 
1390
- /** @internal */
1444
+ /**
1445
+ * Returns new interval after rebasing. If undefined, the interval was
1446
+ * deleted as a result of rebasing. This can occur if the interval applies
1447
+ * to a range that no longer exists, and the interval was unable to slide.
1448
+ *
1449
+ * @internal
1450
+ */
1391
1451
  public rebaseLocalInterval(
1392
1452
  opName: string,
1393
1453
  serializedInterval: ISerializedInterval,
1394
1454
  localSeq: number,
1395
- ) {
1455
+ ): ISerializedInterval | undefined {
1456
+ if (!this.client) {
1457
+ // If there's no associated mergeTree client, the originally submitted op is still correct.
1458
+ return serializedInterval;
1459
+ }
1396
1460
  if (!this.attached) {
1397
1461
  throw new LoggingError("attachSequence must be called");
1398
1462
  }
@@ -1404,6 +1468,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1404
1468
  this.client.rebasePosition(end, sequenceNumber, localSeq);
1405
1469
 
1406
1470
  const intervalId = properties?.[reservedIntervalIdKey];
1471
+ const localInterval = this.localCollection.getIntervalById(intervalId);
1472
+
1407
1473
  const rebased: ISerializedInterval = {
1408
1474
  start: startRebased,
1409
1475
  end: endRebased,
@@ -1411,22 +1477,56 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1411
1477
  sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1412
1478
  properties,
1413
1479
  };
1480
+
1414
1481
  if (opName === "change" && (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
1415
1482
  this.removePendingChange(serializedInterval);
1416
1483
  this.addPendingChange(intervalId, rebased);
1417
1484
  }
1485
+
1486
+ // if the interval slid off the string, rebase the op to be a noop and
1487
+ // delete the interval
1488
+ if (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition) {
1489
+ if (localInterval) {
1490
+ this.localCollection.removeExistingInterval(localInterval);
1491
+ }
1492
+ return undefined;
1493
+ }
1494
+
1495
+ if (!localInterval) {
1496
+ return rebased;
1497
+ }
1498
+
1499
+ // we know we must be using `SequenceInterval` because `this.client` exists
1500
+ assert(
1501
+ localInterval instanceof SequenceInterval,
1502
+ 0x3a0 /* localInterval must be `SequenceInterval` when used with client */,
1503
+ );
1504
+
1505
+ const startSegment = this.getSlideToSegment(localInterval.start);
1506
+ const endSegment = this.getSlideToSegment(localInterval.end);
1507
+
1508
+ // we need to slide because the reference has been removed
1509
+ if (startSegment || endSegment) {
1510
+ const newStart =
1511
+ startSegment && this.client.getPosition(startSegment.segment, localSeq) + startSegment.offset;
1512
+ const newEnd =
1513
+ endSegment && this.client.getPosition(endSegment.segment, localSeq) + endSegment.offset;
1514
+
1515
+ this.localCollection.changeInterval(localInterval, newStart, newEnd, undefined, localSeq);
1516
+ }
1517
+
1418
1518
  return rebased;
1419
1519
  }
1420
1520
 
1421
- private getSlideToSegment(lref: LocalReference) {
1422
- const segoff = { segment: lref.segment, offset: lref.offset };
1521
+ private getSlideToSegment(lref: LocalReferencePosition) {
1522
+ const segoff = { segment: lref.getSegment(), offset: lref.getOffset() };
1423
1523
  const newSegoff = this.client.getSlideToSegment(segoff);
1424
1524
  const value: { segment: ISegment | undefined; offset: number | undefined; } | undefined
1425
1525
  = (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
1426
1526
  return value;
1427
1527
  }
1428
1528
 
1429
- private setSlideOnRemove(lref: LocalReference) {
1529
+ private setSlideOnRemove(lref: LocalReferencePosition) {
1430
1530
  let refType = lref.refType;
1431
1531
  refType = refType & ~ReferenceType.StayOnRemove;
1432
1532
  refType = refType | ReferenceType.SlideOnRemove;
@@ -1434,7 +1534,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1434
1534
  }
1435
1535
 
1436
1536
  private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
1437
- // in current usage, interval is always a SequenceInterval
1537
+ // Only SequenceIntervals need potential sliding
1438
1538
  if (!(interval instanceof SequenceInterval)) {
1439
1539
  return;
1440
1540
  }
@@ -1465,7 +1565,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1465
1565
  if (needsStartUpdate || needsEndUpdate) {
1466
1566
  // In this case, where we change the start or end of an interval,
1467
1567
  // it is necessary to remove and re-add the interval listeners.
1468
- // This ensures that the correct listeners are added to the ReferencePosition.
1568
+ // This ensures that the correct listeners are added to the LocalReferencePosition.
1469
1569
  this.localCollection.removeExistingInterval(interval);
1470
1570
 
1471
1571
  if (needsStartUpdate) {
@@ -1488,14 +1588,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1488
1588
  }
1489
1589
  }
1490
1590
 
1491
- /** @deprecated - use ackAdd */
1492
- public addInternal(
1493
- serializedInterval: ISerializedInterval,
1494
- local: boolean,
1495
- op: ISequencedDocumentMessage) {
1496
- return this.ackAdd(serializedInterval, local, op);
1497
- }
1498
-
1499
1591
  /** @internal */
1500
1592
  public ackAdd(
1501
1593
  serializedInterval: ISerializedInterval,
@@ -1534,14 +1626,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1534
1626
  return interval;
1535
1627
  }
1536
1628
 
1537
- /** @deprecated - use ackDelete */
1538
- public deleteInterval(
1539
- serializedInterval: ISerializedInterval,
1540
- local: boolean,
1541
- op: ISequencedDocumentMessage): void {
1542
- return this.ackDelete(serializedInterval, local, op);
1543
- }
1544
-
1545
1629
  /** @internal */
1546
1630
  public ackDelete(
1547
1631
  serializedInterval: ISerializedInterval,
@@ -1645,3 +1729,31 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1645
1729
  return this.localCollection.nextInterval(pos);
1646
1730
  }
1647
1731
  }
1732
+
1733
+ /**
1734
+ * Information that identifies an interval within a `Sequence`.
1735
+ */
1736
+ export interface IntervalLocator {
1737
+ /**
1738
+ * Label for the collection the interval is a part of
1739
+ */
1740
+ label: string;
1741
+ /**
1742
+ * Interval within that collection
1743
+ */
1744
+ interval: SequenceInterval;
1745
+ }
1746
+
1747
+ /**
1748
+ * Returns an object that can be used to find the interval a given LocalReferencePosition belongs to.
1749
+ * @returns undefined if the reference position is not the endpoint of any interval (e.g. it was created
1750
+ * on the merge tree directly by app code), otherwise an {@link IntervalLocator} for the interval this
1751
+ * endpoint is a part of.
1752
+ */
1753
+ export function intervalLocatorFromEndpoint(potentialEndpoint: LocalReferencePosition): IntervalLocator | undefined {
1754
+ const {
1755
+ interval,
1756
+ [reservedRangeLabelsKey]: collectionNameArray,
1757
+ } = potentialEndpoint.properties ?? {};
1758
+ return (interval && collectionNameArray?.length === 1) ? { label: collectionNameArray[0], interval } : undefined;
1759
+ }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/sequence";
9
- export const pkgVersion = "1.3.0";
9
+ export const pkgVersion = "2.0.0-dev.1.4.5.105745";