@fluidframework/sequence 1.0.0 → 1.1.0-76254

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 (67) 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.d.ts.map +1 -1
  18. package/dist/packageVersion.js +1 -1
  19. package/dist/packageVersion.js.map +1 -1
  20. package/dist/sequence.d.ts +4 -5
  21. package/dist/sequence.d.ts.map +1 -1
  22. package/dist/sequence.js +11 -15
  23. package/dist/sequence.js.map +1 -1
  24. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  25. package/dist/sharedIntervalCollection.js +1 -1
  26. package/dist/sharedIntervalCollection.js.map +1 -1
  27. package/dist/sharedSequence.js.map +1 -1
  28. package/dist/sparsematrix.js +2 -2
  29. package/dist/sparsematrix.js.map +1 -1
  30. package/lib/defaultMap.d.ts +2 -6
  31. package/lib/defaultMap.d.ts.map +1 -1
  32. package/lib/defaultMap.js +27 -37
  33. package/lib/defaultMap.js.map +1 -1
  34. package/lib/defaultMapInterfaces.d.ts +24 -3
  35. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  36. package/lib/defaultMapInterfaces.js.map +1 -1
  37. package/lib/index.d.ts +2 -2
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js.map +1 -1
  40. package/lib/intervalCollection.d.ts +72 -8
  41. package/lib/intervalCollection.d.ts.map +1 -1
  42. package/lib/intervalCollection.js +325 -155
  43. package/lib/intervalCollection.js.map +1 -1
  44. package/lib/packageVersion.d.ts +1 -1
  45. package/lib/packageVersion.d.ts.map +1 -1
  46. package/lib/packageVersion.js +1 -1
  47. package/lib/packageVersion.js.map +1 -1
  48. package/lib/sequence.d.ts +4 -5
  49. package/lib/sequence.d.ts.map +1 -1
  50. package/lib/sequence.js +11 -15
  51. package/lib/sequence.js.map +1 -1
  52. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  53. package/lib/sharedIntervalCollection.js +1 -1
  54. package/lib/sharedIntervalCollection.js.map +1 -1
  55. package/lib/sharedSequence.js.map +1 -1
  56. package/lib/sparsematrix.js +2 -2
  57. package/lib/sparsematrix.js.map +1 -1
  58. package/package.json +20 -44
  59. package/src/defaultMap.ts +39 -41
  60. package/src/defaultMapInterfaces.ts +28 -3
  61. package/src/index.ts +3 -0
  62. package/src/intervalCollection.ts +447 -181
  63. package/src/packageVersion.ts +1 -1
  64. package/src/sequence.ts +17 -21
  65. package/src/sharedIntervalCollection.ts +3 -2
  66. package/src/sharedSequence.ts +1 -1
  67. package/src/sparsematrix.ts +2 -2
@@ -20,6 +20,7 @@ exports.IntervalCollection = exports.IntervalCollectionIterator = exports.Interv
20
20
  const common_utils_1 = require("@fluidframework/common-utils");
21
21
  const container_utils_1 = require("@fluidframework/container-utils");
22
22
  const merge_tree_1 = require("@fluidframework/merge-tree");
23
+ const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
23
24
  const uuid_1 = require("uuid");
24
25
  const reservedIntervalIdKey = "intervalId";
25
26
  var IntervalType;
@@ -39,10 +40,39 @@ var IntervalType;
39
40
  */
40
41
  IntervalType[IntervalType["Transient"] = 4] = "Transient";
41
42
  })(IntervalType = exports.IntervalType || (exports.IntervalType = {}));
43
+ /**
44
+ * Decompress an interval after loading a summary from JSON. The exact format
45
+ * of this compression is unspecified and subject to change
46
+ */
47
+ function decompressInterval(interval, label) {
48
+ return {
49
+ start: interval[0],
50
+ end: interval[1],
51
+ sequenceNumber: interval[2],
52
+ intervalType: interval[3],
53
+ properties: Object.assign(Object.assign({}, interval[4]), { [merge_tree_1.reservedRangeLabelsKey]: label }),
54
+ };
55
+ }
56
+ /**
57
+ * Compress an interval prior to serialization as JSON. The exact format of this
58
+ * compression is unspecified and subject to change
59
+ */
60
+ function compressInterval(interval) {
61
+ const { start, end, sequenceNumber, intervalType, properties } = interval;
62
+ return [
63
+ start,
64
+ end,
65
+ sequenceNumber,
66
+ intervalType,
67
+ Object.assign(Object.assign({}, properties), { [merge_tree_1.reservedRangeLabelsKey]: undefined }),
68
+ ];
69
+ }
42
70
  class Interval {
43
71
  constructor(start, end, props) {
44
72
  this.start = start;
45
73
  this.end = end;
74
+ this.propertyManager = new merge_tree_1.PropertiesManager();
75
+ this.properties = {};
46
76
  if (props) {
47
77
  this.addProperties(props);
48
78
  }
@@ -125,12 +155,7 @@ class Interval {
125
155
  }
126
156
  addProperties(newProps, collaborating = false, seq, op) {
127
157
  if (newProps) {
128
- if (!this.propertyManager) {
129
- this.propertyManager = new merge_tree_1.PropertiesManager();
130
- }
131
- if (!this.properties) {
132
- this.properties = (0, merge_tree_1.createMap)();
133
- }
158
+ this.initializeProperties();
134
159
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collaborating);
135
160
  }
136
161
  }
@@ -141,7 +166,20 @@ class Interval {
141
166
  // Return undefined to indicate that no change is necessary.
142
167
  return;
143
168
  }
144
- return new Interval(startPos, endPos, this.properties);
169
+ const newInterval = new Interval(startPos, endPos);
170
+ if (this.properties) {
171
+ newInterval.initializeProperties();
172
+ this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
173
+ }
174
+ return newInterval;
175
+ }
176
+ initializeProperties() {
177
+ if (!this.propertyManager) {
178
+ this.propertyManager = new merge_tree_1.PropertiesManager();
179
+ }
180
+ if (!this.properties) {
181
+ this.properties = (0, merge_tree_1.createMap)();
182
+ }
145
183
  }
146
184
  }
147
185
  exports.Interval = Interval;
@@ -150,10 +188,41 @@ class SequenceInterval {
150
188
  this.start = start;
151
189
  this.end = end;
152
190
  this.intervalType = intervalType;
191
+ this.propertyManager = new merge_tree_1.PropertiesManager();
192
+ this.properties = {};
153
193
  if (props) {
154
194
  this.addProperties(props);
155
195
  }
156
196
  }
197
+ /**
198
+ * @internal
199
+ * Subscribes to position change events on this interval if there are no current listeners.
200
+ */
201
+ addPositionChangeListeners(beforePositionChange, afterPositionChange) {
202
+ var _a, _b;
203
+ var _c, _d;
204
+ if (this.callbacks === undefined) {
205
+ this.callbacks = {
206
+ beforePositionChange,
207
+ afterPositionChange,
208
+ };
209
+ const startCbs = (_a = (_c = this.start).callbacks) !== null && _a !== void 0 ? _a : (_c.callbacks = {});
210
+ const endCbs = (_b = (_d = this.end).callbacks) !== null && _b !== void 0 ? _b : (_d.callbacks = {});
211
+ startCbs.beforeSlide = endCbs.beforeSlide = beforePositionChange;
212
+ startCbs.afterSlide = endCbs.afterSlide = afterPositionChange;
213
+ }
214
+ }
215
+ /**
216
+ * @internal
217
+ * Removes the currently subscribed position change listeners.
218
+ */
219
+ removePositionChangeListeners() {
220
+ if (this.callbacks) {
221
+ this.callbacks = undefined;
222
+ this.start.callbacks = undefined;
223
+ this.end.callbacks = undefined;
224
+ }
225
+ }
157
226
  serialize(client) {
158
227
  const startPosition = this.start.toPosition();
159
228
  const endPosition = this.end.toPosition();
@@ -217,12 +286,7 @@ class SequenceInterval {
217
286
  return new SequenceInterval(this.start.min(b.start), this.end.max(b.end), this.intervalType);
218
287
  }
219
288
  addProperties(newProps, collab = false, seq, op) {
220
- if (!this.propertyManager) {
221
- this.propertyManager = new merge_tree_1.PropertiesManager();
222
- }
223
- if (!this.properties) {
224
- this.properties = (0, merge_tree_1.createMap)();
225
- }
289
+ this.initializeProperties();
226
290
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
227
291
  }
228
292
  overlapsPos(bstart, bend) {
@@ -231,18 +295,41 @@ class SequenceInterval {
231
295
  return (endPos > bstart) && (startPos < bend);
232
296
  }
233
297
  modify(label, start, end, op) {
234
- const startPos = start !== null && start !== void 0 ? start : this.start.toPosition();
235
- const endPos = end !== null && end !== void 0 ? end : this.end.toPosition();
236
- if (this.start.toPosition() === startPos && this.end.toPosition() === endPos) {
237
- // Return undefined to indicate that no change is necessary.
238
- return;
239
- }
240
- const newInterval = createSequenceInterval(label, startPos, endPos, this.start.getClient(), this.intervalType, op);
298
+ const getRefType = (baseType) => {
299
+ let refType = baseType;
300
+ if (op === undefined) {
301
+ refType &= ~merge_tree_1.ReferenceType.SlideOnRemove;
302
+ refType |= merge_tree_1.ReferenceType.StayOnRemove;
303
+ }
304
+ return refType;
305
+ };
306
+ let startRef = this.start;
307
+ if (start !== undefined) {
308
+ startRef = createPositionReference(this.start.getClient(), start, getRefType(this.start.refType), op);
309
+ startRef.addProperties(this.start.properties);
310
+ }
311
+ let endRef = this.end;
312
+ if (end !== undefined) {
313
+ endRef = createPositionReference(this.end.getClient(), end, getRefType(this.end.refType), op);
314
+ endRef.addProperties(this.end.properties);
315
+ }
316
+ startRef.pairedRef = endRef;
317
+ endRef.pairedRef = startRef;
318
+ const newInterval = new SequenceInterval(startRef, endRef, this.intervalType);
241
319
  if (this.properties) {
242
- newInterval.addProperties(this.properties);
320
+ newInterval.initializeProperties();
321
+ this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
243
322
  }
244
323
  return newInterval;
245
324
  }
325
+ initializeProperties() {
326
+ if (!this.propertyManager) {
327
+ this.propertyManager = new merge_tree_1.PropertiesManager();
328
+ }
329
+ if (!this.properties) {
330
+ this.properties = (0, merge_tree_1.createMap)();
331
+ }
332
+ }
246
333
  }
247
334
  exports.SequenceInterval = SequenceInterval;
248
335
  function createPositionReferenceFromSegoff(client, segoff, refType, op) {
@@ -327,11 +414,15 @@ function createIntervalIndex(conflict) {
327
414
  }
328
415
  exports.createIntervalIndex = createIntervalIndex;
329
416
  class LocalIntervalCollection {
330
- constructor(client, label, helpers) {
417
+ constructor(client, label, helpers,
418
+ /** Callback invoked each time one of the endpoints of an interval slides. */
419
+ onPositionChange) {
331
420
  this.client = client;
332
421
  this.label = label;
333
422
  this.helpers = helpers;
423
+ this.onPositionChange = onPositionChange;
334
424
  this.intervalTree = new merge_tree_1.IntervalTree();
425
+ this.intervalIdMap = new Map();
335
426
  // eslint-disable-next-line @typescript-eslint/unbound-method
336
427
  this.endIntervalTree = new merge_tree_1.RedBlackTree(helpers.compareEnds);
337
428
  }
@@ -339,7 +430,7 @@ class LocalIntervalCollection {
339
430
  this.conflictResolver = conflictResolver;
340
431
  this.endConflictResolver =
341
432
  (key, currentKey) => {
342
- const ival = this.conflictResolver(key, currentKey);
433
+ const ival = conflictResolver(key, currentKey);
343
434
  return {
344
435
  data: ival,
345
436
  key: ival,
@@ -354,13 +445,22 @@ class LocalIntervalCollection {
354
445
  // without ID's.
355
446
  return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
356
447
  }
448
+ /**
449
+ * Validates that a serialized interval has the ID property. Creates an ID
450
+ * if one does not already exist
451
+ *
452
+ * @param serializedInterval - The interval to be checked
453
+ * @returns The interval's existing or newly created id
454
+ */
357
455
  ensureSerializedId(serializedInterval) {
358
456
  var _a;
359
- if (((_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey]) === undefined) {
457
+ let id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
458
+ if (id === undefined) {
360
459
  // An interval came over the wire without an ID, so create a non-unique one based on start/end.
361
460
  // This will allow all clients to refer to this interval consistently.
461
+ id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
362
462
  const newProps = {
363
- [reservedIntervalIdKey]: this.createLegacyId(serializedInterval.start, serializedInterval.end),
463
+ [reservedIntervalIdKey]: id,
364
464
  };
365
465
  serializedInterval.properties = (0, merge_tree_1.addProperties)(serializedInterval.properties, newProps);
366
466
  }
@@ -370,6 +470,7 @@ class LocalIntervalCollection {
370
470
  enumerable: true,
371
471
  writable: false,
372
472
  });
473
+ return id;
373
474
  }
374
475
  mapUntil(fn) {
375
476
  this.intervalTree.mapUntil(fn);
@@ -463,9 +564,16 @@ class LocalIntervalCollection {
463
564
  this.endIntervalTree.remove(transientInterval);
464
565
  return transientInterval;
465
566
  }
466
- removeExistingInterval(interval) {
567
+ removeIntervalFromIndex(interval) {
467
568
  this.intervalTree.removeExisting(interval);
468
569
  this.endIntervalTree.remove(interval);
570
+ const id = interval.getIntervalId();
571
+ (0, common_utils_1.assert)(id !== undefined, 0x311 /* expected id to exist on interval */);
572
+ this.intervalIdMap.delete(id);
573
+ }
574
+ removeExistingInterval(interval) {
575
+ this.removeIntervalFromIndex(interval);
576
+ this.removeIntervalListeners(interval);
469
577
  }
470
578
  createInterval(start, end, intervalType, op) {
471
579
  return this.helpers.create(this.label, start, end, this.client, intervalType, op);
@@ -487,8 +595,9 @@ class LocalIntervalCollection {
487
595
  }
488
596
  return interval;
489
597
  }
490
- add(interval) {
491
- (0, common_utils_1.assert)(Object.prototype.hasOwnProperty.call(interval.properties, reservedIntervalIdKey), 0x2c0 /* "ID must be created before adding interval to collection" */);
598
+ addIntervalToIndex(interval) {
599
+ const id = interval.getIntervalId();
600
+ (0, common_utils_1.assert)(id !== undefined, 0x2c0 /* "ID must be created before adding interval to collection" */);
492
601
  // Make the ID immutable.
493
602
  Object.defineProperty(interval.properties, reservedIntervalIdKey, {
494
603
  configurable: false,
@@ -497,17 +606,14 @@ class LocalIntervalCollection {
497
606
  });
498
607
  this.intervalTree.put(interval, this.conflictResolver);
499
608
  this.endIntervalTree.put(interval, interval, this.endConflictResolver);
609
+ this.intervalIdMap.set(id, interval);
610
+ }
611
+ add(interval) {
612
+ this.addIntervalToIndex(interval);
613
+ this.addIntervalListeners(interval);
500
614
  }
501
615
  getIntervalById(id) {
502
- let result;
503
- this.mapUntil((interval) => {
504
- if (interval.getIntervalId() === id) {
505
- result = interval;
506
- return false;
507
- }
508
- return true;
509
- });
510
- return result;
616
+ return this.intervalIdMap.get(id);
511
617
  }
512
618
  changeInterval(interval, start, end, op) {
513
619
  const newInterval = interval.modify(this.label, start, end, op);
@@ -520,7 +626,25 @@ class LocalIntervalCollection {
520
626
  serialize() {
521
627
  const client = this.client;
522
628
  const intervals = this.intervalTree.intervals.keys();
523
- return intervals.map((interval) => interval.serialize(client));
629
+ return {
630
+ label: this.label,
631
+ intervals: intervals.map((interval) => compressInterval(interval.serialize(client))),
632
+ version: 2,
633
+ };
634
+ }
635
+ addIntervalListeners(interval) {
636
+ if (interval instanceof SequenceInterval) {
637
+ interval.addPositionChangeListeners(() => this.removeIntervalFromIndex(interval), () => {
638
+ var _a;
639
+ this.addIntervalToIndex(interval);
640
+ (_a = this.onPositionChange) === null || _a === void 0 ? void 0 : _a.call(this, interval);
641
+ });
642
+ }
643
+ }
644
+ removeIntervalListeners(interval) {
645
+ if (interval instanceof SequenceInterval) {
646
+ interval.removePositionChangeListeners();
647
+ }
524
648
  }
525
649
  }
526
650
  exports.LocalIntervalCollection = LocalIntervalCollection;
@@ -552,37 +676,12 @@ class SequenceIntervalCollectionValueType {
552
676
  exports.SequenceIntervalCollectionValueType = SequenceIntervalCollectionValueType;
553
677
  SequenceIntervalCollectionValueType.Name = "sharedStringIntervalCollection";
554
678
  SequenceIntervalCollectionValueType._factory = new SequenceIntervalCollectionFactory();
555
- SequenceIntervalCollectionValueType._ops = new Map([[
556
- "add",
557
- {
558
- process: (value, params, local, op) => {
559
- value.ackAdd(params, local, op);
560
- },
561
- },
562
- ],
563
- [
564
- "delete",
565
- {
566
- process: (value, params, local, op) => {
567
- value.ackDelete(params, local, op);
568
- },
569
- },
570
- ],
571
- [
572
- "change",
573
- {
574
- process: (value, params, local, op) => {
575
- value.ackChange(params, local, op);
576
- },
577
- },
578
- ]]);
679
+ SequenceIntervalCollectionValueType._ops = makeOpsMap();
579
680
  const compareIntervalEnds = (a, b) => a.end - b.end;
580
681
  function createInterval(label, start, end, client) {
581
- let rangeProp;
582
- if (label && (label.length > 0)) {
583
- rangeProp = {
584
- [merge_tree_1.reservedRangeLabelsKey]: [label],
585
- };
682
+ const rangeProp = {};
683
+ if (label && label.length > 0) {
684
+ rangeProp[merge_tree_1.reservedRangeLabelsKey] = [label];
586
685
  }
587
686
  return new Interval(start, end, rangeProp);
588
687
  }
@@ -614,30 +713,45 @@ class IntervalCollectionValueType {
614
713
  exports.IntervalCollectionValueType = IntervalCollectionValueType;
615
714
  IntervalCollectionValueType.Name = "sharedIntervalCollection";
616
715
  IntervalCollectionValueType._factory = new IntervalCollectionFactory();
617
- IntervalCollectionValueType._ops = new Map([[
618
- "add",
619
- {
620
- process: (value, params, local, op) => {
621
- value.ackAdd(params, local, op);
716
+ IntervalCollectionValueType._ops = makeOpsMap();
717
+ function makeOpsMap() {
718
+ const rebase = (collection, op, localOpMetadata) => {
719
+ const { localSeq } = localOpMetadata;
720
+ const rebasedValue = collection.rebaseLocalInterval(op.opName, op.value, localSeq);
721
+ const rebasedOp = Object.assign(Object.assign({}, op), { value: rebasedValue });
722
+ return { rebasedOp, rebasedLocalOpMetadata: localOpMetadata };
723
+ };
724
+ return new Map([[
725
+ "add",
726
+ {
727
+ process: (collection, params, local, op) => {
728
+ collection.ackAdd(params, local, op);
729
+ },
730
+ rebase,
622
731
  },
623
- },
624
- ],
625
- [
626
- "delete",
627
- {
628
- process: (value, params, local, op) => {
629
- value.ackDelete(params, local, op);
732
+ ],
733
+ [
734
+ "delete",
735
+ {
736
+ process: (collection, params, local, op) => {
737
+ collection.ackDelete(params, local, op);
738
+ },
739
+ rebase: (collection, op, localOpMetadata) => {
740
+ // Deletion of intervals is based on id, so requires no rebasing.
741
+ return { rebasedOp: op, rebasedLocalOpMetadata: localOpMetadata };
742
+ },
630
743
  },
631
- },
632
- ],
633
- [
634
- "change",
635
- {
636
- process: (value, params, local, op) => {
637
- value.ackChange(params, local, op);
744
+ ],
745
+ [
746
+ "change",
747
+ {
748
+ process: (collection, params, local, op) => {
749
+ collection.ackChange(params, local, op);
750
+ },
751
+ rebase,
638
752
  },
639
- },
640
- ]]);
753
+ ]]);
754
+ }
641
755
  class IntervalCollectionIterator {
642
756
  constructor(collection, iteratesForward = true, start, end) {
643
757
  this.results = [];
@@ -659,26 +773,35 @@ class IntervalCollectionIterator {
659
773
  }
660
774
  exports.IntervalCollectionIterator = IntervalCollectionIterator;
661
775
  class IntervalCollection extends common_utils_1.TypedEventEmitter {
776
+ /** @internal */
662
777
  constructor(helpers, requiresClient, emitter, serializedIntervals) {
663
778
  super();
664
779
  this.helpers = helpers;
665
780
  this.requiresClient = requiresClient;
666
781
  this.emitter = emitter;
667
- this.savedSerializedIntervals = serializedIntervals;
782
+ this.pendingChangesStart = new Map();
783
+ this.pendingChangesEnd = new Map();
784
+ if (Array.isArray(serializedIntervals)) {
785
+ this.savedSerializedIntervals = serializedIntervals;
786
+ }
787
+ else {
788
+ this.savedSerializedIntervals =
789
+ serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
790
+ }
668
791
  }
669
792
  get attached() {
670
793
  return !!this.localCollection;
671
794
  }
672
795
  attachGraph(client, label) {
673
796
  if (this.attached) {
674
- throw new Error("Only supports one Sequence attach");
797
+ throw new telemetry_utils_1.LoggingError("Only supports one Sequence attach");
675
798
  }
676
799
  if ((client === undefined) && (this.requiresClient)) {
677
- throw new Error("Client required for this collection");
800
+ throw new telemetry_utils_1.LoggingError("Client required for this collection");
678
801
  }
679
802
  // Instantiate the local interval collection based on the saved intervals
680
803
  this.client = client;
681
- this.localCollection = new LocalIntervalCollection(client, label, this.helpers);
804
+ this.localCollection = new LocalIntervalCollection(client, label, this.helpers, (interval) => this.emit("changeInterval", interval, true, undefined));
682
805
  if (this.savedSerializedIntervals) {
683
806
  for (const serializedInterval of this.savedSerializedIntervals) {
684
807
  this.localCollection.ensureSerializedId(serializedInterval);
@@ -687,9 +810,15 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
687
810
  }
688
811
  this.savedSerializedIntervals = undefined;
689
812
  }
813
+ /**
814
+ * Gets the next local sequence number, modifying this client's collab window in doing so.
815
+ */
816
+ getNextLocalSeq() {
817
+ return ++this.client.getCollabWindow().localSeq;
818
+ }
690
819
  getIntervalById(id) {
691
820
  if (!this.attached) {
692
- throw new Error("attach must be called before accessing intervals");
821
+ throw new telemetry_utils_1.LoggingError("attach must be called before accessing intervals");
693
822
  }
694
823
  return this.localCollection.getIntervalById(id);
695
824
  }
@@ -704,10 +833,10 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
704
833
  add(start, end, intervalType, props) {
705
834
  var _a, _b;
706
835
  if (!this.attached) {
707
- throw new Error("attach must be called prior to adding intervals");
836
+ throw new telemetry_utils_1.LoggingError("attach must be called prior to adding intervals");
708
837
  }
709
838
  if (intervalType & IntervalType.Transient) {
710
- throw new Error("Can not add transient intervals");
839
+ throw new telemetry_utils_1.LoggingError("Can not add transient intervals");
711
840
  }
712
841
  const interval = this.localCollection.addInterval(start, end, intervalType, props);
713
842
  if (interval) {
@@ -719,7 +848,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
719
848
  start,
720
849
  };
721
850
  // Local ops get submitted to the server. Remote ops have the deserializer run.
722
- this.emitter.emit("add", undefined, serializedInterval);
851
+ this.emitter.emit("add", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
723
852
  }
724
853
  this.emit("addInterval", interval, true, undefined);
725
854
  return interval;
@@ -730,7 +859,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
730
859
  if (interval) {
731
860
  // Local ops get submitted to the server. Remote ops have the deserializer run.
732
861
  if (local) {
733
- this.emitter.emit("delete", undefined, interval.serialize(this.client));
862
+ this.emitter.emit("delete", undefined, interval.serialize(this.client), { localSeq: this.getNextLocalSeq() });
734
863
  }
735
864
  else {
736
865
  if (this.onDeserialize) {
@@ -749,40 +878,41 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
749
878
  }
750
879
  changeProperties(id, props) {
751
880
  if (!this.attached) {
752
- throw new Error("Attach must be called before accessing intervals");
881
+ throw new telemetry_utils_1.LoggingError("Attach must be called before accessing intervals");
753
882
  }
754
883
  if (typeof (id) !== "string") {
755
- throw new Error("Change API requires an ID that is a string");
884
+ throw new telemetry_utils_1.LoggingError("Change API requires an ID that is a string");
756
885
  }
757
886
  if (!props) {
758
- throw new Error("changeProperties should be called with a property set");
887
+ throw new telemetry_utils_1.LoggingError("changeProperties should be called with a property set");
759
888
  }
760
889
  const interval = this.getIntervalById(id);
761
890
  if (interval) {
762
891
  // Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
763
892
  const deltaProps = interval.addProperties(props, true, merge_tree_1.UnassignedSequenceNumber);
764
893
  const serializedInterval = interval.serialize(this.client);
765
- // Emit a change op that will only change properties. Add the ID to the property bag provided by the caller.
894
+ // Emit a change op that will only change properties. Add the ID to
895
+ // the property bag provided by the caller.
766
896
  serializedInterval.start = undefined;
767
897
  serializedInterval.end = undefined;
768
898
  serializedInterval.properties = props;
769
899
  serializedInterval.properties[reservedIntervalIdKey] = interval.getIntervalId();
770
- this.emitter.emit("change", undefined, serializedInterval);
900
+ this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
771
901
  this.emit("propertyChanged", interval, deltaProps);
772
902
  }
773
903
  this.emit("changeInterval", interval, true, undefined);
774
904
  }
775
905
  change(id, start, end) {
776
906
  if (!this.attached) {
777
- throw new Error("Attach must be called before accessing intervals");
907
+ throw new telemetry_utils_1.LoggingError("Attach must be called before accessing intervals");
778
908
  }
909
+ // Force id to be a string.
779
910
  if (typeof (id) !== "string") {
780
- throw new Error("Change API requires an ID that is a string");
911
+ throw new telemetry_utils_1.LoggingError("Change API requires an ID that is a string");
781
912
  }
782
- // Force id to be a string.
783
913
  const interval = this.getIntervalById(id);
784
914
  if (interval) {
785
- this.localCollection.changeInterval(interval, start, end);
915
+ const newInterval = this.localCollection.changeInterval(interval, start, end);
786
916
  const serializedInterval = interval.serialize(this.client);
787
917
  serializedInterval.start = start;
788
918
  serializedInterval.end = end;
@@ -791,23 +921,19 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
791
921
  {
792
922
  [reservedIntervalIdKey]: interval.getIntervalId(),
793
923
  };
794
- this.emitter.emit("change", undefined, serializedInterval);
924
+ this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
795
925
  this.addPendingChange(id, serializedInterval);
926
+ this.emit("changeInterval", newInterval, true, undefined);
927
+ return newInterval;
796
928
  }
797
- this.emit("changeInterval", interval, true, undefined);
798
- return interval;
929
+ // No interval to change
930
+ return undefined;
799
931
  }
800
932
  addPendingChange(id, serializedInterval) {
801
933
  if (serializedInterval.start !== undefined) {
802
- if (!this.pendingChangesStart) {
803
- this.pendingChangesStart = new Map();
804
- }
805
934
  this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
806
935
  }
807
936
  if (serializedInterval.end !== undefined) {
808
- if (!this.pendingChangesEnd) {
809
- this.pendingChangesEnd = new Map();
810
- }
811
937
  this.addPendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
812
938
  }
813
939
  }
@@ -820,8 +946,9 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
820
946
  entries.push(serializedInterval);
821
947
  }
822
948
  removePendingChange(serializedInterval) {
949
+ var _a;
823
950
  // Change ops always have an ID.
824
- const id = serializedInterval.properties[reservedIntervalIdKey];
951
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
825
952
  if (serializedInterval.start !== undefined) {
826
953
  this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
827
954
  }
@@ -830,26 +957,24 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
830
957
  }
831
958
  }
832
959
  removePendingChangeHelper(id, pendingChanges, serializedInterval) {
833
- const entries = pendingChanges === null || pendingChanges === void 0 ? void 0 : pendingChanges.get(id);
960
+ const entries = pendingChanges.get(id);
834
961
  if (entries) {
835
962
  const pendingChange = entries.shift();
836
963
  if (entries.length === 0) {
837
964
  pendingChanges.delete(id);
838
965
  }
839
- if (pendingChange.start !== serializedInterval.start ||
840
- pendingChange.end !== serializedInterval.end) {
841
- throw new Error("Mismatch in pending changes");
966
+ if ((pendingChange === null || pendingChange === void 0 ? void 0 : pendingChange.start) !== serializedInterval.start ||
967
+ (pendingChange === null || pendingChange === void 0 ? void 0 : pendingChange.end) !== serializedInterval.end) {
968
+ throw new telemetry_utils_1.LoggingError("Mismatch in pending changes");
842
969
  }
843
970
  }
844
971
  }
845
972
  hasPendingChangeStart(id) {
846
- var _a;
847
- const entries = (_a = this.pendingChangesStart) === null || _a === void 0 ? void 0 : _a.get(id);
973
+ const entries = this.pendingChangesStart.get(id);
848
974
  return entries && entries.length !== 0;
849
975
  }
850
976
  hasPendingChangeEnd(id) {
851
- var _a;
852
- const entries = (_a = this.pendingChangesEnd) === null || _a === void 0 ? void 0 : _a.get(id);
977
+ const entries = this.pendingChangesEnd.get(id);
853
978
  return entries && entries.length !== 0;
854
979
  }
855
980
  /** @deprecated - use ackChange */
@@ -858,22 +983,21 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
858
983
  }
859
984
  /** @internal */
860
985
  ackChange(serializedInterval, local, op) {
861
- var _a, _b;
986
+ var _a, _b, _c, _d;
862
987
  if (!this.attached) {
863
- throw new Error("Attach must be called before accessing intervals");
988
+ throw new telemetry_utils_1.LoggingError("Attach must be called before accessing intervals");
864
989
  }
865
990
  let interval;
866
991
  if (local) {
867
992
  // This is an ack from the server. Remove the pending change.
868
993
  this.removePendingChange(serializedInterval);
869
- const id = serializedInterval.properties[reservedIntervalIdKey];
870
- // Could store the interval in the localOpMetadata to avoid the getIntervalById call
994
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
871
995
  interval = this.getIntervalById(id);
872
996
  if (interval) {
873
997
  // Let the propertyManager prune its pending change-properties set.
874
- (_a = interval.propertyManager) === null || _a === void 0 ? void 0 : _a.ackPendingProperties({
998
+ (_b = interval.propertyManager) === null || _b === void 0 ? void 0 : _b.ackPendingProperties({
875
999
  type: merge_tree_1.MergeTreeDeltaType.ANNOTATE,
876
- props: serializedInterval.properties,
1000
+ props: (_c = serializedInterval.properties) !== null && _c !== void 0 ? _c : {},
877
1001
  });
878
1002
  this.ackInterval(interval, op);
879
1003
  }
@@ -884,7 +1008,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
884
1008
  // Note that the ID is in the property bag only to allow us to find the interval.
885
1009
  // This API cannot change the ID, and writing to the ID property will result in an exception. So we
886
1010
  // strip it out of the properties here.
887
- const _c = serializedInterval.properties, _d = reservedIntervalIdKey, id = _c[_d], newProps = __rest(_c, [typeof _d === "symbol" ? _d : _d + ""]);
1011
+ const _e = serializedInterval.properties, _f = reservedIntervalIdKey, id = _e[_f], newProps = __rest(_e, [typeof _f === "symbol" ? _f : _f + ""]);
888
1012
  interval = this.getIntervalById(id);
889
1013
  if (interval) {
890
1014
  let start;
@@ -899,7 +1023,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
899
1023
  if (start !== undefined || end !== undefined) {
900
1024
  // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
901
1025
  // the one we originally found in the tree.
902
- interval = (_b = this.localCollection.changeInterval(interval, start, end, op)) !== null && _b !== void 0 ? _b : interval;
1026
+ interval = (_d = this.localCollection.changeInterval(interval, start, end, op)) !== null && _d !== void 0 ? _d : interval;
903
1027
  }
904
1028
  const deltaProps = interval.addProperties(newProps, true, op.sequenceNumber);
905
1029
  if (this.onDeserialize) {
@@ -914,7 +1038,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
914
1038
  }
915
1039
  addConflictResolver(conflictResolver) {
916
1040
  if (!this.attached) {
917
- throw new Error("attachSequence must be called");
1041
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
918
1042
  }
919
1043
  this.localCollection.addConflictResolver(conflictResolver);
920
1044
  }
@@ -927,13 +1051,38 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
927
1051
  this.onDeserialize = onDeserialize;
928
1052
  // Trigger the async prepare work across all values in the collection
929
1053
  this.localCollection.map((interval) => {
930
- this.onDeserialize(interval);
1054
+ onDeserialize(interval);
931
1055
  });
932
1056
  }
1057
+ /** @internal */
1058
+ rebaseLocalInterval(opName, serializedInterval, localSeq) {
1059
+ var _a, _b;
1060
+ if (!this.attached) {
1061
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
1062
+ }
1063
+ const { start, end, intervalType, properties, sequenceNumber } = serializedInterval;
1064
+ const startRebased = start === undefined ? undefined :
1065
+ this.client.rebasePosition(start, sequenceNumber, localSeq);
1066
+ const endRebased = end === undefined ? undefined :
1067
+ this.client.rebasePosition(end, sequenceNumber, localSeq);
1068
+ const intervalId = properties === null || properties === void 0 ? void 0 : properties[reservedIntervalIdKey];
1069
+ const rebased = {
1070
+ start: startRebased,
1071
+ end: endRebased,
1072
+ intervalType,
1073
+ sequenceNumber: (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.getCurrentSeq()) !== null && _b !== void 0 ? _b : 0,
1074
+ properties,
1075
+ };
1076
+ if (opName === "change" && (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
1077
+ this.removePendingChange(serializedInterval);
1078
+ this.addPendingChange(intervalId, rebased);
1079
+ }
1080
+ return rebased;
1081
+ }
933
1082
  getSlideToSegment(lref) {
934
1083
  const segoff = { segment: lref.segment, offset: lref.offset };
935
1084
  const newSegoff = this.client.getSlideToSegment(segoff);
936
- const value = (segoff === newSegoff) ? undefined : newSegoff;
1085
+ const value = (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
937
1086
  return value;
938
1087
  }
939
1088
  setSlideOnRemove(lref) {
@@ -947,25 +1096,43 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
947
1096
  if (!(interval instanceof SequenceInterval)) {
948
1097
  return;
949
1098
  }
950
- if (!(0, merge_tree_1.refTypeIncludesFlag)(interval.start, merge_tree_1.ReferenceType.StayOnRemove)) {
1099
+ if (!(0, merge_tree_1.refTypeIncludesFlag)(interval.start, merge_tree_1.ReferenceType.StayOnRemove) &&
1100
+ !(0, merge_tree_1.refTypeIncludesFlag)(interval.end, merge_tree_1.ReferenceType.StayOnRemove)) {
951
1101
  return;
952
1102
  }
953
- (0, common_utils_1.assert)((0, merge_tree_1.refTypeIncludesFlag)(interval.end, merge_tree_1.ReferenceType.StayOnRemove), 0x2f7 /* start and end must both be StayOnRemove */);
954
1103
  const newStart = this.getSlideToSegment(interval.start);
955
1104
  const newEnd = this.getSlideToSegment(interval.end);
956
- this.setSlideOnRemove(interval.start);
957
- this.setSlideOnRemove(interval.end);
958
- if (newStart || newEnd) {
1105
+ const id = interval.properties[reservedIntervalIdKey];
1106
+ const hasPendingStartChange = this.hasPendingChangeStart(id);
1107
+ const hasPendingEndChange = this.hasPendingChangeEnd(id);
1108
+ if (!hasPendingStartChange) {
1109
+ this.setSlideOnRemove(interval.start);
1110
+ }
1111
+ if (!hasPendingEndChange) {
1112
+ this.setSlideOnRemove(interval.end);
1113
+ }
1114
+ const needsStartUpdate = newStart !== undefined && !hasPendingStartChange;
1115
+ const needsEndUpdate = newEnd !== undefined && !hasPendingEndChange;
1116
+ if (needsStartUpdate || needsEndUpdate) {
1117
+ // In this case, where we change the start or end of an interval,
1118
+ // it is necessary to remove and re-add the interval listeners.
1119
+ // This ensures that the correct listeners are added to the ReferencePosition.
959
1120
  this.localCollection.removeExistingInterval(interval);
960
- if (newStart) {
1121
+ if (needsStartUpdate) {
961
1122
  const props = interval.start.properties;
1123
+ this.client.removeLocalReferencePosition(interval.start);
962
1124
  interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
963
- interval.start.addProperties(props);
1125
+ if (props) {
1126
+ interval.start.addProperties(props);
1127
+ }
964
1128
  }
965
- if (newEnd) {
1129
+ if (needsEndUpdate) {
966
1130
  const props = interval.end.properties;
1131
+ this.client.removeLocalReferencePosition(interval.end);
967
1132
  interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
968
- interval.end.addProperties(props);
1133
+ if (props) {
1134
+ interval.end.addProperties(props);
1135
+ }
969
1136
  }
970
1137
  this.localCollection.add(interval);
971
1138
  }
@@ -976,9 +1143,9 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
976
1143
  }
977
1144
  /** @internal */
978
1145
  ackAdd(serializedInterval, local, op) {
1146
+ var _a;
979
1147
  if (local) {
980
- const id = serializedInterval.properties[reservedIntervalIdKey];
981
- // Could store the interval in the localOpMetadata to avoid the getIntervalById call
1148
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
982
1149
  const localInterval = this.getIntervalById(id);
983
1150
  if (localInterval) {
984
1151
  this.ackInterval(localInterval, op);
@@ -986,7 +1153,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
986
1153
  return;
987
1154
  }
988
1155
  if (!this.attached) {
989
- throw new Error("attachSequence must be called");
1156
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
990
1157
  }
991
1158
  this.localCollection.ensureSerializedId(serializedInterval);
992
1159
  const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op);
@@ -1011,17 +1178,20 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
1011
1178
  return;
1012
1179
  }
1013
1180
  if (!this.attached) {
1014
- throw new Error("attach must be called prior to deleting intervals");
1181
+ throw new telemetry_utils_1.LoggingError("attach must be called prior to deleting intervals");
1015
1182
  }
1016
- this.localCollection.ensureSerializedId(serializedInterval);
1017
- const interval = this.localCollection.getIntervalById(serializedInterval.properties[reservedIntervalIdKey]);
1183
+ const id = this.localCollection.ensureSerializedId(serializedInterval);
1184
+ const interval = this.localCollection.getIntervalById(id);
1018
1185
  if (interval) {
1019
1186
  this.deleteExistingInterval(interval, local, op);
1020
1187
  }
1021
1188
  }
1189
+ /**
1190
+ * @internal
1191
+ */
1022
1192
  serializeInternal() {
1023
1193
  if (!this.attached) {
1024
- throw new Error("attachSequence must be called");
1194
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
1025
1195
  }
1026
1196
  return this.localCollection.serialize();
1027
1197
  }
@@ -1053,25 +1223,25 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
1053
1223
  }
1054
1224
  findOverlappingIntervals(startPosition, endPosition) {
1055
1225
  if (!this.attached) {
1056
- throw new Error("attachSequence must be called");
1226
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
1057
1227
  }
1058
1228
  return this.localCollection.findOverlappingIntervals(startPosition, endPosition);
1059
1229
  }
1060
1230
  map(fn) {
1061
1231
  if (!this.attached) {
1062
- throw new Error("attachSequence must be called");
1232
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
1063
1233
  }
1064
1234
  this.localCollection.map(fn);
1065
1235
  }
1066
1236
  previousInterval(pos) {
1067
1237
  if (!this.attached) {
1068
- throw new Error("attachSequence must be called");
1238
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
1069
1239
  }
1070
1240
  return this.localCollection.previousInterval(pos);
1071
1241
  }
1072
1242
  nextInterval(pos) {
1073
1243
  if (!this.attached) {
1074
- throw new Error("attachSequence must be called");
1244
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
1075
1245
  }
1076
1246
  return this.localCollection.nextInterval(pos);
1077
1247
  }