@fluidframework/sequence 0.59.4001 → 1.1.0-75972

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 (68) hide show
  1. package/.eslintrc.js +1 -1
  2. package/README.md +18 -6
  3. package/dist/defaultMap.d.ts +2 -6
  4. package/dist/defaultMap.d.ts.map +1 -1
  5. package/dist/defaultMap.js +27 -37
  6. package/dist/defaultMap.js.map +1 -1
  7. package/dist/defaultMapInterfaces.d.ts +24 -3
  8. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  9. package/dist/defaultMapInterfaces.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/intervalCollection.d.ts +93 -8
  14. package/dist/intervalCollection.d.ts.map +1 -1
  15. package/dist/intervalCollection.js +434 -178
  16. package/dist/intervalCollection.js.map +1 -1
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.d.ts.map +1 -1
  19. package/dist/packageVersion.js +1 -1
  20. package/dist/packageVersion.js.map +1 -1
  21. package/dist/sequence.d.ts +6 -7
  22. package/dist/sequence.d.ts.map +1 -1
  23. package/dist/sequence.js +17 -21
  24. package/dist/sequence.js.map +1 -1
  25. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  26. package/dist/sharedIntervalCollection.js +2 -2
  27. package/dist/sharedIntervalCollection.js.map +1 -1
  28. package/dist/sharedSequence.js.map +1 -1
  29. package/dist/sparsematrix.js +2 -2
  30. package/dist/sparsematrix.js.map +1 -1
  31. package/lib/defaultMap.d.ts +2 -6
  32. package/lib/defaultMap.d.ts.map +1 -1
  33. package/lib/defaultMap.js +27 -37
  34. package/lib/defaultMap.js.map +1 -1
  35. package/lib/defaultMapInterfaces.d.ts +24 -3
  36. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  37. package/lib/defaultMapInterfaces.js.map +1 -1
  38. package/lib/index.d.ts +2 -2
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js.map +1 -1
  41. package/lib/intervalCollection.d.ts +93 -8
  42. package/lib/intervalCollection.d.ts.map +1 -1
  43. package/lib/intervalCollection.js +435 -179
  44. package/lib/intervalCollection.js.map +1 -1
  45. package/lib/packageVersion.d.ts +1 -1
  46. package/lib/packageVersion.d.ts.map +1 -1
  47. package/lib/packageVersion.js +1 -1
  48. package/lib/packageVersion.js.map +1 -1
  49. package/lib/sequence.d.ts +6 -7
  50. package/lib/sequence.d.ts.map +1 -1
  51. package/lib/sequence.js +18 -22
  52. package/lib/sequence.js.map +1 -1
  53. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  54. package/lib/sharedIntervalCollection.js +2 -2
  55. package/lib/sharedIntervalCollection.js.map +1 -1
  56. package/lib/sharedSequence.js.map +1 -1
  57. package/lib/sparsematrix.js +2 -2
  58. package/lib/sparsematrix.js.map +1 -1
  59. package/package.json +23 -45
  60. package/src/defaultMap.ts +39 -41
  61. package/src/defaultMapInterfaces.ts +28 -3
  62. package/src/index.ts +3 -0
  63. package/src/intervalCollection.ts +575 -211
  64. package/src/packageVersion.ts +1 -1
  65. package/src/sequence.ts +36 -38
  66. package/src/sharedIntervalCollection.ts +4 -3
  67. package/src/sharedSequence.ts +1 -1
  68. package/src/sparsematrix.ts +2 -2
@@ -18,20 +18,61 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.IntervalCollection = exports.IntervalCollectionIterator = exports.IntervalCollectionValueType = exports.SequenceIntervalCollectionValueType = exports.LocalIntervalCollection = exports.createIntervalIndex = exports.defaultIntervalConflictResolver = exports.SequenceInterval = exports.Interval = exports.IntervalType = void 0;
19
19
  /* eslint-disable no-bitwise */
20
20
  const common_utils_1 = require("@fluidframework/common-utils");
21
+ const container_utils_1 = require("@fluidframework/container-utils");
21
22
  const merge_tree_1 = require("@fluidframework/merge-tree");
23
+ const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
22
24
  const uuid_1 = require("uuid");
23
25
  const reservedIntervalIdKey = "intervalId";
24
26
  var IntervalType;
25
27
  (function (IntervalType) {
26
28
  IntervalType[IntervalType["Simple"] = 0] = "Simple";
27
29
  IntervalType[IntervalType["Nest"] = 1] = "Nest";
30
+ /**
31
+ * SlideOnRemove indicates that the ends of the interval will slide if the segment
32
+ * they reference is removed and acked.
33
+ * See `packages\dds\merge-tree\REFERENCEPOSITIONS.md` for details
34
+ * SlideOnRemove is the default interval behavior and does not need to be specified.
35
+ */
28
36
  IntervalType[IntervalType["SlideOnRemove"] = 2] = "SlideOnRemove";
37
+ /**
38
+ * @internal
39
+ * A temporary interval, used internally
40
+ */
29
41
  IntervalType[IntervalType["Transient"] = 4] = "Transient";
30
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
+ }
31
70
  class Interval {
32
71
  constructor(start, end, props) {
33
72
  this.start = start;
34
73
  this.end = end;
74
+ this.propertyManager = new merge_tree_1.PropertiesManager();
75
+ this.properties = {};
35
76
  if (props) {
36
77
  this.addProperties(props);
37
78
  }
@@ -114,12 +155,7 @@ class Interval {
114
155
  }
115
156
  addProperties(newProps, collaborating = false, seq, op) {
116
157
  if (newProps) {
117
- if (!this.propertyManager) {
118
- this.propertyManager = new merge_tree_1.PropertiesManager();
119
- }
120
- if (!this.properties) {
121
- this.properties = (0, merge_tree_1.createMap)();
122
- }
158
+ this.initializeProperties();
123
159
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collaborating);
124
160
  }
125
161
  }
@@ -130,7 +166,20 @@ class Interval {
130
166
  // Return undefined to indicate that no change is necessary.
131
167
  return;
132
168
  }
133
- 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
+ }
134
183
  }
135
184
  }
136
185
  exports.Interval = Interval;
@@ -139,10 +188,41 @@ class SequenceInterval {
139
188
  this.start = start;
140
189
  this.end = end;
141
190
  this.intervalType = intervalType;
191
+ this.propertyManager = new merge_tree_1.PropertiesManager();
192
+ this.properties = {};
142
193
  if (props) {
143
194
  this.addProperties(props);
144
195
  }
145
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
+ }
146
226
  serialize(client) {
147
227
  const startPosition = this.start.toPosition();
148
228
  const endPosition = this.end.toPosition();
@@ -206,12 +286,7 @@ class SequenceInterval {
206
286
  return new SequenceInterval(this.start.min(b.start), this.end.max(b.end), this.intervalType);
207
287
  }
208
288
  addProperties(newProps, collab = false, seq, op) {
209
- if (!this.propertyManager) {
210
- this.propertyManager = new merge_tree_1.PropertiesManager();
211
- }
212
- if (!this.properties) {
213
- this.properties = (0, merge_tree_1.createMap)();
214
- }
289
+ this.initializeProperties();
215
290
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
216
291
  }
217
292
  overlapsPos(bstart, bend) {
@@ -220,60 +295,103 @@ class SequenceInterval {
220
295
  return (endPos > bstart) && (startPos < bend);
221
296
  }
222
297
  modify(label, start, end, op) {
223
- const startPos = start !== null && start !== void 0 ? start : this.start.toPosition();
224
- const endPos = end !== null && end !== void 0 ? end : this.end.toPosition();
225
- if (this.start.toPosition() === startPos && this.end.toPosition() === endPos) {
226
- // Return undefined to indicate that no change is necessary.
227
- return;
228
- }
229
- 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);
230
319
  if (this.properties) {
231
- newInterval.addProperties(this.properties);
320
+ newInterval.initializeProperties();
321
+ this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
232
322
  }
233
323
  return newInterval;
234
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
+ }
235
333
  }
236
334
  exports.SequenceInterval = SequenceInterval;
237
- function createPositionReference(client, pos, refType, op) {
238
- const segoff = client.getContainingSegment(pos, op);
239
- if (segoff === null || segoff === void 0 ? void 0 : segoff.segment) {
240
- const lref = new merge_tree_1.LocalReference(client, segoff.segment, segoff.offset, refType);
241
- if (refType !== merge_tree_1.ReferenceType.Transient) {
242
- client.addLocalReference(lref);
335
+ function createPositionReferenceFromSegoff(client, segoff, refType, op) {
336
+ if (segoff.segment) {
337
+ const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
338
+ return ref;
339
+ }
340
+ else {
341
+ if (!op && !(0, merge_tree_1.refTypeIncludesFlag)(refType, merge_tree_1.ReferenceType.Transient)) {
342
+ throw new container_utils_1.UsageError("Non-transient references need segment");
243
343
  }
244
- return lref;
344
+ return new merge_tree_1.LocalReference(client, undefined, 0, refType);
345
+ }
346
+ }
347
+ function createPositionReference(client, pos, refType, op) {
348
+ let segoff;
349
+ if (op) {
350
+ (0, common_utils_1.assert)((refType & merge_tree_1.ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
351
+ segoff = client.getContainingSegment(pos, op);
352
+ segoff = client.getSlideToSegment(segoff);
353
+ }
354
+ else {
355
+ (0, common_utils_1.assert)((refType & merge_tree_1.ReferenceType.SlideOnRemove) === 0, 0x2f6 /* SlideOnRemove references must be op created */);
356
+ segoff = client.getContainingSegment(pos);
245
357
  }
246
- return new merge_tree_1.LocalReference(client, undefined);
358
+ return createPositionReferenceFromSegoff(client, segoff, refType, op);
247
359
  }
248
360
  function createSequenceInterval(label, start, end, client, intervalType, op) {
249
361
  let beginRefType = merge_tree_1.ReferenceType.RangeBegin;
250
362
  let endRefType = merge_tree_1.ReferenceType.RangeEnd;
251
- if (intervalType === IntervalType.Nest) {
252
- beginRefType = merge_tree_1.ReferenceType.NestBegin;
253
- endRefType = merge_tree_1.ReferenceType.NestEnd;
254
- }
255
- else if (intervalType === IntervalType.Transient) {
363
+ if (intervalType === IntervalType.Transient) {
256
364
  beginRefType = merge_tree_1.ReferenceType.Transient;
257
365
  endRefType = merge_tree_1.ReferenceType.Transient;
258
366
  }
259
- // TODO: Should SlideOnRemove be the default behavior?
260
- if (intervalType & IntervalType.SlideOnRemove) {
261
- beginRefType |= merge_tree_1.ReferenceType.SlideOnRemove;
262
- endRefType |= merge_tree_1.ReferenceType.SlideOnRemove;
367
+ else {
368
+ if (intervalType === IntervalType.Nest) {
369
+ beginRefType = merge_tree_1.ReferenceType.NestBegin;
370
+ endRefType = merge_tree_1.ReferenceType.NestEnd;
371
+ }
372
+ // All non-transient interval references must eventually be SlideOnRemove
373
+ // To ensure eventual consistency, they must start as StayOnRemove when
374
+ // pending (created locally and creation op is not acked)
375
+ if (op) {
376
+ beginRefType |= merge_tree_1.ReferenceType.SlideOnRemove;
377
+ endRefType |= merge_tree_1.ReferenceType.SlideOnRemove;
378
+ }
379
+ else {
380
+ beginRefType |= merge_tree_1.ReferenceType.StayOnRemove;
381
+ endRefType |= merge_tree_1.ReferenceType.StayOnRemove;
382
+ }
263
383
  }
264
384
  const startLref = createPositionReference(client, start, beginRefType, op);
265
385
  const endLref = createPositionReference(client, end, endRefType, op);
266
- if (startLref && endLref) {
267
- startLref.pairedRef = endLref;
268
- endLref.pairedRef = startLref;
269
- const rangeProp = {
270
- [merge_tree_1.reservedRangeLabelsKey]: [label],
271
- };
272
- startLref.addProperties(rangeProp);
273
- endLref.addProperties(rangeProp);
274
- const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
275
- return ival;
276
- }
386
+ startLref.pairedRef = endLref;
387
+ endLref.pairedRef = startLref;
388
+ const rangeProp = {
389
+ [merge_tree_1.reservedRangeLabelsKey]: [label],
390
+ };
391
+ startLref.addProperties(rangeProp);
392
+ endLref.addProperties(rangeProp);
393
+ const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
394
+ return ival;
277
395
  }
278
396
  function defaultIntervalConflictResolver(a, b) {
279
397
  a.addPropertySet(b.properties);
@@ -296,11 +414,15 @@ function createIntervalIndex(conflict) {
296
414
  }
297
415
  exports.createIntervalIndex = createIntervalIndex;
298
416
  class LocalIntervalCollection {
299
- constructor(client, label, helpers) {
417
+ constructor(client, label, helpers,
418
+ /** Callback invoked each time one of the endpoints of an interval slides. */
419
+ onPositionChange) {
300
420
  this.client = client;
301
421
  this.label = label;
302
422
  this.helpers = helpers;
423
+ this.onPositionChange = onPositionChange;
303
424
  this.intervalTree = new merge_tree_1.IntervalTree();
425
+ this.intervalIdMap = new Map();
304
426
  // eslint-disable-next-line @typescript-eslint/unbound-method
305
427
  this.endIntervalTree = new merge_tree_1.RedBlackTree(helpers.compareEnds);
306
428
  }
@@ -308,7 +430,7 @@ class LocalIntervalCollection {
308
430
  this.conflictResolver = conflictResolver;
309
431
  this.endConflictResolver =
310
432
  (key, currentKey) => {
311
- const ival = this.conflictResolver(key, currentKey);
433
+ const ival = conflictResolver(key, currentKey);
312
434
  return {
313
435
  data: ival,
314
436
  key: ival,
@@ -323,13 +445,22 @@ class LocalIntervalCollection {
323
445
  // without ID's.
324
446
  return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
325
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
+ */
326
455
  ensureSerializedId(serializedInterval) {
327
456
  var _a;
328
- 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) {
329
459
  // An interval came over the wire without an ID, so create a non-unique one based on start/end.
330
460
  // This will allow all clients to refer to this interval consistently.
461
+ id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
331
462
  const newProps = {
332
- [reservedIntervalIdKey]: this.createLegacyId(serializedInterval.start, serializedInterval.end),
463
+ [reservedIntervalIdKey]: id,
333
464
  };
334
465
  serializedInterval.properties = (0, merge_tree_1.addProperties)(serializedInterval.properties, newProps);
335
466
  }
@@ -339,6 +470,7 @@ class LocalIntervalCollection {
339
470
  enumerable: true,
340
471
  writable: false,
341
472
  });
473
+ return id;
342
474
  }
343
475
  mapUntil(fn) {
344
476
  this.intervalTree.mapUntil(fn);
@@ -405,14 +537,12 @@ class LocalIntervalCollection {
405
537
  }
406
538
  }
407
539
  findOverlappingIntervals(startPosition, endPosition) {
408
- if (!this.intervalTree.intervals.isEmpty()) {
409
- const transientInterval = this.helpers.create("transient", startPosition, endPosition, this.client, IntervalType.Transient);
410
- const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
411
- return overlappingIntervalNodes.map((node) => node.key);
412
- }
413
- else {
540
+ if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
414
541
  return [];
415
542
  }
543
+ const transientInterval = this.helpers.create("transient", startPosition, endPosition, this.client, IntervalType.Transient);
544
+ const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
545
+ return overlappingIntervalNodes.map((node) => node.key);
416
546
  }
417
547
  previousInterval(pos) {
418
548
  const transientInterval = this.helpers.create("transient", pos, pos, this.client, IntervalType.Transient);
@@ -434,9 +564,16 @@ class LocalIntervalCollection {
434
564
  this.endIntervalTree.remove(transientInterval);
435
565
  return transientInterval;
436
566
  }
437
- removeExistingInterval(interval) {
567
+ removeIntervalFromIndex(interval) {
438
568
  this.intervalTree.removeExisting(interval);
439
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);
440
577
  }
441
578
  createInterval(start, end, intervalType, op) {
442
579
  return this.helpers.create(this.label, start, end, this.client, intervalType, op);
@@ -458,8 +595,9 @@ class LocalIntervalCollection {
458
595
  }
459
596
  return interval;
460
597
  }
461
- add(interval) {
462
- (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" */);
463
601
  // Make the ID immutable.
464
602
  Object.defineProperty(interval.properties, reservedIntervalIdKey, {
465
603
  configurable: false,
@@ -468,17 +606,14 @@ class LocalIntervalCollection {
468
606
  });
469
607
  this.intervalTree.put(interval, this.conflictResolver);
470
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);
471
614
  }
472
615
  getIntervalById(id) {
473
- let result;
474
- this.mapUntil((interval) => {
475
- if (interval.getIntervalId() === id) {
476
- result = interval;
477
- return false;
478
- }
479
- return true;
480
- });
481
- return result;
616
+ return this.intervalIdMap.get(id);
482
617
  }
483
618
  changeInterval(interval, start, end, op) {
484
619
  const newInterval = interval.modify(this.label, start, end, op);
@@ -491,7 +626,25 @@ class LocalIntervalCollection {
491
626
  serialize() {
492
627
  const client = this.client;
493
628
  const intervals = this.intervalTree.intervals.keys();
494
- 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
+ }
495
648
  }
496
649
  }
497
650
  exports.LocalIntervalCollection = LocalIntervalCollection;
@@ -523,37 +676,12 @@ class SequenceIntervalCollectionValueType {
523
676
  exports.SequenceIntervalCollectionValueType = SequenceIntervalCollectionValueType;
524
677
  SequenceIntervalCollectionValueType.Name = "sharedStringIntervalCollection";
525
678
  SequenceIntervalCollectionValueType._factory = new SequenceIntervalCollectionFactory();
526
- SequenceIntervalCollectionValueType._ops = new Map([[
527
- "add",
528
- {
529
- process: (value, params, local, op) => {
530
- value.ackAdd(params, local, op);
531
- },
532
- },
533
- ],
534
- [
535
- "delete",
536
- {
537
- process: (value, params, local, op) => {
538
- value.ackDelete(params, local, op);
539
- },
540
- },
541
- ],
542
- [
543
- "change",
544
- {
545
- process: (value, params, local, op) => {
546
- value.ackChange(params, local, op);
547
- },
548
- },
549
- ]]);
679
+ SequenceIntervalCollectionValueType._ops = makeOpsMap();
550
680
  const compareIntervalEnds = (a, b) => a.end - b.end;
551
681
  function createInterval(label, start, end, client) {
552
- let rangeProp;
553
- if (label && (label.length > 0)) {
554
- rangeProp = {
555
- [merge_tree_1.reservedRangeLabelsKey]: [label],
556
- };
682
+ const rangeProp = {};
683
+ if (label && label.length > 0) {
684
+ rangeProp[merge_tree_1.reservedRangeLabelsKey] = [label];
557
685
  }
558
686
  return new Interval(start, end, rangeProp);
559
687
  }
@@ -585,30 +713,45 @@ class IntervalCollectionValueType {
585
713
  exports.IntervalCollectionValueType = IntervalCollectionValueType;
586
714
  IntervalCollectionValueType.Name = "sharedIntervalCollection";
587
715
  IntervalCollectionValueType._factory = new IntervalCollectionFactory();
588
- IntervalCollectionValueType._ops = new Map([[
589
- "add",
590
- {
591
- process: (value, params, local, op) => {
592
- 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,
593
731
  },
594
- },
595
- ],
596
- [
597
- "delete",
598
- {
599
- process: (value, params, local, op) => {
600
- 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
+ },
601
743
  },
602
- },
603
- ],
604
- [
605
- "change",
606
- {
607
- process: (value, params, local, op) => {
608
- 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,
609
752
  },
610
- },
611
- ]]);
753
+ ]]);
754
+ }
612
755
  class IntervalCollectionIterator {
613
756
  constructor(collection, iteratesForward = true, start, end) {
614
757
  this.results = [];
@@ -630,26 +773,35 @@ class IntervalCollectionIterator {
630
773
  }
631
774
  exports.IntervalCollectionIterator = IntervalCollectionIterator;
632
775
  class IntervalCollection extends common_utils_1.TypedEventEmitter {
776
+ /** @internal */
633
777
  constructor(helpers, requiresClient, emitter, serializedIntervals) {
634
778
  super();
635
779
  this.helpers = helpers;
636
780
  this.requiresClient = requiresClient;
637
781
  this.emitter = emitter;
638
- 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
+ }
639
791
  }
640
792
  get attached() {
641
793
  return !!this.localCollection;
642
794
  }
643
795
  attachGraph(client, label) {
644
796
  if (this.attached) {
645
- throw new Error("Only supports one Sequence attach");
797
+ throw new telemetry_utils_1.LoggingError("Only supports one Sequence attach");
646
798
  }
647
799
  if ((client === undefined) && (this.requiresClient)) {
648
- throw new Error("Client required for this collection");
800
+ throw new telemetry_utils_1.LoggingError("Client required for this collection");
649
801
  }
650
802
  // Instantiate the local interval collection based on the saved intervals
651
803
  this.client = client;
652
- this.localCollection = new LocalIntervalCollection(client, label, this.helpers);
804
+ this.localCollection = new LocalIntervalCollection(client, label, this.helpers, (interval) => this.emit("changeInterval", interval, true, undefined));
653
805
  if (this.savedSerializedIntervals) {
654
806
  for (const serializedInterval of this.savedSerializedIntervals) {
655
807
  this.localCollection.ensureSerializedId(serializedInterval);
@@ -658,16 +810,33 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
658
810
  }
659
811
  this.savedSerializedIntervals = undefined;
660
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
+ }
661
819
  getIntervalById(id) {
662
820
  if (!this.attached) {
663
- throw new Error("attach must be called before accessing intervals");
821
+ throw new telemetry_utils_1.LoggingError("attach must be called before accessing intervals");
664
822
  }
665
823
  return this.localCollection.getIntervalById(id);
666
824
  }
825
+ /**
826
+ * Create a new interval and add it to the collection
827
+ * @param start - interval start position
828
+ * @param end - interval end position
829
+ * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
830
+ * @param props - properties of the interval
831
+ * @returns - the created interval
832
+ */
667
833
  add(start, end, intervalType, props) {
668
834
  var _a, _b;
669
835
  if (!this.attached) {
670
- 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");
837
+ }
838
+ if (intervalType & IntervalType.Transient) {
839
+ throw new telemetry_utils_1.LoggingError("Can not add transient intervals");
671
840
  }
672
841
  const interval = this.localCollection.addInterval(start, end, intervalType, props);
673
842
  if (interval) {
@@ -679,7 +848,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
679
848
  start,
680
849
  };
681
850
  // Local ops get submitted to the server. Remote ops have the deserializer run.
682
- this.emitter.emit("add", undefined, serializedInterval);
851
+ this.emitter.emit("add", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
683
852
  }
684
853
  this.emit("addInterval", interval, true, undefined);
685
854
  return interval;
@@ -690,7 +859,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
690
859
  if (interval) {
691
860
  // Local ops get submitted to the server. Remote ops have the deserializer run.
692
861
  if (local) {
693
- this.emitter.emit("delete", undefined, interval.serialize(this.client));
862
+ this.emitter.emit("delete", undefined, interval.serialize(this.client), { localSeq: this.getNextLocalSeq() });
694
863
  }
695
864
  else {
696
865
  if (this.onDeserialize) {
@@ -709,40 +878,41 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
709
878
  }
710
879
  changeProperties(id, props) {
711
880
  if (!this.attached) {
712
- throw new Error("Attach must be called before accessing intervals");
881
+ throw new telemetry_utils_1.LoggingError("Attach must be called before accessing intervals");
713
882
  }
714
883
  if (typeof (id) !== "string") {
715
- 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");
716
885
  }
717
886
  if (!props) {
718
- 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");
719
888
  }
720
889
  const interval = this.getIntervalById(id);
721
890
  if (interval) {
722
891
  // Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
723
892
  const deltaProps = interval.addProperties(props, true, merge_tree_1.UnassignedSequenceNumber);
724
893
  const serializedInterval = interval.serialize(this.client);
725
- // 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.
726
896
  serializedInterval.start = undefined;
727
897
  serializedInterval.end = undefined;
728
898
  serializedInterval.properties = props;
729
899
  serializedInterval.properties[reservedIntervalIdKey] = interval.getIntervalId();
730
- this.emitter.emit("change", undefined, serializedInterval);
900
+ this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
731
901
  this.emit("propertyChanged", interval, deltaProps);
732
902
  }
733
903
  this.emit("changeInterval", interval, true, undefined);
734
904
  }
735
905
  change(id, start, end) {
736
906
  if (!this.attached) {
737
- throw new Error("Attach must be called before accessing intervals");
907
+ throw new telemetry_utils_1.LoggingError("Attach must be called before accessing intervals");
738
908
  }
909
+ // Force id to be a string.
739
910
  if (typeof (id) !== "string") {
740
- 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");
741
912
  }
742
- // Force id to be a string.
743
913
  const interval = this.getIntervalById(id);
744
914
  if (interval) {
745
- this.localCollection.changeInterval(interval, start, end);
915
+ const newInterval = this.localCollection.changeInterval(interval, start, end);
746
916
  const serializedInterval = interval.serialize(this.client);
747
917
  serializedInterval.start = start;
748
918
  serializedInterval.end = end;
@@ -751,23 +921,19 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
751
921
  {
752
922
  [reservedIntervalIdKey]: interval.getIntervalId(),
753
923
  };
754
- this.emitter.emit("change", undefined, serializedInterval);
924
+ this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
755
925
  this.addPendingChange(id, serializedInterval);
926
+ this.emit("changeInterval", newInterval, true, undefined);
927
+ return newInterval;
756
928
  }
757
- this.emit("changeInterval", interval, true, undefined);
758
- return interval;
929
+ // No interval to change
930
+ return undefined;
759
931
  }
760
932
  addPendingChange(id, serializedInterval) {
761
933
  if (serializedInterval.start !== undefined) {
762
- if (!this.pendingChangesStart) {
763
- this.pendingChangesStart = new Map();
764
- }
765
934
  this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
766
935
  }
767
936
  if (serializedInterval.end !== undefined) {
768
- if (!this.pendingChangesEnd) {
769
- this.pendingChangesEnd = new Map();
770
- }
771
937
  this.addPendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
772
938
  }
773
939
  }
@@ -780,8 +946,9 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
780
946
  entries.push(serializedInterval);
781
947
  }
782
948
  removePendingChange(serializedInterval) {
949
+ var _a;
783
950
  // Change ops always have an ID.
784
- const id = serializedInterval.properties[reservedIntervalIdKey];
951
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
785
952
  if (serializedInterval.start !== undefined) {
786
953
  this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
787
954
  }
@@ -790,26 +957,24 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
790
957
  }
791
958
  }
792
959
  removePendingChangeHelper(id, pendingChanges, serializedInterval) {
793
- const entries = pendingChanges === null || pendingChanges === void 0 ? void 0 : pendingChanges.get(id);
960
+ const entries = pendingChanges.get(id);
794
961
  if (entries) {
795
962
  const pendingChange = entries.shift();
796
963
  if (entries.length === 0) {
797
964
  pendingChanges.delete(id);
798
965
  }
799
- if (pendingChange.start !== serializedInterval.start ||
800
- pendingChange.end !== serializedInterval.end) {
801
- 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");
802
969
  }
803
970
  }
804
971
  }
805
972
  hasPendingChangeStart(id) {
806
- var _a;
807
- const entries = (_a = this.pendingChangesStart) === null || _a === void 0 ? void 0 : _a.get(id);
973
+ const entries = this.pendingChangesStart.get(id);
808
974
  return entries && entries.length !== 0;
809
975
  }
810
976
  hasPendingChangeEnd(id) {
811
- var _a;
812
- const entries = (_a = this.pendingChangesEnd) === null || _a === void 0 ? void 0 : _a.get(id);
977
+ const entries = this.pendingChangesEnd.get(id);
813
978
  return entries && entries.length !== 0;
814
979
  }
815
980
  /** @deprecated - use ackChange */
@@ -818,22 +983,23 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
818
983
  }
819
984
  /** @internal */
820
985
  ackChange(serializedInterval, local, op) {
821
- var _a, _b;
986
+ var _a, _b, _c, _d;
822
987
  if (!this.attached) {
823
- throw new Error("Attach must be called before accessing intervals");
988
+ throw new telemetry_utils_1.LoggingError("Attach must be called before accessing intervals");
824
989
  }
825
990
  let interval;
826
991
  if (local) {
827
992
  // This is an ack from the server. Remove the pending change.
828
993
  this.removePendingChange(serializedInterval);
829
- const id = serializedInterval.properties[reservedIntervalIdKey];
994
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
830
995
  interval = this.getIntervalById(id);
831
996
  if (interval) {
832
997
  // Let the propertyManager prune its pending change-properties set.
833
- (_a = interval.propertyManager) === null || _a === void 0 ? void 0 : _a.ackPendingProperties({
834
- type: 2 /* ANNOTATE */,
835
- props: serializedInterval.properties,
998
+ (_b = interval.propertyManager) === null || _b === void 0 ? void 0 : _b.ackPendingProperties({
999
+ type: merge_tree_1.MergeTreeDeltaType.ANNOTATE,
1000
+ props: (_c = serializedInterval.properties) !== null && _c !== void 0 ? _c : {},
836
1001
  });
1002
+ this.ackInterval(interval, op);
837
1003
  }
838
1004
  }
839
1005
  else {
@@ -842,7 +1008,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
842
1008
  // Note that the ID is in the property bag only to allow us to find the interval.
843
1009
  // This API cannot change the ID, and writing to the ID property will result in an exception. So we
844
1010
  // strip it out of the properties here.
845
- 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 + ""]);
846
1012
  interval = this.getIntervalById(id);
847
1013
  if (interval) {
848
1014
  let start;
@@ -857,7 +1023,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
857
1023
  if (start !== undefined || end !== undefined) {
858
1024
  // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
859
1025
  // the one we originally found in the tree.
860
- 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;
861
1027
  }
862
1028
  const deltaProps = interval.addProperties(newProps, true, op.sequenceNumber);
863
1029
  if (this.onDeserialize) {
@@ -872,7 +1038,7 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
872
1038
  }
873
1039
  addConflictResolver(conflictResolver) {
874
1040
  if (!this.attached) {
875
- throw new Error("attachSequence must be called");
1041
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
876
1042
  }
877
1043
  this.localCollection.addConflictResolver(conflictResolver);
878
1044
  }
@@ -885,22 +1051,109 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
885
1051
  this.onDeserialize = onDeserialize;
886
1052
  // Trigger the async prepare work across all values in the collection
887
1053
  this.localCollection.map((interval) => {
888
- this.onDeserialize(interval);
1054
+ onDeserialize(interval);
889
1055
  });
890
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
+ }
1082
+ getSlideToSegment(lref) {
1083
+ const segoff = { segment: lref.segment, offset: lref.offset };
1084
+ const newSegoff = this.client.getSlideToSegment(segoff);
1085
+ const value = (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
1086
+ return value;
1087
+ }
1088
+ setSlideOnRemove(lref) {
1089
+ let refType = lref.refType;
1090
+ refType = refType & ~merge_tree_1.ReferenceType.StayOnRemove;
1091
+ refType = refType | merge_tree_1.ReferenceType.SlideOnRemove;
1092
+ lref.refType = refType;
1093
+ }
1094
+ ackInterval(interval, op) {
1095
+ // in current usage, interval is always a SequenceInterval
1096
+ if (!(interval instanceof SequenceInterval)) {
1097
+ return;
1098
+ }
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)) {
1101
+ return;
1102
+ }
1103
+ const newStart = this.getSlideToSegment(interval.start);
1104
+ const newEnd = this.getSlideToSegment(interval.end);
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.
1120
+ this.localCollection.removeExistingInterval(interval);
1121
+ if (needsStartUpdate) {
1122
+ const props = interval.start.properties;
1123
+ this.client.removeLocalReferencePosition(interval.start);
1124
+ interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
1125
+ if (props) {
1126
+ interval.start.addProperties(props);
1127
+ }
1128
+ }
1129
+ if (needsEndUpdate) {
1130
+ const props = interval.end.properties;
1131
+ this.client.removeLocalReferencePosition(interval.end);
1132
+ interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
1133
+ if (props) {
1134
+ interval.end.addProperties(props);
1135
+ }
1136
+ }
1137
+ this.localCollection.add(interval);
1138
+ }
1139
+ }
891
1140
  /** @deprecated - use ackAdd */
892
1141
  addInternal(serializedInterval, local, op) {
893
1142
  return this.ackAdd(serializedInterval, local, op);
894
1143
  }
895
1144
  /** @internal */
896
1145
  ackAdd(serializedInterval, local, op) {
1146
+ var _a;
897
1147
  if (local) {
898
- // Local ops were applied when the message was created and there's no "pending add"
899
- // state to bookkeep
1148
+ const id = (_a = serializedInterval.properties) === null || _a === void 0 ? void 0 : _a[reservedIntervalIdKey];
1149
+ const localInterval = this.getIntervalById(id);
1150
+ if (localInterval) {
1151
+ this.ackInterval(localInterval, op);
1152
+ }
900
1153
  return;
901
1154
  }
902
1155
  if (!this.attached) {
903
- throw new Error("attachSequence must be called");
1156
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
904
1157
  }
905
1158
  this.localCollection.ensureSerializedId(serializedInterval);
906
1159
  const interval = this.localCollection.addInterval(serializedInterval.start, serializedInterval.end, serializedInterval.intervalType, serializedInterval.properties, op);
@@ -925,17 +1178,20 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
925
1178
  return;
926
1179
  }
927
1180
  if (!this.attached) {
928
- 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");
929
1182
  }
930
- this.localCollection.ensureSerializedId(serializedInterval);
931
- const interval = this.localCollection.getIntervalById(serializedInterval.properties[reservedIntervalIdKey]);
1183
+ const id = this.localCollection.ensureSerializedId(serializedInterval);
1184
+ const interval = this.localCollection.getIntervalById(id);
932
1185
  if (interval) {
933
1186
  this.deleteExistingInterval(interval, local, op);
934
1187
  }
935
1188
  }
1189
+ /**
1190
+ * @internal
1191
+ */
936
1192
  serializeInternal() {
937
1193
  if (!this.attached) {
938
- throw new Error("attachSequence must be called");
1194
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
939
1195
  }
940
1196
  return this.localCollection.serialize();
941
1197
  }
@@ -967,25 +1223,25 @@ class IntervalCollection extends common_utils_1.TypedEventEmitter {
967
1223
  }
968
1224
  findOverlappingIntervals(startPosition, endPosition) {
969
1225
  if (!this.attached) {
970
- throw new Error("attachSequence must be called");
1226
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
971
1227
  }
972
1228
  return this.localCollection.findOverlappingIntervals(startPosition, endPosition);
973
1229
  }
974
1230
  map(fn) {
975
1231
  if (!this.attached) {
976
- throw new Error("attachSequence must be called");
1232
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
977
1233
  }
978
1234
  this.localCollection.map(fn);
979
1235
  }
980
1236
  previousInterval(pos) {
981
1237
  if (!this.attached) {
982
- throw new Error("attachSequence must be called");
1238
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
983
1239
  }
984
1240
  return this.localCollection.previousInterval(pos);
985
1241
  }
986
1242
  nextInterval(pos) {
987
1243
  if (!this.attached) {
988
- throw new Error("attachSequence must be called");
1244
+ throw new telemetry_utils_1.LoggingError("attachSequence must be called");
989
1245
  }
990
1246
  return this.localCollection.nextInterval(pos);
991
1247
  }