@fluidframework/sequence 1.0.1 → 1.1.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 (65) hide show
  1. package/README.md +18 -6
  2. package/dist/defaultMap.d.ts +2 -6
  3. package/dist/defaultMap.d.ts.map +1 -1
  4. package/dist/defaultMap.js +27 -37
  5. package/dist/defaultMap.js.map +1 -1
  6. package/dist/defaultMapInterfaces.d.ts +24 -3
  7. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  8. package/dist/defaultMapInterfaces.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/intervalCollection.d.ts +72 -8
  13. package/dist/intervalCollection.d.ts.map +1 -1
  14. package/dist/intervalCollection.js +325 -155
  15. package/dist/intervalCollection.js.map +1 -1
  16. package/dist/packageVersion.d.ts +1 -1
  17. package/dist/packageVersion.js +1 -1
  18. package/dist/packageVersion.js.map +1 -1
  19. package/dist/sequence.d.ts +4 -5
  20. package/dist/sequence.d.ts.map +1 -1
  21. package/dist/sequence.js +11 -15
  22. package/dist/sequence.js.map +1 -1
  23. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  24. package/dist/sharedIntervalCollection.js +1 -1
  25. package/dist/sharedIntervalCollection.js.map +1 -1
  26. package/dist/sharedSequence.js.map +1 -1
  27. package/dist/sparsematrix.js +2 -2
  28. package/dist/sparsematrix.js.map +1 -1
  29. package/lib/defaultMap.d.ts +2 -6
  30. package/lib/defaultMap.d.ts.map +1 -1
  31. package/lib/defaultMap.js +27 -37
  32. package/lib/defaultMap.js.map +1 -1
  33. package/lib/defaultMapInterfaces.d.ts +24 -3
  34. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  35. package/lib/defaultMapInterfaces.js.map +1 -1
  36. package/lib/index.d.ts +2 -2
  37. package/lib/index.d.ts.map +1 -1
  38. package/lib/index.js.map +1 -1
  39. package/lib/intervalCollection.d.ts +72 -8
  40. package/lib/intervalCollection.d.ts.map +1 -1
  41. package/lib/intervalCollection.js +325 -155
  42. package/lib/intervalCollection.js.map +1 -1
  43. package/lib/packageVersion.d.ts +1 -1
  44. package/lib/packageVersion.js +1 -1
  45. package/lib/packageVersion.js.map +1 -1
  46. package/lib/sequence.d.ts +4 -5
  47. package/lib/sequence.d.ts.map +1 -1
  48. package/lib/sequence.js +11 -15
  49. package/lib/sequence.js.map +1 -1
  50. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  51. package/lib/sharedIntervalCollection.js +1 -1
  52. package/lib/sharedIntervalCollection.js.map +1 -1
  53. package/lib/sharedSequence.js.map +1 -1
  54. package/lib/sparsematrix.js +2 -2
  55. package/lib/sparsematrix.js.map +1 -1
  56. package/package.json +20 -44
  57. package/src/defaultMap.ts +39 -41
  58. package/src/defaultMapInterfaces.ts +28 -3
  59. package/src/index.ts +3 -0
  60. package/src/intervalCollection.ts +447 -181
  61. package/src/packageVersion.ts +1 -1
  62. package/src/sequence.ts +17 -21
  63. package/src/sharedIntervalCollection.ts +3 -2
  64. package/src/sharedSequence.ts +1 -1
  65. package/src/sparsematrix.ts +2 -2
@@ -17,6 +17,7 @@ var __rest = (this && this.__rest) || function (s, e) {
17
17
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
18
18
  import { UsageError } from "@fluidframework/container-utils";
19
19
  import { addProperties, createMap, IntervalTree, LocalReference, MergeTreeDeltaType, PropertiesManager, RedBlackTree, ReferenceType, refTypeIncludesFlag, reservedRangeLabelsKey, UnassignedSequenceNumber, } from "@fluidframework/merge-tree";
20
+ import { LoggingError } from "@fluidframework/telemetry-utils";
20
21
  import { v4 as uuid } from "uuid";
21
22
  const reservedIntervalIdKey = "intervalId";
22
23
  export var IntervalType;
@@ -36,10 +37,39 @@ export var IntervalType;
36
37
  */
37
38
  IntervalType[IntervalType["Transient"] = 4] = "Transient";
38
39
  })(IntervalType || (IntervalType = {}));
40
+ /**
41
+ * Decompress an interval after loading a summary from JSON. The exact format
42
+ * of this compression is unspecified and subject to change
43
+ */
44
+ function decompressInterval(interval, label) {
45
+ return {
46
+ start: interval[0],
47
+ end: interval[1],
48
+ sequenceNumber: interval[2],
49
+ intervalType: interval[3],
50
+ properties: Object.assign(Object.assign({}, interval[4]), { [reservedRangeLabelsKey]: label }),
51
+ };
52
+ }
53
+ /**
54
+ * Compress an interval prior to serialization as JSON. The exact format of this
55
+ * compression is unspecified and subject to change
56
+ */
57
+ function compressInterval(interval) {
58
+ const { start, end, sequenceNumber, intervalType, properties } = interval;
59
+ return [
60
+ start,
61
+ end,
62
+ sequenceNumber,
63
+ intervalType,
64
+ Object.assign(Object.assign({}, properties), { [reservedRangeLabelsKey]: undefined }),
65
+ ];
66
+ }
39
67
  export class Interval {
40
68
  constructor(start, end, props) {
41
69
  this.start = start;
42
70
  this.end = end;
71
+ this.propertyManager = new PropertiesManager();
72
+ this.properties = {};
43
73
  if (props) {
44
74
  this.addProperties(props);
45
75
  }
@@ -122,12 +152,7 @@ export class Interval {
122
152
  }
123
153
  addProperties(newProps, collaborating = false, seq, op) {
124
154
  if (newProps) {
125
- if (!this.propertyManager) {
126
- this.propertyManager = new PropertiesManager();
127
- }
128
- if (!this.properties) {
129
- this.properties = createMap();
130
- }
155
+ this.initializeProperties();
131
156
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collaborating);
132
157
  }
133
158
  }
@@ -138,7 +163,20 @@ export class Interval {
138
163
  // Return undefined to indicate that no change is necessary.
139
164
  return;
140
165
  }
141
- return new Interval(startPos, endPos, this.properties);
166
+ const newInterval = new Interval(startPos, endPos);
167
+ if (this.properties) {
168
+ newInterval.initializeProperties();
169
+ this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
170
+ }
171
+ return newInterval;
172
+ }
173
+ initializeProperties() {
174
+ if (!this.propertyManager) {
175
+ this.propertyManager = new PropertiesManager();
176
+ }
177
+ if (!this.properties) {
178
+ this.properties = createMap();
179
+ }
142
180
  }
143
181
  }
144
182
  export class SequenceInterval {
@@ -146,10 +184,41 @@ export class SequenceInterval {
146
184
  this.start = start;
147
185
  this.end = end;
148
186
  this.intervalType = intervalType;
187
+ this.propertyManager = new PropertiesManager();
188
+ this.properties = {};
149
189
  if (props) {
150
190
  this.addProperties(props);
151
191
  }
152
192
  }
193
+ /**
194
+ * @internal
195
+ * Subscribes to position change events on this interval if there are no current listeners.
196
+ */
197
+ addPositionChangeListeners(beforePositionChange, afterPositionChange) {
198
+ var _a, _b;
199
+ var _c, _d;
200
+ if (this.callbacks === undefined) {
201
+ this.callbacks = {
202
+ beforePositionChange,
203
+ afterPositionChange,
204
+ };
205
+ const startCbs = (_a = (_c = this.start).callbacks) !== null && _a !== void 0 ? _a : (_c.callbacks = {});
206
+ const endCbs = (_b = (_d = this.end).callbacks) !== null && _b !== void 0 ? _b : (_d.callbacks = {});
207
+ startCbs.beforeSlide = endCbs.beforeSlide = beforePositionChange;
208
+ startCbs.afterSlide = endCbs.afterSlide = afterPositionChange;
209
+ }
210
+ }
211
+ /**
212
+ * @internal
213
+ * Removes the currently subscribed position change listeners.
214
+ */
215
+ removePositionChangeListeners() {
216
+ if (this.callbacks) {
217
+ this.callbacks = undefined;
218
+ this.start.callbacks = undefined;
219
+ this.end.callbacks = undefined;
220
+ }
221
+ }
153
222
  serialize(client) {
154
223
  const startPosition = this.start.toPosition();
155
224
  const endPosition = this.end.toPosition();
@@ -213,12 +282,7 @@ export class SequenceInterval {
213
282
  return new SequenceInterval(this.start.min(b.start), this.end.max(b.end), this.intervalType);
214
283
  }
215
284
  addProperties(newProps, collab = false, seq, op) {
216
- if (!this.propertyManager) {
217
- this.propertyManager = new PropertiesManager();
218
- }
219
- if (!this.properties) {
220
- this.properties = createMap();
221
- }
285
+ this.initializeProperties();
222
286
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
223
287
  }
224
288
  overlapsPos(bstart, bend) {
@@ -227,18 +291,41 @@ export class SequenceInterval {
227
291
  return (endPos > bstart) && (startPos < bend);
228
292
  }
229
293
  modify(label, start, end, op) {
230
- const startPos = start !== null && start !== void 0 ? start : this.start.toPosition();
231
- const endPos = end !== null && end !== void 0 ? end : this.end.toPosition();
232
- if (this.start.toPosition() === startPos && this.end.toPosition() === endPos) {
233
- // Return undefined to indicate that no change is necessary.
234
- return;
235
- }
236
- const newInterval = createSequenceInterval(label, startPos, endPos, this.start.getClient(), this.intervalType, op);
294
+ const getRefType = (baseType) => {
295
+ let refType = baseType;
296
+ if (op === undefined) {
297
+ refType &= ~ReferenceType.SlideOnRemove;
298
+ refType |= ReferenceType.StayOnRemove;
299
+ }
300
+ return refType;
301
+ };
302
+ let startRef = this.start;
303
+ if (start !== undefined) {
304
+ startRef = createPositionReference(this.start.getClient(), start, getRefType(this.start.refType), op);
305
+ startRef.addProperties(this.start.properties);
306
+ }
307
+ let endRef = this.end;
308
+ if (end !== undefined) {
309
+ endRef = createPositionReference(this.end.getClient(), end, getRefType(this.end.refType), op);
310
+ endRef.addProperties(this.end.properties);
311
+ }
312
+ startRef.pairedRef = endRef;
313
+ endRef.pairedRef = startRef;
314
+ const newInterval = new SequenceInterval(startRef, endRef, this.intervalType);
237
315
  if (this.properties) {
238
- newInterval.addProperties(this.properties);
316
+ newInterval.initializeProperties();
317
+ this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
239
318
  }
240
319
  return newInterval;
241
320
  }
321
+ initializeProperties() {
322
+ if (!this.propertyManager) {
323
+ this.propertyManager = new PropertiesManager();
324
+ }
325
+ if (!this.properties) {
326
+ this.properties = createMap();
327
+ }
328
+ }
242
329
  }
243
330
  function createPositionReferenceFromSegoff(client, segoff, refType, op) {
244
331
  if (segoff.segment) {
@@ -320,11 +407,15 @@ export function createIntervalIndex(conflict) {
320
407
  return lc;
321
408
  }
322
409
  export class LocalIntervalCollection {
323
- constructor(client, label, helpers) {
410
+ constructor(client, label, helpers,
411
+ /** Callback invoked each time one of the endpoints of an interval slides. */
412
+ onPositionChange) {
324
413
  this.client = client;
325
414
  this.label = label;
326
415
  this.helpers = helpers;
416
+ this.onPositionChange = onPositionChange;
327
417
  this.intervalTree = new IntervalTree();
418
+ this.intervalIdMap = new Map();
328
419
  // eslint-disable-next-line @typescript-eslint/unbound-method
329
420
  this.endIntervalTree = new RedBlackTree(helpers.compareEnds);
330
421
  }
@@ -332,7 +423,7 @@ export class LocalIntervalCollection {
332
423
  this.conflictResolver = conflictResolver;
333
424
  this.endConflictResolver =
334
425
  (key, currentKey) => {
335
- const ival = this.conflictResolver(key, currentKey);
426
+ const ival = conflictResolver(key, currentKey);
336
427
  return {
337
428
  data: ival,
338
429
  key: ival,
@@ -347,13 +438,22 @@ export class LocalIntervalCollection {
347
438
  // without ID's.
348
439
  return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
349
440
  }
441
+ /**
442
+ * Validates that a serialized interval has the ID property. Creates an ID
443
+ * if one does not already exist
444
+ *
445
+ * @param serializedInterval - The interval to be checked
446
+ * @returns The interval's existing or newly created id
447
+ */
350
448
  ensureSerializedId(serializedInterval) {
351
449
  var _a;
352
- if (((_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey]) === undefined) {
450
+ let id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
451
+ if (id === undefined) {
353
452
  // An interval came over the wire without an ID, so create a non-unique one based on start/end.
354
453
  // This will allow all clients to refer to this interval consistently.
454
+ id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
355
455
  const newProps = {
356
- [reservedIntervalIdKey]: this.createLegacyId(serializedInterval.start, serializedInterval.end),
456
+ [reservedIntervalIdKey]: id,
357
457
  };
358
458
  serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
359
459
  }
@@ -363,6 +463,7 @@ export class LocalIntervalCollection {
363
463
  enumerable: true,
364
464
  writable: false,
365
465
  });
466
+ return id;
366
467
  }
367
468
  mapUntil(fn) {
368
469
  this.intervalTree.mapUntil(fn);
@@ -456,9 +557,16 @@ export class LocalIntervalCollection {
456
557
  this.endIntervalTree.remove(transientInterval);
457
558
  return transientInterval;
458
559
  }
459
- removeExistingInterval(interval) {
560
+ removeIntervalFromIndex(interval) {
460
561
  this.intervalTree.removeExisting(interval);
461
562
  this.endIntervalTree.remove(interval);
563
+ const id = interval.getIntervalId();
564
+ assert(id !== undefined, 0x311 /* expected id to exist on interval */);
565
+ this.intervalIdMap.delete(id);
566
+ }
567
+ removeExistingInterval(interval) {
568
+ this.removeIntervalFromIndex(interval);
569
+ this.removeIntervalListeners(interval);
462
570
  }
463
571
  createInterval(start, end, intervalType, op) {
464
572
  return this.helpers.create(this.label, start, end, this.client, intervalType, op);
@@ -480,8 +588,9 @@ export class LocalIntervalCollection {
480
588
  }
481
589
  return interval;
482
590
  }
483
- add(interval) {
484
- assert(Object.prototype.hasOwnProperty.call(interval.properties, reservedIntervalIdKey), 0x2c0 /* "ID must be created before adding interval to collection" */);
591
+ addIntervalToIndex(interval) {
592
+ const id = interval.getIntervalId();
593
+ assert(id !== undefined, 0x2c0 /* "ID must be created before adding interval to collection" */);
485
594
  // Make the ID immutable.
486
595
  Object.defineProperty(interval.properties, reservedIntervalIdKey, {
487
596
  configurable: false,
@@ -490,17 +599,14 @@ export class LocalIntervalCollection {
490
599
  });
491
600
  this.intervalTree.put(interval, this.conflictResolver);
492
601
  this.endIntervalTree.put(interval, interval, this.endConflictResolver);
602
+ this.intervalIdMap.set(id, interval);
603
+ }
604
+ add(interval) {
605
+ this.addIntervalToIndex(interval);
606
+ this.addIntervalListeners(interval);
493
607
  }
494
608
  getIntervalById(id) {
495
- let result;
496
- this.mapUntil((interval) => {
497
- if (interval.getIntervalId() === id) {
498
- result = interval;
499
- return false;
500
- }
501
- return true;
502
- });
503
- return result;
609
+ return this.intervalIdMap.get(id);
504
610
  }
505
611
  changeInterval(interval, start, end, op) {
506
612
  const newInterval = interval.modify(this.label, start, end, op);
@@ -513,7 +619,25 @@ export class LocalIntervalCollection {
513
619
  serialize() {
514
620
  const client = this.client;
515
621
  const intervals = this.intervalTree.intervals.keys();
516
- return intervals.map((interval) => interval.serialize(client));
622
+ return {
623
+ label: this.label,
624
+ intervals: intervals.map((interval) => compressInterval(interval.serialize(client))),
625
+ version: 2,
626
+ };
627
+ }
628
+ addIntervalListeners(interval) {
629
+ if (interval instanceof SequenceInterval) {
630
+ interval.addPositionChangeListeners(() => this.removeIntervalFromIndex(interval), () => {
631
+ var _a;
632
+ this.addIntervalToIndex(interval);
633
+ (_a = this.onPositionChange) === null || _a === void 0 ? void 0 : _a.call(this, interval);
634
+ });
635
+ }
636
+ }
637
+ removeIntervalListeners(interval) {
638
+ if (interval instanceof SequenceInterval) {
639
+ interval.removePositionChangeListeners();
640
+ }
517
641
  }
518
642
  }
519
643
  LocalIntervalCollection.legacyIdPrefix = "legacy";
@@ -543,37 +667,12 @@ export class SequenceIntervalCollectionValueType {
543
667
  }
544
668
  SequenceIntervalCollectionValueType.Name = "sharedStringIntervalCollection";
545
669
  SequenceIntervalCollectionValueType._factory = new SequenceIntervalCollectionFactory();
546
- SequenceIntervalCollectionValueType._ops = new Map([[
547
- "add",
548
- {
549
- process: (value, params, local, op) => {
550
- value.ackAdd(params, local, op);
551
- },
552
- },
553
- ],
554
- [
555
- "delete",
556
- {
557
- process: (value, params, local, op) => {
558
- value.ackDelete(params, local, op);
559
- },
560
- },
561
- ],
562
- [
563
- "change",
564
- {
565
- process: (value, params, local, op) => {
566
- value.ackChange(params, local, op);
567
- },
568
- },
569
- ]]);
670
+ SequenceIntervalCollectionValueType._ops = makeOpsMap();
570
671
  const compareIntervalEnds = (a, b) => a.end - b.end;
571
672
  function createInterval(label, start, end, client) {
572
- let rangeProp;
573
- if (label && (label.length > 0)) {
574
- rangeProp = {
575
- [reservedRangeLabelsKey]: [label],
576
- };
673
+ const rangeProp = {};
674
+ if (label && label.length > 0) {
675
+ rangeProp[reservedRangeLabelsKey] = [label];
577
676
  }
578
677
  return new Interval(start, end, rangeProp);
579
678
  }
@@ -604,30 +703,45 @@ export class IntervalCollectionValueType {
604
703
  }
605
704
  IntervalCollectionValueType.Name = "sharedIntervalCollection";
606
705
  IntervalCollectionValueType._factory = new IntervalCollectionFactory();
607
- IntervalCollectionValueType._ops = new Map([[
608
- "add",
609
- {
610
- process: (value, params, local, op) => {
611
- value.ackAdd(params, local, op);
706
+ IntervalCollectionValueType._ops = makeOpsMap();
707
+ function makeOpsMap() {
708
+ const rebase = (collection, op, localOpMetadata) => {
709
+ const { localSeq } = localOpMetadata;
710
+ const rebasedValue = collection.rebaseLocalInterval(op.opName, op.value, localSeq);
711
+ const rebasedOp = Object.assign(Object.assign({}, op), { value: rebasedValue });
712
+ return { rebasedOp, rebasedLocalOpMetadata: localOpMetadata };
713
+ };
714
+ return new Map([[
715
+ "add",
716
+ {
717
+ process: (collection, params, local, op) => {
718
+ collection.ackAdd(params, local, op);
719
+ },
720
+ rebase,
612
721
  },
613
- },
614
- ],
615
- [
616
- "delete",
617
- {
618
- process: (value, params, local, op) => {
619
- value.ackDelete(params, local, op);
722
+ ],
723
+ [
724
+ "delete",
725
+ {
726
+ process: (collection, params, local, op) => {
727
+ collection.ackDelete(params, local, op);
728
+ },
729
+ rebase: (collection, op, localOpMetadata) => {
730
+ // Deletion of intervals is based on id, so requires no rebasing.
731
+ return { rebasedOp: op, rebasedLocalOpMetadata: localOpMetadata };
732
+ },
620
733
  },
621
- },
622
- ],
623
- [
624
- "change",
625
- {
626
- process: (value, params, local, op) => {
627
- value.ackChange(params, local, op);
734
+ ],
735
+ [
736
+ "change",
737
+ {
738
+ process: (collection, params, local, op) => {
739
+ collection.ackChange(params, local, op);
740
+ },
741
+ rebase,
628
742
  },
629
- },
630
- ]]);
743
+ ]]);
744
+ }
631
745
  export class IntervalCollectionIterator {
632
746
  constructor(collection, iteratesForward = true, start, end) {
633
747
  this.results = [];
@@ -648,26 +762,35 @@ export class IntervalCollectionIterator {
648
762
  }
649
763
  }
650
764
  export class IntervalCollection extends TypedEventEmitter {
765
+ /** @internal */
651
766
  constructor(helpers, requiresClient, emitter, serializedIntervals) {
652
767
  super();
653
768
  this.helpers = helpers;
654
769
  this.requiresClient = requiresClient;
655
770
  this.emitter = emitter;
656
- this.savedSerializedIntervals = serializedIntervals;
771
+ this.pendingChangesStart = new Map();
772
+ this.pendingChangesEnd = new Map();
773
+ if (Array.isArray(serializedIntervals)) {
774
+ this.savedSerializedIntervals = serializedIntervals;
775
+ }
776
+ else {
777
+ this.savedSerializedIntervals =
778
+ serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
779
+ }
657
780
  }
658
781
  get attached() {
659
782
  return !!this.localCollection;
660
783
  }
661
784
  attachGraph(client, label) {
662
785
  if (this.attached) {
663
- throw new Error("Only supports one Sequence attach");
786
+ throw new LoggingError("Only supports one Sequence attach");
664
787
  }
665
788
  if ((client === undefined) && (this.requiresClient)) {
666
- throw new Error("Client required for this collection");
789
+ throw new LoggingError("Client required for this collection");
667
790
  }
668
791
  // Instantiate the local interval collection based on the saved intervals
669
792
  this.client = client;
670
- this.localCollection = new LocalIntervalCollection(client, label, this.helpers);
793
+ this.localCollection = new LocalIntervalCollection(client, label, this.helpers, (interval) => this.emit("changeInterval", interval, true, undefined));
671
794
  if (this.savedSerializedIntervals) {
672
795
  for (const serializedInterval of this.savedSerializedIntervals) {
673
796
  this.localCollection.ensureSerializedId(serializedInterval);
@@ -676,9 +799,15 @@ export class IntervalCollection extends TypedEventEmitter {
676
799
  }
677
800
  this.savedSerializedIntervals = undefined;
678
801
  }
802
+ /**
803
+ * Gets the next local sequence number, modifying this client's collab window in doing so.
804
+ */
805
+ getNextLocalSeq() {
806
+ return ++this.client.getCollabWindow().localSeq;
807
+ }
679
808
  getIntervalById(id) {
680
809
  if (!this.attached) {
681
- throw new Error("attach must be called before accessing intervals");
810
+ throw new LoggingError("attach must be called before accessing intervals");
682
811
  }
683
812
  return this.localCollection.getIntervalById(id);
684
813
  }
@@ -693,10 +822,10 @@ export class IntervalCollection extends TypedEventEmitter {
693
822
  add(start, end, intervalType, props) {
694
823
  var _a, _b;
695
824
  if (!this.attached) {
696
- throw new Error("attach must be called prior to adding intervals");
825
+ throw new LoggingError("attach must be called prior to adding intervals");
697
826
  }
698
827
  if (intervalType & IntervalType.Transient) {
699
- throw new Error("Can not add transient intervals");
828
+ throw new LoggingError("Can not add transient intervals");
700
829
  }
701
830
  const interval = this.localCollection.addInterval(start, end, intervalType, props);
702
831
  if (interval) {
@@ -708,7 +837,7 @@ export class IntervalCollection extends TypedEventEmitter {
708
837
  start,
709
838
  };
710
839
  // Local ops get submitted to the server. Remote ops have the deserializer run.
711
- this.emitter.emit("add", undefined, serializedInterval);
840
+ this.emitter.emit("add", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
712
841
  }
713
842
  this.emit("addInterval", interval, true, undefined);
714
843
  return interval;
@@ -719,7 +848,7 @@ export class IntervalCollection extends TypedEventEmitter {
719
848
  if (interval) {
720
849
  // Local ops get submitted to the server. Remote ops have the deserializer run.
721
850
  if (local) {
722
- this.emitter.emit("delete", undefined, interval.serialize(this.client));
851
+ this.emitter.emit("delete", undefined, interval.serialize(this.client), { localSeq: this.getNextLocalSeq() });
723
852
  }
724
853
  else {
725
854
  if (this.onDeserialize) {
@@ -738,40 +867,41 @@ export class IntervalCollection extends TypedEventEmitter {
738
867
  }
739
868
  changeProperties(id, props) {
740
869
  if (!this.attached) {
741
- throw new Error("Attach must be called before accessing intervals");
870
+ throw new LoggingError("Attach must be called before accessing intervals");
742
871
  }
743
872
  if (typeof (id) !== "string") {
744
- throw new Error("Change API requires an ID that is a string");
873
+ throw new LoggingError("Change API requires an ID that is a string");
745
874
  }
746
875
  if (!props) {
747
- throw new Error("changeProperties should be called with a property set");
876
+ throw new LoggingError("changeProperties should be called with a property set");
748
877
  }
749
878
  const interval = this.getIntervalById(id);
750
879
  if (interval) {
751
880
  // Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
752
881
  const deltaProps = interval.addProperties(props, true, UnassignedSequenceNumber);
753
882
  const serializedInterval = interval.serialize(this.client);
754
- // Emit a change op that will only change properties. Add the ID to the property bag provided by the caller.
883
+ // Emit a change op that will only change properties. Add the ID to
884
+ // the property bag provided by the caller.
755
885
  serializedInterval.start = undefined;
756
886
  serializedInterval.end = undefined;
757
887
  serializedInterval.properties = props;
758
888
  serializedInterval.properties[reservedIntervalIdKey] = interval.getIntervalId();
759
- this.emitter.emit("change", undefined, serializedInterval);
889
+ this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
760
890
  this.emit("propertyChanged", interval, deltaProps);
761
891
  }
762
892
  this.emit("changeInterval", interval, true, undefined);
763
893
  }
764
894
  change(id, start, end) {
765
895
  if (!this.attached) {
766
- throw new Error("Attach must be called before accessing intervals");
896
+ throw new LoggingError("Attach must be called before accessing intervals");
767
897
  }
898
+ // Force id to be a string.
768
899
  if (typeof (id) !== "string") {
769
- throw new Error("Change API requires an ID that is a string");
900
+ throw new LoggingError("Change API requires an ID that is a string");
770
901
  }
771
- // Force id to be a string.
772
902
  const interval = this.getIntervalById(id);
773
903
  if (interval) {
774
- this.localCollection.changeInterval(interval, start, end);
904
+ const newInterval = this.localCollection.changeInterval(interval, start, end);
775
905
  const serializedInterval = interval.serialize(this.client);
776
906
  serializedInterval.start = start;
777
907
  serializedInterval.end = end;
@@ -780,23 +910,19 @@ export class IntervalCollection extends TypedEventEmitter {
780
910
  {
781
911
  [reservedIntervalIdKey]: interval.getIntervalId(),
782
912
  };
783
- this.emitter.emit("change", undefined, serializedInterval);
913
+ this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
784
914
  this.addPendingChange(id, serializedInterval);
915
+ this.emit("changeInterval", newInterval, true, undefined);
916
+ return newInterval;
785
917
  }
786
- this.emit("changeInterval", interval, true, undefined);
787
- return interval;
918
+ // No interval to change
919
+ return undefined;
788
920
  }
789
921
  addPendingChange(id, serializedInterval) {
790
922
  if (serializedInterval.start !== undefined) {
791
- if (!this.pendingChangesStart) {
792
- this.pendingChangesStart = new Map();
793
- }
794
923
  this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
795
924
  }
796
925
  if (serializedInterval.end !== undefined) {
797
- if (!this.pendingChangesEnd) {
798
- this.pendingChangesEnd = new Map();
799
- }
800
926
  this.addPendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
801
927
  }
802
928
  }
@@ -809,8 +935,9 @@ export class IntervalCollection extends TypedEventEmitter {
809
935
  entries.push(serializedInterval);
810
936
  }
811
937
  removePendingChange(serializedInterval) {
938
+ var _a;
812
939
  // Change ops always have an ID.
813
- const id = serializedInterval.properties[reservedIntervalIdKey];
940
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
814
941
  if (serializedInterval.start !== undefined) {
815
942
  this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
816
943
  }
@@ -819,26 +946,24 @@ export class IntervalCollection extends TypedEventEmitter {
819
946
  }
820
947
  }
821
948
  removePendingChangeHelper(id, pendingChanges, serializedInterval) {
822
- const entries = pendingChanges === null || pendingChanges === void 0 ? void 0 : pendingChanges.get(id);
949
+ const entries = pendingChanges.get(id);
823
950
  if (entries) {
824
951
  const pendingChange = entries.shift();
825
952
  if (entries.length === 0) {
826
953
  pendingChanges.delete(id);
827
954
  }
828
- if (pendingChange.start !== serializedInterval.start ||
829
- pendingChange.end !== serializedInterval.end) {
830
- throw new Error("Mismatch in pending changes");
955
+ if ((pendingChange === null || pendingChange === void 0 ? void 0 : pendingChange.start) !== serializedInterval.start ||
956
+ (pendingChange === null || pendingChange === void 0 ? void 0 : pendingChange.end) !== serializedInterval.end) {
957
+ throw new LoggingError("Mismatch in pending changes");
831
958
  }
832
959
  }
833
960
  }
834
961
  hasPendingChangeStart(id) {
835
- var _a;
836
- const entries = (_a = this.pendingChangesStart) === null || _a === void 0 ? void 0 : _a.get(id);
962
+ const entries = this.pendingChangesStart.get(id);
837
963
  return entries && entries.length !== 0;
838
964
  }
839
965
  hasPendingChangeEnd(id) {
840
- var _a;
841
- const entries = (_a = this.pendingChangesEnd) === null || _a === void 0 ? void 0 : _a.get(id);
966
+ const entries = this.pendingChangesEnd.get(id);
842
967
  return entries && entries.length !== 0;
843
968
  }
844
969
  /** @deprecated - use ackChange */
@@ -847,22 +972,21 @@ export class IntervalCollection extends TypedEventEmitter {
847
972
  }
848
973
  /** @internal */
849
974
  ackChange(serializedInterval, local, op) {
850
- var _a, _b;
975
+ var _a, _b, _c, _d;
851
976
  if (!this.attached) {
852
- throw new Error("Attach must be called before accessing intervals");
977
+ throw new LoggingError("Attach must be called before accessing intervals");
853
978
  }
854
979
  let interval;
855
980
  if (local) {
856
981
  // This is an ack from the server. Remove the pending change.
857
982
  this.removePendingChange(serializedInterval);
858
- const id = serializedInterval.properties[reservedIntervalIdKey];
859
- // Could store the interval in the localOpMetadata to avoid the getIntervalById call
983
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
860
984
  interval = this.getIntervalById(id);
861
985
  if (interval) {
862
986
  // Let the propertyManager prune its pending change-properties set.
863
- (_a = interval.propertyManager) === null || _a === void 0 ? void 0 : _a.ackPendingProperties({
987
+ (_b = interval.propertyManager) === null || _b === void 0 ? void 0 : _b.ackPendingProperties({
864
988
  type: MergeTreeDeltaType.ANNOTATE,
865
- props: serializedInterval.properties,
989
+ props: (_c = serializedInterval.properties) !== null && _c !== void 0 ? _c : {},
866
990
  });
867
991
  this.ackInterval(interval, op);
868
992
  }
@@ -873,7 +997,7 @@ export class IntervalCollection extends TypedEventEmitter {
873
997
  // Note that the ID is in the property bag only to allow us to find the interval.
874
998
  // This API cannot change the ID, and writing to the ID property will result in an exception. So we
875
999
  // strip it out of the properties here.
876
- const _c = serializedInterval.properties, _d = reservedIntervalIdKey, id = _c[_d], newProps = __rest(_c, [typeof _d === "symbol" ? _d : _d + ""]);
1000
+ const _e = serializedInterval.properties, _f = reservedIntervalIdKey, id = _e[_f], newProps = __rest(_e, [typeof _f === "symbol" ? _f : _f + ""]);
877
1001
  interval = this.getIntervalById(id);
878
1002
  if (interval) {
879
1003
  let start;
@@ -888,7 +1012,7 @@ export class IntervalCollection extends TypedEventEmitter {
888
1012
  if (start !== undefined || end !== undefined) {
889
1013
  // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
890
1014
  // the one we originally found in the tree.
891
- interval = (_b = this.localCollection.changeInterval(interval, start, end, op)) !== null && _b !== void 0 ? _b : interval;
1015
+ interval = (_d = this.localCollection.changeInterval(interval, start, end, op)) !== null && _d !== void 0 ? _d : interval;
892
1016
  }
893
1017
  const deltaProps = interval.addProperties(newProps, true, op.sequenceNumber);
894
1018
  if (this.onDeserialize) {
@@ -903,7 +1027,7 @@ export class IntervalCollection extends TypedEventEmitter {
903
1027
  }
904
1028
  addConflictResolver(conflictResolver) {
905
1029
  if (!this.attached) {
906
- throw new Error("attachSequence must be called");
1030
+ throw new LoggingError("attachSequence must be called");
907
1031
  }
908
1032
  this.localCollection.addConflictResolver(conflictResolver);
909
1033
  }
@@ -916,13 +1040,38 @@ export class IntervalCollection extends TypedEventEmitter {
916
1040
  this.onDeserialize = onDeserialize;
917
1041
  // Trigger the async prepare work across all values in the collection
918
1042
  this.localCollection.map((interval) => {
919
- this.onDeserialize(interval);
1043
+ onDeserialize(interval);
920
1044
  });
921
1045
  }
1046
+ /** @internal */
1047
+ rebaseLocalInterval(opName, serializedInterval, localSeq) {
1048
+ var _a, _b;
1049
+ if (!this.attached) {
1050
+ throw new LoggingError("attachSequence must be called");
1051
+ }
1052
+ const { start, end, intervalType, properties, sequenceNumber } = serializedInterval;
1053
+ const startRebased = start === undefined ? undefined :
1054
+ this.client.rebasePosition(start, sequenceNumber, localSeq);
1055
+ const endRebased = end === undefined ? undefined :
1056
+ this.client.rebasePosition(end, sequenceNumber, localSeq);
1057
+ const intervalId = properties === null || properties === void 0 ? void 0 : properties[reservedIntervalIdKey];
1058
+ const rebased = {
1059
+ start: startRebased,
1060
+ end: endRebased,
1061
+ intervalType,
1062
+ sequenceNumber: (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.getCurrentSeq()) !== null && _b !== void 0 ? _b : 0,
1063
+ properties,
1064
+ };
1065
+ if (opName === "change" && (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
1066
+ this.removePendingChange(serializedInterval);
1067
+ this.addPendingChange(intervalId, rebased);
1068
+ }
1069
+ return rebased;
1070
+ }
922
1071
  getSlideToSegment(lref) {
923
1072
  const segoff = { segment: lref.segment, offset: lref.offset };
924
1073
  const newSegoff = this.client.getSlideToSegment(segoff);
925
- const value = (segoff === newSegoff) ? undefined : newSegoff;
1074
+ const value = (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
926
1075
  return value;
927
1076
  }
928
1077
  setSlideOnRemove(lref) {
@@ -936,25 +1085,43 @@ export class IntervalCollection extends TypedEventEmitter {
936
1085
  if (!(interval instanceof SequenceInterval)) {
937
1086
  return;
938
1087
  }
939
- if (!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove)) {
1088
+ if (!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove) &&
1089
+ !refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove)) {
940
1090
  return;
941
1091
  }
942
- assert(refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove), 0x2f7 /* start and end must both be StayOnRemove */);
943
1092
  const newStart = this.getSlideToSegment(interval.start);
944
1093
  const newEnd = this.getSlideToSegment(interval.end);
945
- this.setSlideOnRemove(interval.start);
946
- this.setSlideOnRemove(interval.end);
947
- if (newStart || newEnd) {
1094
+ const id = interval.properties[reservedIntervalIdKey];
1095
+ const hasPendingStartChange = this.hasPendingChangeStart(id);
1096
+ const hasPendingEndChange = this.hasPendingChangeEnd(id);
1097
+ if (!hasPendingStartChange) {
1098
+ this.setSlideOnRemove(interval.start);
1099
+ }
1100
+ if (!hasPendingEndChange) {
1101
+ this.setSlideOnRemove(interval.end);
1102
+ }
1103
+ const needsStartUpdate = newStart !== undefined && !hasPendingStartChange;
1104
+ const needsEndUpdate = newEnd !== undefined && !hasPendingEndChange;
1105
+ if (needsStartUpdate || needsEndUpdate) {
1106
+ // In this case, where we change the start or end of an interval,
1107
+ // it is necessary to remove and re-add the interval listeners.
1108
+ // This ensures that the correct listeners are added to the ReferencePosition.
948
1109
  this.localCollection.removeExistingInterval(interval);
949
- if (newStart) {
1110
+ if (needsStartUpdate) {
950
1111
  const props = interval.start.properties;
1112
+ this.client.removeLocalReferencePosition(interval.start);
951
1113
  interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
952
- interval.start.addProperties(props);
1114
+ if (props) {
1115
+ interval.start.addProperties(props);
1116
+ }
953
1117
  }
954
- if (newEnd) {
1118
+ if (needsEndUpdate) {
955
1119
  const props = interval.end.properties;
1120
+ this.client.removeLocalReferencePosition(interval.end);
956
1121
  interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
957
- interval.end.addProperties(props);
1122
+ if (props) {
1123
+ interval.end.addProperties(props);
1124
+ }
958
1125
  }
959
1126
  this.localCollection.add(interval);
960
1127
  }
@@ -965,9 +1132,9 @@ export class IntervalCollection extends TypedEventEmitter {
965
1132
  }
966
1133
  /** @internal */
967
1134
  ackAdd(serializedInterval, local, op) {
1135
+ var _a;
968
1136
  if (local) {
969
- const id = serializedInterval.properties[reservedIntervalIdKey];
970
- // Could store the interval in the localOpMetadata to avoid the getIntervalById call
1137
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
971
1138
  const localInterval = this.getIntervalById(id);
972
1139
  if (localInterval) {
973
1140
  this.ackInterval(localInterval, op);
@@ -975,7 +1142,7 @@ export class IntervalCollection extends TypedEventEmitter {
975
1142
  return;
976
1143
  }
977
1144
  if (!this.attached) {
978
- throw new Error("attachSequence must be called");
1145
+ throw new LoggingError("attachSequence must be called");
979
1146
  }
980
1147
  this.localCollection.ensureSerializedId(serializedInterval);
981
1148
  const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op);
@@ -1000,17 +1167,20 @@ export class IntervalCollection extends TypedEventEmitter {
1000
1167
  return;
1001
1168
  }
1002
1169
  if (!this.attached) {
1003
- throw new Error("attach must be called prior to deleting intervals");
1170
+ throw new LoggingError("attach must be called prior to deleting intervals");
1004
1171
  }
1005
- this.localCollection.ensureSerializedId(serializedInterval);
1006
- const interval = this.localCollection.getIntervalById(serializedInterval.properties[reservedIntervalIdKey]);
1172
+ const id = this.localCollection.ensureSerializedId(serializedInterval);
1173
+ const interval = this.localCollection.getIntervalById(id);
1007
1174
  if (interval) {
1008
1175
  this.deleteExistingInterval(interval, local, op);
1009
1176
  }
1010
1177
  }
1178
+ /**
1179
+ * @internal
1180
+ */
1011
1181
  serializeInternal() {
1012
1182
  if (!this.attached) {
1013
- throw new Error("attachSequence must be called");
1183
+ throw new LoggingError("attachSequence must be called");
1014
1184
  }
1015
1185
  return this.localCollection.serialize();
1016
1186
  }
@@ -1042,25 +1212,25 @@ export class IntervalCollection extends TypedEventEmitter {
1042
1212
  }
1043
1213
  findOverlappingIntervals(startPosition, endPosition) {
1044
1214
  if (!this.attached) {
1045
- throw new Error("attachSequence must be called");
1215
+ throw new LoggingError("attachSequence must be called");
1046
1216
  }
1047
1217
  return this.localCollection.findOverlappingIntervals(startPosition, endPosition);
1048
1218
  }
1049
1219
  map(fn) {
1050
1220
  if (!this.attached) {
1051
- throw new Error("attachSequence must be called");
1221
+ throw new LoggingError("attachSequence must be called");
1052
1222
  }
1053
1223
  this.localCollection.map(fn);
1054
1224
  }
1055
1225
  previousInterval(pos) {
1056
1226
  if (!this.attached) {
1057
- throw new Error("attachSequence must be called");
1227
+ throw new LoggingError("attachSequence must be called");
1058
1228
  }
1059
1229
  return this.localCollection.previousInterval(pos);
1060
1230
  }
1061
1231
  nextInterval(pos) {
1062
1232
  if (!this.attached) {
1063
- throw new Error("attachSequence must be called");
1233
+ throw new LoggingError("attachSequence must be called");
1064
1234
  }
1065
1235
  return this.localCollection.nextInterval(pos);
1066
1236
  }