@fluidframework/sequence 2.0.0-internal.1.4.2 → 2.0.0-internal.2.0.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 (117) hide show
  1. package/dist/defaultMap.d.ts.map +1 -1
  2. package/dist/defaultMap.js +1 -0
  3. package/dist/defaultMap.js.map +1 -1
  4. package/dist/index.d.ts +4 -5
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -4
  7. package/dist/index.js.map +1 -1
  8. package/dist/intervalCollection.d.ts +271 -34
  9. package/dist/intervalCollection.d.ts.map +1 -1
  10. package/dist/intervalCollection.js +343 -97
  11. package/dist/intervalCollection.js.map +1 -1
  12. package/dist/intervalTree.d.ts +72 -0
  13. package/dist/intervalTree.d.ts.map +1 -0
  14. package/dist/intervalTree.js +91 -0
  15. package/dist/intervalTree.js.map +1 -0
  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 +66 -15
  20. package/dist/sequence.d.ts.map +1 -1
  21. package/dist/sequence.js +73 -19
  22. package/dist/sequence.js.map +1 -1
  23. package/dist/sequenceDeltaEvent.d.ts +15 -1
  24. package/dist/sequenceDeltaEvent.d.ts.map +1 -1
  25. package/dist/sequenceDeltaEvent.js +2 -1
  26. package/dist/sequenceDeltaEvent.js.map +1 -1
  27. package/dist/sequenceFactory.d.ts +0 -89
  28. package/dist/sequenceFactory.d.ts.map +1 -1
  29. package/dist/sequenceFactory.js +2 -142
  30. package/dist/sequenceFactory.js.map +1 -1
  31. package/dist/sharedIntervalCollection.d.ts +0 -6
  32. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  33. package/dist/sharedIntervalCollection.js +0 -7
  34. package/dist/sharedIntervalCollection.js.map +1 -1
  35. package/dist/sharedSequence.d.ts +2 -2
  36. package/dist/sharedString.d.ts +16 -15
  37. package/dist/sharedString.d.ts.map +1 -1
  38. package/dist/sharedString.js +96 -15
  39. package/dist/sharedString.js.map +1 -1
  40. package/lib/defaultMap.d.ts.map +1 -1
  41. package/lib/defaultMap.js +1 -0
  42. package/lib/defaultMap.js.map +1 -1
  43. package/lib/index.d.ts +4 -5
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +2 -4
  46. package/lib/index.js.map +1 -1
  47. package/lib/intervalCollection.d.ts +271 -34
  48. package/lib/intervalCollection.d.ts.map +1 -1
  49. package/lib/intervalCollection.js +341 -98
  50. package/lib/intervalCollection.js.map +1 -1
  51. package/lib/intervalTree.d.ts +72 -0
  52. package/lib/intervalTree.d.ts.map +1 -0
  53. package/lib/intervalTree.js +86 -0
  54. package/lib/intervalTree.js.map +1 -0
  55. package/lib/packageVersion.d.ts +1 -1
  56. package/lib/packageVersion.js +1 -1
  57. package/lib/packageVersion.js.map +1 -1
  58. package/lib/sequence.d.ts +66 -15
  59. package/lib/sequence.d.ts.map +1 -1
  60. package/lib/sequence.js +73 -19
  61. package/lib/sequence.js.map +1 -1
  62. package/lib/sequenceDeltaEvent.d.ts +15 -1
  63. package/lib/sequenceDeltaEvent.d.ts.map +1 -1
  64. package/lib/sequenceDeltaEvent.js +2 -1
  65. package/lib/sequenceDeltaEvent.js.map +1 -1
  66. package/lib/sequenceFactory.d.ts +0 -89
  67. package/lib/sequenceFactory.d.ts.map +1 -1
  68. package/lib/sequenceFactory.js +1 -139
  69. package/lib/sequenceFactory.js.map +1 -1
  70. package/lib/sharedIntervalCollection.d.ts +0 -6
  71. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  72. package/lib/sharedIntervalCollection.js +0 -7
  73. package/lib/sharedIntervalCollection.js.map +1 -1
  74. package/lib/sharedSequence.d.ts +2 -2
  75. package/lib/sharedString.d.ts +16 -15
  76. package/lib/sharedString.d.ts.map +1 -1
  77. package/lib/sharedString.js +97 -16
  78. package/lib/sharedString.js.map +1 -1
  79. package/package.json +99 -27
  80. package/src/defaultMap.ts +3 -0
  81. package/src/index.ts +4 -4
  82. package/src/intervalCollection.ts +486 -143
  83. package/src/intervalTree.ts +166 -0
  84. package/src/packageVersion.ts +1 -1
  85. package/src/sequence.ts +86 -30
  86. package/src/sequenceDeltaEvent.ts +18 -4
  87. package/src/sequenceFactory.ts +2 -163
  88. package/src/sharedIntervalCollection.ts +0 -11
  89. package/src/sharedString.ts +120 -23
  90. package/tsconfig.json +0 -1
  91. package/dist/sharedNumberSequence.d.ts +0 -50
  92. package/dist/sharedNumberSequence.d.ts.map +0 -1
  93. package/dist/sharedNumberSequence.js +0 -61
  94. package/dist/sharedNumberSequence.js.map +0 -1
  95. package/dist/sharedObjectSequence.d.ts +0 -50
  96. package/dist/sharedObjectSequence.d.ts.map +0 -1
  97. package/dist/sharedObjectSequence.js +0 -61
  98. package/dist/sharedObjectSequence.js.map +0 -1
  99. package/dist/sparsematrix.d.ts +0 -152
  100. package/dist/sparsematrix.d.ts.map +0 -1
  101. package/dist/sparsematrix.js +0 -343
  102. package/dist/sparsematrix.js.map +0 -1
  103. package/lib/sharedNumberSequence.d.ts +0 -50
  104. package/lib/sharedNumberSequence.d.ts.map +0 -1
  105. package/lib/sharedNumberSequence.js +0 -57
  106. package/lib/sharedNumberSequence.js.map +0 -1
  107. package/lib/sharedObjectSequence.d.ts +0 -50
  108. package/lib/sharedObjectSequence.d.ts.map +0 -1
  109. package/lib/sharedObjectSequence.js +0 -57
  110. package/lib/sharedObjectSequence.js.map +0 -1
  111. package/lib/sparsematrix.d.ts +0 -152
  112. package/lib/sparsematrix.d.ts.map +0 -1
  113. package/lib/sparsematrix.js +0 -334
  114. package/lib/sparsematrix.js.map +0 -1
  115. package/src/sharedNumberSequence.ts +0 -62
  116. package/src/sharedObjectSequence.ts +0 -62
  117. package/src/sparsematrix.ts +0 -434
@@ -15,10 +15,6 @@ import {
15
15
  ConflictAction,
16
16
  createMap,
17
17
  ICombiningOp,
18
- IInterval,
19
- IntervalConflictResolver,
20
- IntervalNode,
21
- IntervalTree,
22
18
  ISegment,
23
19
  MergeTreeDeltaType,
24
20
  minReferencePosition,
@@ -45,6 +41,7 @@ import {
45
41
  IValueType,
46
42
  IValueTypeOperationValue,
47
43
  } from "./defaultMapInterfaces";
44
+ import { IInterval, IntervalConflictResolver, IntervalTree, IntervalNode } from "./intervalTree";
48
45
 
49
46
  const reservedIntervalIdKey = "intervalId";
50
47
 
@@ -67,17 +64,44 @@ export enum IntervalType {
67
64
  Transient = 0x4,
68
65
  }
69
66
 
67
+ /**
68
+ * Serialized object representation of an interval.
69
+ * This representation is used for ops that create or change intervals.
70
+ * @internal
71
+ */
70
72
  export interface ISerializedInterval {
73
+ /**
74
+ * Sequence number at which `start` and `end` should be interpreted
75
+ *
76
+ * @remarks - It's unclear that this is necessary to store here.
77
+ * This should just be the refSeq on the op that modified the interval, which should be available via other means.
78
+ * At the time of writing, it's not plumbed through to the reconnect/rebase code, however, which does need it.
79
+ */
71
80
  sequenceNumber: number;
81
+ /** Start position of the interval (inclusive) */
72
82
  start: number;
83
+ /** End position of the interval (inclusive) */
73
84
  end: number;
85
+ /** Interval type to create */
74
86
  intervalType: IntervalType;
87
+ /** Any properties the interval has */
75
88
  properties?: PropertySet;
76
89
  }
77
90
 
91
+ /**
92
+ * Represents a change that should be applied to an existing interval.
93
+ * Changes can modify any of start/end/properties, with `undefined` signifying no change should be made.
94
+ * @internal
95
+ */
96
+ export type SerializedIntervalDelta =
97
+ Omit<ISerializedInterval, "start" | "end" | "properties">
98
+ & Partial<Pick<ISerializedInterval, "start" | "end" | "properties">>;
99
+
78
100
  /**
79
101
  * A size optimization to avoid redundantly storing keys when serializing intervals
80
- * as JSON. Intervals are of the format:
102
+ * as JSON for summaries.
103
+ *
104
+ * Intervals are of the format:
81
105
  *
82
106
  * [start, end, sequenceNumber, intervalType, properties]
83
107
  */
@@ -125,11 +149,22 @@ function compressInterval(interval: ISerializedInterval): CompressedSerializedIn
125
149
  }
126
150
 
127
151
  export interface ISerializableInterval extends IInterval {
152
+ /** Serializable bag of properties associated with the interval. */
128
153
  properties: PropertySet;
154
+ /** @internal */
129
155
  propertyManager: PropertiesManager;
130
- serialize(client: Client): ISerializedInterval;
156
+ /** @internal */
157
+ serialize(): ISerializedInterval;
158
+ /** @internal */
131
159
  addProperties(props: PropertySet, collaborating?: boolean, seq?: number):
132
160
  PropertySet | undefined;
161
+ /**
162
+ * Gets the id associated with this interval.
163
+ * When the interval is used as part of an interval collection, this id can be used to modify or remove the
164
+ * interval.
165
+ * @remarks - This signature includes `undefined` strictly for backwards-compatibility reasons, as older versions
166
+ * of Fluid didn't always write interval ids.
167
+ */
133
168
  getIntervalId(): string | undefined;
134
169
  }
135
170
 
@@ -148,18 +183,28 @@ export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
148
183
  */
149
184
  create(
150
185
  label: string,
151
- start: number,
152
- end: number,
153
- client: Client,
154
- intervalType?: IntervalType,
186
+ start: number | undefined,
187
+ end: number | undefined,
188
+ client: Client | undefined,
189
+ intervalType: IntervalType,
155
190
  op?: ISequencedDocumentMessage,
156
191
  fromSnapshot?: boolean,
157
192
  ): TInterval;
158
193
  }
159
194
 
195
+ /**
196
+ * Serializable interval whose endpoints are plain-old numbers.
197
+ */
160
198
  export class Interval implements ISerializableInterval {
199
+ /**
200
+ * {@inheritDoc ISerializableInterval.properties}
201
+ */
161
202
  public properties: PropertySet;
162
- public auxProps: PropertySet[];
203
+ /** @internal */
204
+ public auxProps: PropertySet[] | undefined;
205
+ /**
206
+ * {@inheritDoc ISerializableInterval.propertyManager}
207
+ */
163
208
  public propertyManager: PropertiesManager;
164
209
  constructor(
165
210
  public start: number,
@@ -174,6 +219,9 @@ export class Interval implements ISerializableInterval {
174
219
  }
175
220
  }
176
221
 
222
+ /**
223
+ * {@inheritDoc ISerializableInterval.getIntervalId}
224
+ */
177
225
  public getIntervalId(): string | undefined {
178
226
  const id = this.properties?.[reservedIntervalIdKey];
179
227
  if (id === undefined) {
@@ -182,10 +230,21 @@ export class Interval implements ISerializableInterval {
182
230
  return `${id}`;
183
231
  }
184
232
 
185
- public getAdditionalPropertySets() {
186
- return this.auxProps;
233
+ /**
234
+ * @returns an array containing any auxiliary property sets added with `addPropertySet`.
235
+ */
236
+ public getAdditionalPropertySets(): PropertySet[] {
237
+ return this.auxProps ?? [];
187
238
  }
188
239
 
240
+ /**
241
+ * Adds an auxiliary set of properties to this interval.
242
+ * These properties can be recovered using `getAdditionalPropertySets`
243
+ * @param props - set of properties to add
244
+ * @remarks - This gets called as part of the default conflict resolver for `IntervalCollection<Interval>`
245
+ * (i.e. non-sequence-based interval collections). However, the additional properties don't get serialized.
246
+ * This functionality seems half-baked.
247
+ */
189
248
  public addPropertySet(props: PropertySet) {
190
249
  if (this.auxProps === undefined) {
191
250
  this.auxProps = [];
@@ -193,12 +252,15 @@ export class Interval implements ISerializableInterval {
193
252
  this.auxProps.push(props);
194
253
  }
195
254
 
196
- public serialize(client: Client): ISerializedInterval {
197
- const seq = client?.getCurrentSeq() ?? 0;
255
+ /**
256
+ * {@inheritDoc ISerializableInterval.serialize}
257
+ * @internal
258
+ */
259
+ public serialize(): ISerializedInterval {
198
260
  const serializedInterval: ISerializedInterval = {
199
261
  end: this.end,
200
262
  intervalType: 0,
201
- sequenceNumber: seq,
263
+ sequenceNumber: 0,
202
264
  start: this.start,
203
265
  };
204
266
  if (this.properties) {
@@ -207,10 +269,16 @@ export class Interval implements ISerializableInterval {
207
269
  return serializedInterval;
208
270
  }
209
271
 
272
+ /**
273
+ * {@inheritDoc IInterval.clone}
274
+ */
210
275
  public clone() {
211
276
  return new Interval(this.start, this.end, this.properties);
212
277
  }
213
278
 
279
+ /**
280
+ * {@inheritDoc IInterval.compare}
281
+ */
214
282
  public compare(b: Interval) {
215
283
  const startResult = this.compareStart(b);
216
284
  if (startResult === 0) {
@@ -233,20 +301,32 @@ export class Interval implements ISerializableInterval {
233
301
  }
234
302
  }
235
303
 
304
+ /**
305
+ * {@inheritDoc IInterval.compareStart}
306
+ */
236
307
  public compareStart(b: Interval) {
237
308
  return this.start - b.start;
238
309
  }
239
310
 
311
+ /**
312
+ * {@inheritDoc IInterval.compareEnd}
313
+ */
240
314
  public compareEnd(b: Interval) {
241
315
  return this.end - b.end;
242
316
  }
243
317
 
318
+ /**
319
+ * {@inheritDoc IInterval.overlaps}
320
+ */
244
321
  public overlaps(b: Interval) {
245
322
  const result = (this.start <= b.end) &&
246
323
  (this.end >= b.start);
247
324
  return result;
248
325
  }
249
326
 
327
+ /**
328
+ * {@inheritDoc IInterval.union}
329
+ */
250
330
  public union(b: Interval) {
251
331
  return new Interval(Math.min(this.start, b.start),
252
332
  Math.max(this.end, b.end), this.properties);
@@ -256,6 +336,9 @@ export class Interval implements ISerializableInterval {
256
336
  return this.properties;
257
337
  }
258
338
 
339
+ /**
340
+ * {@inheritDoc ISerializableInterval.addProperties}
341
+ */
259
342
  public addProperties(
260
343
  newProps: PropertySet,
261
344
  collaborating: boolean = false,
@@ -268,6 +351,9 @@ export class Interval implements ISerializableInterval {
268
351
  }
269
352
  }
270
353
 
354
+ /**
355
+ * {@inheritDoc IInterval.modify}
356
+ */
271
357
  public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
272
358
  const startPos = start ?? this.start;
273
359
  const endPos = end ?? this.end;
@@ -293,13 +379,32 @@ export class Interval implements ISerializableInterval {
293
379
  }
294
380
  }
295
381
 
382
+ /**
383
+ * Interval impelmentation whose ends are associated with positions in a mutatable sequence.
384
+ * As such, when content is inserted into the middle of the interval, the interval expands to
385
+ * include that content.
386
+ */
296
387
  export class SequenceInterval implements ISerializableInterval {
388
+ /**
389
+ * {@inheritDoc ISerializableInterval.properties}
390
+ */
297
391
  public properties: PropertySet;
392
+ /**
393
+ * {@inheritDoc ISerializableInterval.propertyManager}
394
+ */
298
395
  public propertyManager: PropertiesManager;
299
396
 
300
397
  constructor(
301
398
  private readonly client: Client,
399
+ /**
400
+ * Start endpoint of this interval.
401
+ * @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
402
+ */
302
403
  public start: LocalReferencePosition,
404
+ /**
405
+ * End endpoint of this interval.
406
+ * @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
407
+ */
303
408
  public end: LocalReferencePosition,
304
409
  public intervalType: IntervalType,
305
410
  props?: PropertySet,
@@ -344,13 +449,17 @@ export class SequenceInterval implements ISerializableInterval {
344
449
  }
345
450
  }
346
451
 
347
- public serialize(client: Client): ISerializedInterval {
348
- const startPosition = client.localReferencePositionToPosition(this.start);
349
- const endPosition = client.localReferencePositionToPosition(this.end);
452
+ /**
453
+ * {@inheritDoc ISerializableInterval.serialize}
454
+ * @internal
455
+ */
456
+ public serialize(): ISerializedInterval {
457
+ const startPosition = this.client.localReferencePositionToPosition(this.start);
458
+ const endPosition = this.client.localReferencePositionToPosition(this.end);
350
459
  const serializedInterval: ISerializedInterval = {
351
460
  end: endPosition,
352
461
  intervalType: this.intervalType,
353
- sequenceNumber: client.getCurrentSeq(),
462
+ sequenceNumber: this.client.getCurrentSeq(),
354
463
  start: startPosition,
355
464
  };
356
465
 
@@ -361,10 +470,16 @@ export class SequenceInterval implements ISerializableInterval {
361
470
  return serializedInterval;
362
471
  }
363
472
 
473
+ /**
474
+ * {@inheritDoc IInterval.clone}
475
+ */
364
476
  public clone() {
365
477
  return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties);
366
478
  }
367
479
 
480
+ /**
481
+ * {@inheritDoc IInterval.compare}
482
+ */
368
483
  public compare(b: SequenceInterval) {
369
484
  const startResult = this.compareStart(b);
370
485
  if (startResult === 0) {
@@ -387,20 +502,32 @@ export class SequenceInterval implements ISerializableInterval {
387
502
  }
388
503
  }
389
504
 
505
+ /**
506
+ * {@inheritDoc IInterval.compareStart}
507
+ */
390
508
  public compareStart(b: SequenceInterval) {
391
509
  return compareReferencePositions(this.start, b.start);
392
510
  }
393
511
 
512
+ /**
513
+ * {@inheritDoc IInterval.compareEnd}
514
+ */
394
515
  public compareEnd(b: SequenceInterval) {
395
516
  return compareReferencePositions(this.end, b.end);
396
517
  }
397
518
 
519
+ /**
520
+ * {@inheritDoc IInterval.overlaps}
521
+ */
398
522
  public overlaps(b: SequenceInterval) {
399
523
  const result = (compareReferencePositions(this.start, b.end) <= 0) &&
400
524
  (compareReferencePositions(this.end, b.start) >= 0);
401
525
  return result;
402
526
  }
403
527
 
528
+ /**
529
+ * {@inheritDoc ISerializableInterval.getIntervalId}
530
+ */
404
531
  public getIntervalId(): string | undefined {
405
532
  const id = this.properties?.[reservedIntervalIdKey];
406
533
  if (id === undefined) {
@@ -409,11 +536,17 @@ export class SequenceInterval implements ISerializableInterval {
409
536
  return `${id}`;
410
537
  }
411
538
 
539
+ /**
540
+ * {@inheritDoc IInterval.union}
541
+ */
412
542
  public union(b: SequenceInterval) {
413
543
  return new SequenceInterval(this.client, minReferencePosition(this.start, b.start),
414
544
  maxReferencePosition(this.end, b.end), this.intervalType);
415
545
  }
416
546
 
547
+ /**
548
+ * {@inheritDoc ISerializableInterval.addProperties}
549
+ */
417
550
  public addProperties(
418
551
  newProps: PropertySet,
419
552
  collab: boolean = false,
@@ -424,12 +557,19 @@ export class SequenceInterval implements ISerializableInterval {
424
557
  return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
425
558
  }
426
559
 
560
+ /**
561
+ * @returns whether this interval overlaps two numerical positions.
562
+ * @remarks - this is currently strict overlap, which doesn't align with the endpoint treatment of`.overlaps()`
563
+ */
427
564
  public overlapsPos(bstart: number, bend: number) {
428
565
  const startPos = this.client.localReferencePositionToPosition(this.start);
429
566
  const endPos = this.client.localReferencePositionToPosition(this.end);
430
567
  return (endPos > bstart) && (startPos < bend);
431
568
  }
432
569
 
570
+ /**
571
+ * {@inheritDoc IInterval.modify}
572
+ */
433
573
  public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage, localSeq?: number) {
434
574
  const getRefType = (baseType: ReferenceType): ReferenceType => {
435
575
  let refType = baseType;
@@ -445,7 +585,9 @@ export class SequenceInterval implements ISerializableInterval {
445
585
  startRef = createPositionReference(
446
586
  this.client, start, getRefType(this.start.refType), op, undefined, localSeq,
447
587
  );
448
- startRef.addProperties(this.start.properties);
588
+ if (this.start.properties) {
589
+ startRef.addProperties(this.start.properties);
590
+ }
449
591
  }
450
592
 
451
593
  let endRef = this.end;
@@ -453,7 +595,9 @@ export class SequenceInterval implements ISerializableInterval {
453
595
  endRef = createPositionReference(
454
596
  this.client, end, getRefType(this.end.refType), op, undefined, localSeq,
455
597
  );
456
- endRef.addProperties(this.end.properties);
598
+ if (this.end.properties) {
599
+ endRef.addProperties(this.end.properties);
600
+ }
457
601
  }
458
602
 
459
603
  const newInterval = new SequenceInterval(this.client, startRef, endRef, this.intervalType);
@@ -506,21 +650,22 @@ function createPositionReference(
506
650
  segoff = client.getContainingSegment(pos, op);
507
651
  segoff = client.getSlideToSegment(segoff);
508
652
  } else {
509
- assert((refType & ReferenceType.SlideOnRemove) === 0 || fromSnapshot,
653
+ assert((refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
510
654
  0x2f6 /* SlideOnRemove references must be op created */);
511
655
  segoff = client.getContainingSegment(pos, undefined, localSeq);
512
656
  }
513
657
  return createPositionReferenceFromSegoff(client, segoff, refType, op);
514
658
  }
515
659
 
516
- function createSequenceInterval(
660
+ export function createSequenceInterval(
517
661
  label: string,
518
662
  start: number,
519
663
  end: number,
520
664
  client: Client,
521
- intervalType?: IntervalType,
665
+ intervalType: IntervalType,
522
666
  op?: ISequencedDocumentMessage,
523
- fromSnapshot?: boolean): SequenceInterval {
667
+ fromSnapshot?: boolean,
668
+ ): SequenceInterval {
524
669
  let beginRefType = ReferenceType.RangeBegin;
525
670
  let endRefType = ReferenceType.RangeEnd;
526
671
  if (intervalType === IntervalType.Transient) {
@@ -551,7 +696,13 @@ function createSequenceInterval(
551
696
  startLref.addProperties(rangeProp);
552
697
  endLref.addProperties(rangeProp);
553
698
 
554
- const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp);
699
+ const ival = new SequenceInterval(
700
+ client,
701
+ startLref,
702
+ endLref,
703
+ intervalType,
704
+ rangeProp,
705
+ );
555
706
  return ival;
556
707
  }
557
708
 
@@ -565,7 +716,7 @@ export function createIntervalIndex(conflict?: IntervalConflictResolver<Interval
565
716
  compareEnds: compareIntervalEnds,
566
717
  create: createInterval,
567
718
  };
568
- const lc = new LocalIntervalCollection<Interval>(undefined, "", helpers);
719
+ const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
569
720
  if (conflict) {
570
721
  lc.addConflictResolver(conflict);
571
722
  } else {
@@ -588,7 +739,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
588
739
  private readonly label: string,
589
740
  private readonly helpers: IIntervalHelpers<TInterval>,
590
741
  /** Callback invoked each time one of the endpoints of an interval slides. */
591
- private readonly onPositionChange?: (interval: TInterval) => void,
742
+ private readonly onPositionChange?: (interval: TInterval, previousInterval: TInterval) => void,
592
743
  ) {
593
744
  // eslint-disable-next-line @typescript-eslint/unbound-method
594
745
  this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
@@ -723,6 +874,10 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
723
874
  }
724
875
  }
725
876
 
877
+ /**
878
+ * @returns an array of all intervals contained in this collection that overlap the range
879
+ * `[startPosition, endPosition]`.
880
+ */
726
881
  public findOverlappingIntervals(startPosition: number, endPosition: number) {
727
882
  if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
728
883
  return [];
@@ -831,7 +986,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
831
986
  this.intervalIdMap.set(id, interval);
832
987
  }
833
988
 
834
- public add(interval: TInterval) {
989
+ public add(interval: TInterval): void {
835
990
  this.linkEndpointsToInterval(interval);
836
991
  this.addIntervalToIndex(interval);
837
992
  this.addIntervalListeners(interval);
@@ -843,8 +998,8 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
843
998
 
844
999
  public changeInterval(
845
1000
  interval: TInterval,
846
- start: number,
847
- end: number,
1001
+ start: number | undefined,
1002
+ end: number | undefined,
848
1003
  op?: ISequencedDocumentMessage,
849
1004
  localSeq?: number,
850
1005
  ) {
@@ -857,23 +1012,46 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
857
1012
  }
858
1013
 
859
1014
  public serialize(): ISerializedIntervalCollectionV2 {
860
- const client = this.client;
861
1015
  const intervals = this.intervalTree.intervals.keys();
862
-
863
1016
  return {
864
1017
  label: this.label,
865
- intervals: intervals.map((interval) => compressInterval(interval.serialize(client))),
1018
+ intervals: intervals.map((interval) => compressInterval(interval.serialize())),
866
1019
  version: 2,
867
1020
  };
868
1021
  }
869
1022
 
870
1023
  private addIntervalListeners(interval: TInterval) {
1024
+ const cloneRef = (ref: LocalReferencePosition) => {
1025
+ const segment = ref.getSegment();
1026
+ if (segment === undefined) {
1027
+ // Cloning is unnecessary: refs which have slid off the string entirely
1028
+ // never get slid back on. Creation code for refs doesn't accept undefined segment
1029
+ // either, so this must be special-cased.
1030
+ return ref;
1031
+ }
1032
+
1033
+ return this.client.createLocalReferencePosition(
1034
+ segment,
1035
+ ref.getOffset(),
1036
+ ReferenceType.Transient,
1037
+ ref.properties,
1038
+ );
1039
+ };
871
1040
  if (interval instanceof SequenceInterval) {
1041
+ let previousInterval: TInterval & SequenceInterval | undefined;
872
1042
  interval.addPositionChangeListeners(
873
- () => this.removeIntervalFromIndex(interval),
874
1043
  () => {
1044
+ assert(!previousInterval, 0x3f9 /* Invalid interleaving of before/after slide */);
1045
+ previousInterval = interval.clone() as TInterval & SequenceInterval;
1046
+ previousInterval.start = cloneRef(previousInterval.start);
1047
+ previousInterval.end = cloneRef(previousInterval.end);
1048
+ this.removeIntervalFromIndex(interval);
1049
+ },
1050
+ () => {
1051
+ assert(previousInterval !== undefined, 0x3fa /* Invalid interleaving of before/after slide */);
875
1052
  this.addIntervalToIndex(interval);
876
- this.onPositionChange?.(interval);
1053
+ this.onPositionChange?.(interval, previousInterval);
1054
+ previousInterval = undefined;
877
1055
  },
878
1056
  );
879
1057
  }
@@ -886,7 +1064,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
886
1064
  }
887
1065
  }
888
1066
 
889
- const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
1067
+ export const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
890
1068
  compareReferencePositions(a.end, b.end);
891
1069
 
892
1070
  class SequenceIntervalCollectionFactory
@@ -902,7 +1080,7 @@ class SequenceIntervalCollectionFactory
902
1080
  return new IntervalCollection<SequenceInterval>(helpers, true, emitter, raw);
903
1081
  }
904
1082
 
905
- public store(value: IntervalCollection<SequenceInterval>): ISerializedIntervalCollectionV2 {
1083
+ public store(value: IntervalCollection<SequenceInterval>): ISerializedInterval[] | ISerializedIntervalCollectionV2 {
906
1084
  return value.serializeInternal();
907
1085
  }
908
1086
  }
@@ -952,7 +1130,7 @@ class IntervalCollectionFactory
952
1130
  create: createInterval,
953
1131
  };
954
1132
  const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw);
955
- collection.attachGraph(undefined, "");
1133
+ collection.attachGraph(undefined as any as Client, "");
956
1134
  return collection;
957
1135
  }
958
1136
 
@@ -982,7 +1160,7 @@ export class IntervalCollectionValueType
982
1160
  private static readonly _ops = makeOpsMap<Interval>();
983
1161
  }
984
1162
 
985
- function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperation<IntervalCollection<T>>> {
1163
+ export function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperation<IntervalCollection<T>>> {
986
1164
  const rebase = (
987
1165
  collection: IntervalCollection<T>,
988
1166
  op: IValueTypeOperationValue,
@@ -1004,6 +1182,7 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
1004
1182
  if (!params) {
1005
1183
  return;
1006
1184
  }
1185
+ assert(op !== undefined, 0x3fb /* op should exist here */);
1007
1186
  collection.ackAdd(params, local, op);
1008
1187
  },
1009
1188
  rebase,
@@ -1013,6 +1192,7 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
1013
1192
  "delete",
1014
1193
  {
1015
1194
  process: (collection, params, local, op) => {
1195
+ assert(op !== undefined, 0x3fc /* op should exist here */);
1016
1196
  collection.ackDelete(params, local, op);
1017
1197
  },
1018
1198
  rebase: (collection, op, localOpMetadata) => {
@@ -1030,6 +1210,7 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
1030
1210
  if (!params) {
1031
1211
  return;
1032
1212
  }
1213
+ assert(op !== undefined, 0x3fd /* op should exist here */);
1033
1214
  collection.ackChange(params, local, op);
1034
1215
  },
1035
1216
  rebase,
@@ -1039,7 +1220,8 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
1039
1220
 
1040
1221
  export type DeserializeCallback = (properties: PropertySet) => void;
1041
1222
 
1042
- export class IntervalCollectionIterator<TInterval extends ISerializableInterval> {
1223
+ export class IntervalCollectionIterator<TInterval extends ISerializableInterval>
1224
+ implements Iterator<TInterval> {
1043
1225
  private readonly results: TInterval[];
1044
1226
  private index: number;
1045
1227
 
@@ -1054,43 +1236,80 @@ export class IntervalCollectionIterator<TInterval extends ISerializableInterval>
1054
1236
  collection.gatherIterationResults(this.results, iteratesForward, start, end);
1055
1237
  }
1056
1238
 
1057
- public next() {
1058
- let _value: TInterval | undefined;
1059
- let _done: boolean = true;
1060
-
1239
+ public next(): IteratorResult<TInterval> {
1061
1240
  if (this.index < this.results.length) {
1062
- _value = this.results[this.index++];
1063
- _done = false;
1241
+ return {
1242
+ value: this.results[this.index++],
1243
+ done: false,
1244
+ };
1064
1245
  }
1065
1246
 
1066
1247
  return {
1067
- value: _value,
1068
- done: _done,
1248
+ value: undefined,
1249
+ done: true,
1069
1250
  };
1070
1251
  }
1071
1252
  }
1072
1253
 
1254
+ /**
1255
+ * Change events emitted by `IntervalCollection`s
1256
+ */
1073
1257
  export interface IIntervalCollectionEvent<TInterval extends ISerializableInterval> extends IEvent {
1074
1258
  /**
1075
- * This event is invoked whenever the properties or endpoints of an interval may have changed.
1259
+ * This event is invoked whenever the endpoints of an interval may have changed.
1076
1260
  * This can happen on:
1077
- * - endpoint modification (local or remote)
1078
- * - ack of an endpoint modification
1079
- * - property change (local or remote)
1080
- * - position change due to segment sliding (will always appear as a local change)
1261
+ * - local endpoint modification
1262
+ * - ack of a remote endpoint modification
1263
+ * - position change due to segment sliding (slides due to mergeTree segment deletion will always appear local)
1081
1264
  * The `interval` argument reflects the new values.
1265
+ * `previousInterval` contains transient `ReferencePosition`s at the same location as the interval's original
1266
+ * endpoints. These references should be used for position information only.
1267
+ * `local` reflects whether the change originated locally.
1268
+ * `op` is defined if and only if the server has acked this change.
1082
1269
  */
1083
1270
  (event: "changeInterval",
1084
- listener: (interval: TInterval, local: boolean, op: ISequencedDocumentMessage | undefined) => void);
1271
+ listener: (
1272
+ interval: TInterval,
1273
+ previousInterval: TInterval,
1274
+ local: boolean,
1275
+ op: ISequencedDocumentMessage | undefined
1276
+ ) => void);
1277
+ /**
1278
+ * This event is invoked whenever an interval is added or removed from the collection.
1279
+ * `local` reflects whether the change originated locally.
1280
+ * `op` is defined if and only if the server has acked this change.
1281
+ */
1085
1282
  (event: "addInterval" | "deleteInterval",
1086
- listener: (interval: TInterval, local: boolean, op: ISequencedDocumentMessage) => void);
1087
- (event: "propertyChanged", listener: (interval: TInterval, propertyArgs: PropertySet) => void);
1283
+ listener: (interval: TInterval, local: boolean, op: ISequencedDocumentMessage | undefined) => void);
1284
+ /**
1285
+ * This event is invoked whenever an interval's properties have changed.
1286
+ * `interval` reflects the state of the updated properties.
1287
+ * `propertyDeltas` is a map-like whose keys contain all values that were changed, and whose
1288
+ * values contain all previous values of the property set.
1289
+ * This object can be used directly in a call to `changeProperties` to revert the property change if desired.
1290
+ * `local` reflects whether the change originated locally.
1291
+ * `op` is defined if and only if the server has acked this change.
1292
+ */
1293
+ (event: "propertyChanged",
1294
+ listener: (
1295
+ interval: TInterval,
1296
+ propertyDeltas: PropertySet,
1297
+ local: boolean,
1298
+ op: ISequencedDocumentMessage | undefined
1299
+ ) => void);
1088
1300
  }
1089
1301
 
1302
+ /**
1303
+ * Collection of intervals that supports addition, modification, removal, and efficient spatial querying.
1304
+ * This class is not a DDS in its own right, but emits events on mutating operations such that it's possible to
1305
+ * integrate into a DDS.
1306
+ * This aligns with its usage in `SharedSegmentSequence`, which allows associating intervals to positions in the
1307
+ * sequence DDS which are broadcast to all other clients in an eventually consistent fashion.
1308
+ */
1090
1309
  export class IntervalCollection<TInterval extends ISerializableInterval>
1091
1310
  extends TypedEventEmitter<IIntervalCollectionEvent<TInterval>> {
1092
1311
  private savedSerializedIntervals?: ISerializedInterval[];
1093
- private localCollection: LocalIntervalCollection<TInterval>;
1312
+ private localCollection: LocalIntervalCollection<TInterval> | undefined;
1094
1313
  private onDeserialize: DeserializeCallback | undefined;
1095
1314
  private client: Client | undefined;
1096
1315
  private readonly pendingChangesStart: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
@@ -1114,6 +1333,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1114
1333
  : serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
1115
1334
  }
1116
1335
 
1336
+ /** @internal */
1117
1337
  public attachGraph(client: Client, label: string) {
1118
1338
  if (this.attached) {
1119
1339
  throw new LoggingError("Only supports one Sequence attach");
@@ -1129,7 +1349,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1129
1349
  client,
1130
1350
  label,
1131
1351
  this.helpers,
1132
- (interval) => this.emit("changeInterval", interval, true, undefined),
1352
+ (interval, previousInterval) => this.emitChange(interval, previousInterval, true),
1133
1353
  );
1134
1354
  if (this.savedSerializedIntervals) {
1135
1355
  for (const serializedInterval of this.savedSerializedIntervals) {
@@ -1144,7 +1364,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1144
1364
  undefined,
1145
1365
  true,
1146
1366
  );
1147
- interval.addProperties(properties);
1367
+ if (properties) {
1368
+ interval.addProperties(properties);
1369
+ }
1148
1370
  this.localCollection.add(interval);
1149
1371
  }
1150
1372
  }
@@ -1162,15 +1384,43 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1162
1384
  return 0;
1163
1385
  }
1164
1386
 
1387
+ private emitChange(
1388
+ interval: TInterval,
1389
+ previousInterval: TInterval,
1390
+ local: boolean,
1391
+ op?: ISequencedDocumentMessage,
1392
+ ): void {
1393
+ // Temporarily make references transient so that positional queries work (non-transient refs
1394
+ // on resolve to DetachedPosition on any segments that don't contain them). The original refType
1395
+ // is restored as single-endpoint changes re-use previous references.
1396
+ let startRefType: ReferenceType;
1397
+ let endRefType: ReferenceType;
1398
+ if (previousInterval instanceof SequenceInterval) {
1399
+ startRefType = previousInterval.start.refType;
1400
+ endRefType = previousInterval.end.refType;
1401
+ previousInterval.start.refType = ReferenceType.Transient;
1402
+ previousInterval.end.refType = ReferenceType.Transient;
1403
+ this.emit("changeInterval", interval, previousInterval, local, op);
1404
+ previousInterval.start.refType = startRefType;
1405
+ previousInterval.end.refType = endRefType;
1406
+ } else {
1407
+ this.emit("changeInterval", interval, previousInterval, local, op);
1408
+ }
1409
+ }
1410
+
1411
+ /**
1412
+ * @returns the interval in this collection that has the provided `id`.
1413
+ * If no interval in the collection has this `id`, returns `undefined`.
1414
+ */
1165
1415
  public getIntervalById(id: string) {
1166
- if (!this.attached) {
1416
+ if (!this.localCollection) {
1167
1417
  throw new LoggingError("attach must be called before accessing intervals");
1168
1418
  }
1169
1419
  return this.localCollection.getIntervalById(id);
1170
1420
  }
1171
1421
 
1172
1422
  /**
1173
- * Create a new interval and add it to the collection
1423
+ * Creates a new interval and add it to the collection.
1174
1424
  * @param start - interval start position
1175
1425
  * @param end - interval end position
1176
1426
  * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
@@ -1182,8 +1432,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1182
1432
  end: number,
1183
1433
  intervalType: IntervalType,
1184
1434
  props?: PropertySet,
1185
- ) {
1186
- if (!this.attached) {
1435
+ ): TInterval {
1436
+ if (!this.localCollection) {
1187
1437
  throw new LoggingError("attach must be called prior to adding intervals");
1188
1438
  }
1189
1439
  if (intervalType & IntervalType.Transient) {
@@ -1210,6 +1460,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1210
1460
  }
1211
1461
 
1212
1462
  private deleteExistingInterval(interval: TInterval, local: boolean, op?: ISequencedDocumentMessage) {
1463
+ if (!this.localCollection) {
1464
+ throw new LoggingError("Attach must be called before accessing intervals");
1465
+ }
1213
1466
  // The given interval is known to exist in the collection.
1214
1467
  this.localCollection.removeExistingInterval(interval);
1215
1468
 
@@ -1219,7 +1472,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1219
1472
  this.emitter.emit(
1220
1473
  "delete",
1221
1474
  undefined,
1222
- interval.serialize(this.client),
1475
+ interval.serialize(),
1223
1476
  { localSeq: this.getNextLocalSeq() },
1224
1477
  );
1225
1478
  } else {
@@ -1232,7 +1485,15 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1232
1485
  this.emit("deleteInterval", interval, local, op);
1233
1486
  }
1234
1487
 
1488
+ /**
1489
+ * Removes an interval from the collection.
1490
+ * @param id - Id of the interval to remove
1491
+ * @returns the removed interval
1492
+ */
1235
1493
  public removeIntervalById(id: string) {
1494
+ if (!this.localCollection) {
1495
+ throw new LoggingError("Attach must be called before accessing intervals");
1496
+ }
1236
1497
  const interval = this.localCollection.getIntervalById(id);
1237
1498
  if (interval) {
1238
1499
  this.deleteExistingInterval(interval, true, undefined);
@@ -1240,6 +1501,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1240
1501
  return interval;
1241
1502
  }
1242
1503
 
1504
+ /**
1505
+ * Changes the properties on an existing interval.
1506
+ * @param id - Id of the interval whose properties should be changed
1507
+ * @param props - Property set to apply to the interval. Shallow merging is used between any existing properties
1508
+ * and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`.
1509
+ */
1243
1510
  public changeProperties(id: string, props: PropertySet) {
1244
1511
  if (!this.attached) {
1245
1512
  throw new LoggingError("Attach must be called before accessing intervals");
@@ -1255,7 +1522,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1255
1522
  if (interval) {
1256
1523
  // Pass Unassigned as the sequence number to indicate that this is a local op that is waiting for an ack.
1257
1524
  const deltaProps = interval.addProperties(props, true, UnassignedSequenceNumber);
1258
- const serializedInterval: ISerializedInterval = interval.serialize(this.client);
1525
+ const serializedInterval: ISerializedInterval = interval.serialize();
1259
1526
 
1260
1527
  // Emit a change op that will only change properties. Add the ID to
1261
1528
  // the property bag provided by the caller.
@@ -1265,13 +1532,19 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1265
1532
  serializedInterval.properties = props;
1266
1533
  serializedInterval.properties[reservedIntervalIdKey] = interval.getIntervalId();
1267
1534
  this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
1268
- this.emit("propertyChanged", interval, deltaProps);
1535
+ this.emit("propertyChanged", interval, deltaProps, true, undefined);
1269
1536
  }
1270
- this.emit("changeInterval", interval, true, undefined);
1271
1537
  }
1272
1538
 
1539
+ /**
1540
+ * Changes the endpoints of an existing interval.
1541
+ * @param id - Id of the interval to change
1542
+ * @param start - New start value, if defined. `undefined` signifies this endpoint should be left unchanged.
1543
+ * @param end - New end value, if defined. `undefined` signifies this endpoint should be left unchanged.
1544
+ * @returns the interval that was changed, if it existed in the collection.
1545
+ */
1273
1546
  public change(id: string, start?: number, end?: number): TInterval | undefined {
1274
- if (!this.attached) {
1547
+ if (!this.localCollection) {
1275
1548
  throw new LoggingError("Attach must be called before accessing intervals");
1276
1549
  }
1277
1550
 
@@ -1283,7 +1556,10 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1283
1556
  const interval = this.getIntervalById(id);
1284
1557
  if (interval) {
1285
1558
  const newInterval = this.localCollection.changeInterval(interval, start, end);
1286
- const serializedInterval: ISerializedInterval = interval.serialize(this.client);
1559
+ if (!newInterval) {
1560
+ return undefined;
1561
+ }
1562
+ const serializedInterval: SerializedIntervalDelta = interval.serialize();
1287
1563
  serializedInterval.start = start;
1288
1564
  serializedInterval.end = end;
1289
1565
  // Emit a property bag containing only the ID, as we don't intend for this op to change any properties.
@@ -1293,14 +1569,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1293
1569
  };
1294
1570
  this.emitter.emit("change", undefined, serializedInterval, { localSeq: this.getNextLocalSeq() });
1295
1571
  this.addPendingChange(id, serializedInterval);
1296
- this.emit("changeInterval", newInterval, true, undefined);
1572
+ this.emitChange(newInterval, interval, true);
1297
1573
  return newInterval;
1298
1574
  }
1299
1575
  // No interval to change
1300
1576
  return undefined;
1301
1577
  }
1302
1578
 
1303
- private addPendingChange(id: string, serializedInterval: ISerializedInterval) {
1579
+ private addPendingChange(id: string, serializedInterval: SerializedIntervalDelta) {
1304
1580
  if (serializedInterval.start !== undefined) {
1305
1581
  this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
1306
1582
  }
@@ -1311,10 +1587,10 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1311
1587
 
1312
1588
  private addPendingChangeHelper(
1313
1589
  id: string,
1314
- pendingChanges: Map<string, ISerializedInterval[]>,
1315
- serializedInterval: ISerializedInterval,
1590
+ pendingChanges: Map<string, SerializedIntervalDelta[]>,
1591
+ serializedInterval: SerializedIntervalDelta,
1316
1592
  ) {
1317
- let entries: ISerializedInterval[] | undefined = pendingChanges.get(id);
1593
+ let entries: SerializedIntervalDelta[] | undefined = pendingChanges.get(id);
1318
1594
  if (!entries) {
1319
1595
  entries = [];
1320
1596
  pendingChanges.set(id, entries);
@@ -1322,7 +1598,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1322
1598
  entries.push(serializedInterval);
1323
1599
  }
1324
1600
 
1325
- private removePendingChange(serializedInterval: ISerializedInterval) {
1601
+ private removePendingChange(serializedInterval: SerializedIntervalDelta) {
1326
1602
  // Change ops always have an ID.
1327
1603
  const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
1328
1604
  if (serializedInterval.start !== undefined) {
@@ -1335,8 +1611,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1335
1611
 
1336
1612
  private removePendingChangeHelper(
1337
1613
  id: string,
1338
- pendingChanges: Map<string, ISerializedInterval[]>,
1339
- serializedInterval: ISerializedInterval,
1614
+ pendingChanges: Map<string, SerializedIntervalDelta[]>,
1615
+ serializedInterval: SerializedIntervalDelta,
1340
1616
  ) {
1341
1617
  const entries = pendingChanges.get(id);
1342
1618
  if (entries) {
@@ -1363,64 +1639,72 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1363
1639
 
1364
1640
  /** @internal */
1365
1641
  public ackChange(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
1366
- if (!this.attached) {
1642
+ if (!this.localCollection) {
1367
1643
  throw new LoggingError("Attach must be called before accessing intervals");
1368
1644
  }
1369
1645
 
1370
- let interval: TInterval | undefined;
1371
-
1372
1646
  if (local) {
1373
1647
  // This is an ack from the server. Remove the pending change.
1374
1648
  this.removePendingChange(serializedInterval);
1375
- const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
1376
- interval = this.getIntervalById(id);
1377
- if (interval) {
1378
- // Let the propertyManager prune its pending change-properties set.
1379
- interval.propertyManager?.ackPendingProperties(
1380
- {
1381
- type: MergeTreeDeltaType.ANNOTATE,
1382
- props: serializedInterval.properties ?? {},
1383
- });
1649
+ }
1384
1650
 
1385
- this.ackInterval(interval, op);
1386
- }
1651
+ // Note that the ID is in the property bag only to allow us to find the interval.
1652
+ // This API cannot change the ID, and writing to the ID property will result in an exception. So we
1653
+ // strip it out of the properties here.
1654
+ const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties ?? {};
1655
+ assert(id !== undefined, 0x3fe /* id must exist on the interval */);
1656
+ const interval: TInterval | undefined = this.getIntervalById(id);
1657
+ if (!interval) {
1658
+ // The interval has been removed locally; no-op.
1659
+ return;
1660
+ }
1661
+
1662
+ if (local) {
1663
+ // Let the propertyManager prune its pending change-properties set.
1664
+ interval.propertyManager?.ackPendingProperties(
1665
+ {
1666
+ type: MergeTreeDeltaType.ANNOTATE,
1667
+ props: serializedInterval.properties ?? {},
1668
+ });
1669
+
1670
+ this.ackInterval(interval, op);
1387
1671
  } else {
1388
1672
  // If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
1389
1673
  // should be the winning change.
1390
- // Note that the ID is in the property bag only to allow us to find the interval.
1391
- // This API cannot change the ID, and writing to the ID property will result in an exception. So we
1392
- // strip it out of the properties here.
1393
- const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties;
1394
- interval = this.getIntervalById(id);
1395
- if (interval) {
1396
- let start: number | undefined;
1397
- let end: number | undefined;
1398
- // Track pending start/end independently of one another.
1399
- if (!this.hasPendingChangeStart(id)) {
1400
- start = serializedInterval.start;
1401
- }
1402
- if (!this.hasPendingChangeEnd(id)) {
1403
- end = serializedInterval.end;
1404
- }
1405
- if (start !== undefined || end !== undefined) {
1406
- // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
1407
- // the one we originally found in the tree.
1408
- interval = this.localCollection.changeInterval(interval, start, end, op) ?? interval;
1409
- }
1410
- const deltaProps = interval.addProperties(newProps, true, op.sequenceNumber);
1411
- if (this.onDeserialize) {
1412
- this.onDeserialize(interval);
1413
- }
1414
- this.emit("propertyChanged", interval, deltaProps);
1674
+ let start: number | undefined;
1675
+ let end: number | undefined;
1676
+ // Track pending start/end independently of one another.
1677
+ if (!this.hasPendingChangeStart(id)) {
1678
+ start = serializedInterval.start;
1679
+ }
1680
+ if (!this.hasPendingChangeEnd(id)) {
1681
+ end = serializedInterval.end;
1682
+ }
1683
+
1684
+ let newInterval = interval;
1685
+ if (start !== undefined || end !== undefined) {
1686
+ // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
1687
+ // the one we originally found in the tree.
1688
+ newInterval = this.localCollection.changeInterval(interval, start, end, op) ?? interval;
1689
+ }
1690
+ const deltaProps = newInterval.addProperties(newProps, true, op.sequenceNumber);
1691
+ if (this.onDeserialize) {
1692
+ this.onDeserialize(newInterval);
1693
+ }
1694
+
1695
+ if (newInterval !== interval) {
1696
+ this.emitChange(newInterval, interval, local, op);
1697
+ }
1698
+
1699
+ const changedProperties = Object.keys(newProps).length > 0;
1700
+ if (changedProperties) {
1701
+ this.emit("propertyChanged", interval, deltaProps, local, op);
1415
1702
  }
1416
- }
1417
- if (interval) {
1418
- this.emit("changeInterval", interval, local, op);
1419
1703
  }
1420
1704
  }
1421
1705
 
1422
1706
  public addConflictResolver(conflictResolver: IntervalConflictResolver<TInterval>): void {
1423
- if (!this.attached) {
1707
+ if (!this.localCollection) {
1424
1708
  throw new LoggingError("attachSequence must be called");
1425
1709
  }
1426
1710
  this.localCollection.addConflictResolver(conflictResolver);
@@ -1436,7 +1720,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1436
1720
  this.onDeserialize = onDeserialize;
1437
1721
 
1438
1722
  // Trigger the async prepare work across all values in the collection
1439
- this.localCollection.map((interval) => {
1723
+ this.localCollection?.map((interval) => {
1440
1724
  onDeserialize(interval);
1441
1725
  });
1442
1726
  }
@@ -1450,9 +1734,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1450
1734
  */
1451
1735
  public rebaseLocalInterval(
1452
1736
  opName: string,
1453
- serializedInterval: ISerializedInterval,
1737
+ serializedInterval: SerializedIntervalDelta,
1454
1738
  localSeq: number,
1455
- ): ISerializedInterval | undefined {
1739
+ ): SerializedIntervalDelta | undefined {
1456
1740
  if (!this.client) {
1457
1741
  // If there's no associated mergeTree client, the originally submitted op is still correct.
1458
1742
  return serializedInterval;
@@ -1468,9 +1752,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1468
1752
  this.client.rebasePosition(end, sequenceNumber, localSeq);
1469
1753
 
1470
1754
  const intervalId = properties?.[reservedIntervalIdKey];
1471
- const localInterval = this.localCollection.getIntervalById(intervalId);
1755
+ const localInterval = this.localCollection?.getIntervalById(intervalId);
1472
1756
 
1473
- const rebased: ISerializedInterval = {
1757
+ const rebased: SerializedIntervalDelta = {
1474
1758
  start: startRebased,
1475
1759
  end: endRebased,
1476
1760
  intervalType,
@@ -1487,7 +1771,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1487
1771
  // delete the interval
1488
1772
  if (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition) {
1489
1773
  if (localInterval) {
1490
- this.localCollection.removeExistingInterval(localInterval);
1774
+ this.localCollection?.removeExistingInterval(localInterval);
1491
1775
  }
1492
1776
  return undefined;
1493
1777
  }
@@ -1508,18 +1792,24 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1508
1792
  // we need to slide because the reference has been removed
1509
1793
  if (startSegment || endSegment) {
1510
1794
  const newStart =
1511
- startSegment && this.client.getPosition(startSegment.segment, localSeq) + startSegment.offset;
1795
+ startSegment && this.client.getPosition(startSegment.segment, localSeq) + (startSegment.offset ?? 0);
1512
1796
  const newEnd =
1513
- endSegment && this.client.getPosition(endSegment.segment, localSeq) + endSegment.offset;
1797
+ endSegment && this.client.getPosition(endSegment.segment, localSeq) + (endSegment.offset ?? 0);
1514
1798
 
1515
- this.localCollection.changeInterval(localInterval, newStart, newEnd, undefined, localSeq);
1799
+ this.localCollection?.changeInterval(localInterval, newStart, newEnd, undefined, localSeq);
1516
1800
  }
1517
1801
 
1518
1802
  return rebased;
1519
1803
  }
1520
1804
 
1521
1805
  private getSlideToSegment(lref: LocalReferencePosition) {
1806
+ if (!this.client) {
1807
+ throw new LoggingError("client does not exist");
1808
+ }
1522
1809
  const segoff = { segment: lref.getSegment(), offset: lref.getOffset() };
1810
+ if (segoff.segment?.localRefs?.has(lref) !== true) {
1811
+ return undefined;
1812
+ }
1523
1813
  const newSegoff = this.client.getSlideToSegment(segoff);
1524
1814
  const value: { segment: ISegment | undefined; offset: number | undefined; } | undefined
1525
1815
  = (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
@@ -1563,28 +1853,51 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1563
1853
  const needsEndUpdate = newEnd !== undefined && !hasPendingEndChange;
1564
1854
 
1565
1855
  if (needsStartUpdate || needsEndUpdate) {
1856
+ if (!this.localCollection) {
1857
+ throw new LoggingError("Attach must be called before accessing intervals");
1858
+ }
1859
+
1860
+ // `interval`'s endpoints will get modified in-place, so clone it prior to doing so for event emission.
1861
+ const oldInterval = interval.clone() as TInterval & SequenceInterval;
1862
+
1566
1863
  // In this case, where we change the start or end of an interval,
1567
1864
  // it is necessary to remove and re-add the interval listeners.
1568
1865
  // This ensures that the correct listeners are added to the LocalReferencePosition.
1569
1866
  this.localCollection.removeExistingInterval(interval);
1867
+ if (!this.client) {
1868
+ throw new LoggingError("client does not exist");
1869
+ }
1570
1870
 
1571
1871
  if (needsStartUpdate) {
1572
1872
  const props = interval.start.properties;
1573
- this.client.removeLocalReferencePosition(interval.start);
1574
1873
  interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
1575
1874
  if (props) {
1576
1875
  interval.start.addProperties(props);
1577
1876
  }
1877
+ const oldSeg = oldInterval.start.getSegment();
1878
+ // remove and rebuild start interval as transient for event
1879
+ this.client.removeLocalReferencePosition(oldInterval.start);
1880
+ oldInterval.start.refType = ReferenceType.Transient;
1881
+ oldSeg?.localRefs?.addLocalRef(
1882
+ oldInterval.start,
1883
+ oldInterval.start.getOffset());
1578
1884
  }
1579
1885
  if (needsEndUpdate) {
1580
1886
  const props = interval.end.properties;
1581
- this.client.removeLocalReferencePosition(interval.end);
1582
1887
  interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
1583
1888
  if (props) {
1584
1889
  interval.end.addProperties(props);
1585
1890
  }
1891
+ // remove and rebuild end interval as transient for event
1892
+ const oldSeg = oldInterval.end.getSegment();
1893
+ this.client.removeLocalReferencePosition(oldInterval.end);
1894
+ oldInterval.end.refType = ReferenceType.Transient;
1895
+ oldSeg?.localRefs?.addLocalRef(
1896
+ oldInterval.end,
1897
+ oldInterval.end.getOffset());
1586
1898
  }
1587
1899
  this.localCollection.add(interval);
1900
+ this.emitChange(interval, oldInterval as TInterval, true, op);
1588
1901
  }
1589
1902
  }
1590
1903
 
@@ -1602,7 +1915,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1602
1915
  return;
1603
1916
  }
1604
1917
 
1605
- if (!this.attached) {
1918
+ if (!this.localCollection) {
1606
1919
  throw new LoggingError("attachSequence must be called");
1607
1920
  }
1608
1921
 
@@ -1638,7 +1951,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1638
1951
  return;
1639
1952
  }
1640
1953
 
1641
- if (!this.attached) {
1954
+ if (!this.localCollection) {
1642
1955
  throw new LoggingError("attach must be called prior to deleting intervals");
1643
1956
  }
1644
1957
 
@@ -1653,76 +1966,106 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
1653
1966
  * @internal
1654
1967
  */
1655
1968
  public serializeInternal(): ISerializedIntervalCollectionV2 {
1656
- if (!this.attached) {
1969
+ if (!this.localCollection) {
1657
1970
  throw new LoggingError("attachSequence must be called");
1658
1971
  }
1659
1972
 
1660
1973
  return this.localCollection.serialize();
1661
1974
  }
1662
1975
 
1976
+ /**
1977
+ * @returns an iterator over all intervals in this collection.
1978
+ */
1663
1979
  public [Symbol.iterator](): IntervalCollectionIterator<TInterval> {
1664
1980
  const iterator = new IntervalCollectionIterator<TInterval>(this);
1665
1981
  return iterator;
1666
1982
  }
1667
1983
 
1984
+ /**
1985
+ * @returns a forward iterator over all intervals in this collection with start point equal to `startPosition`.
1986
+ */
1668
1987
  public CreateForwardIteratorWithStartPosition(startPosition: number): IntervalCollectionIterator<TInterval> {
1669
1988
  const iterator = new IntervalCollectionIterator<TInterval>(this, true, startPosition);
1670
1989
  return iterator;
1671
1990
  }
1672
1991
 
1992
+ /**
1993
+ * @returns a backward iterator over all intervals in this collection with start point equal to `startPosition`.
1994
+ */
1673
1995
  public CreateBackwardIteratorWithStartPosition(startPosition: number): IntervalCollectionIterator<TInterval> {
1674
1996
  const iterator = new IntervalCollectionIterator<TInterval>(this, false, startPosition);
1675
1997
  return iterator;
1676
1998
  }
1677
1999
 
2000
+ /**
2001
+ * @returns a forward iterator over all intervals in this collection with end point equal to `endPosition`.
2002
+ */
1678
2003
  public CreateForwardIteratorWithEndPosition(endPosition: number): IntervalCollectionIterator<TInterval> {
1679
2004
  const iterator = new IntervalCollectionIterator<TInterval>(this, true, undefined, endPosition);
1680
2005
  return iterator;
1681
2006
  }
1682
2007
 
2008
+ /**
2009
+ * @returns a backward iterator over all intervals in this collection with end point equal to `endPosition`.
2010
+ */
1683
2011
  public CreateBackwardIteratorWithEndPosition(endPosition: number): IntervalCollectionIterator<TInterval> {
1684
2012
  const iterator = new IntervalCollectionIterator<TInterval>(this, false, undefined, endPosition);
1685
2013
  return iterator;
1686
2014
  }
1687
2015
 
2016
+ /**
2017
+ * Gathers iteration results that optionally match a start/end criteria into the provided array.
2018
+ * @param results - Array to gather the results into. In lieu of a return value, this array will be populated with
2019
+ * intervals matching the query upon edit.
2020
+ * @param iteratesForward - whether or not iteration should be in the forward direction
2021
+ * @param start - If provided, only match intervals whose start point is equal to `start`.
2022
+ * @param end - If provided, only match intervals whose end point is equal to `end`.
2023
+ */
1688
2024
  public gatherIterationResults(
1689
2025
  results: TInterval[],
1690
2026
  iteratesForward: boolean,
1691
2027
  start?: number,
1692
2028
  end?: number) {
1693
- if (!this.attached) {
2029
+ if (!this.localCollection) {
1694
2030
  return;
1695
2031
  }
1696
2032
 
1697
2033
  this.localCollection.gatherIterationResults(results, iteratesForward, start, end);
1698
2034
  }
1699
2035
 
2036
+ /**
2037
+ * @returns an array of all intervals in this collection that overlap with the interval
2038
+ * `[startPosition, endPosition]`.
2039
+ */
1700
2040
  public findOverlappingIntervals(startPosition: number, endPosition: number): TInterval[] {
1701
- if (!this.attached) {
2041
+ if (!this.localCollection) {
1702
2042
  throw new LoggingError("attachSequence must be called");
1703
2043
  }
1704
2044
 
1705
2045
  return this.localCollection.findOverlappingIntervals(startPosition, endPosition);
1706
2046
  }
1707
2047
 
2048
+ /**
2049
+ * Applies a function to each interval in this collection.
2050
+ */
1708
2051
  public map(fn: (interval: TInterval) => void) {
1709
- if (!this.attached) {
2052
+ if (!this.localCollection) {
1710
2053
  throw new LoggingError("attachSequence must be called");
1711
2054
  }
1712
2055
 
1713
2056
  this.localCollection.map(fn);
1714
2057
  }
1715
2058
 
1716
- public previousInterval(pos: number): TInterval {
1717
- if (!this.attached) {
2059
+ public previousInterval(pos: number): TInterval | undefined {
2060
+ if (!this.localCollection) {
1718
2061
  throw new LoggingError("attachSequence must be called");
1719
2062
  }
1720
2063
 
1721
2064
  return this.localCollection.previousInterval(pos);
1722
2065
  }
1723
2066
 
1724
- public nextInterval(pos: number): TInterval {
1725
- if (!this.attached) {
2067
+ public nextInterval(pos: number): TInterval | undefined {
2068
+ if (!this.localCollection) {
1726
2069
  throw new LoggingError("attachSequence must be called");
1727
2070
  }
1728
2071