@fluidframework/sequence 2.31.1 → 2.33.0-333010

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 (130) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/intervalCollection.d.ts +11 -13
  7. package/dist/intervalCollection.d.ts.map +1 -1
  8. package/dist/intervalCollection.js +96 -149
  9. package/dist/intervalCollection.js.map +1 -1
  10. package/dist/intervalCollectionMap.d.ts +4 -4
  11. package/dist/intervalCollectionMap.d.ts.map +1 -1
  12. package/dist/intervalCollectionMap.js +16 -49
  13. package/dist/intervalCollectionMap.js.map +1 -1
  14. package/dist/intervalCollectionMapInterfaces.d.ts +21 -15
  15. package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -1
  16. package/dist/intervalCollectionMapInterfaces.js.map +1 -1
  17. package/dist/intervalIndex/endpointInRangeIndex.d.ts.map +1 -1
  18. package/dist/intervalIndex/endpointInRangeIndex.js +2 -2
  19. package/dist/intervalIndex/endpointInRangeIndex.js.map +1 -1
  20. package/dist/intervalIndex/endpointIndex.d.ts.map +1 -1
  21. package/dist/intervalIndex/endpointIndex.js +2 -3
  22. package/dist/intervalIndex/endpointIndex.js.map +1 -1
  23. package/dist/intervalIndex/idIntervalIndex.d.ts.map +1 -1
  24. package/dist/intervalIndex/idIntervalIndex.js +0 -7
  25. package/dist/intervalIndex/idIntervalIndex.js.map +1 -1
  26. package/dist/intervalIndex/index.d.ts +0 -1
  27. package/dist/intervalIndex/index.d.ts.map +1 -1
  28. package/dist/intervalIndex/index.js +1 -3
  29. package/dist/intervalIndex/index.js.map +1 -1
  30. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
  31. package/dist/intervalIndex/overlappingIntervalsIndex.js +2 -2
  32. package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
  33. package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
  34. package/dist/intervalIndex/startpointInRangeIndex.js +2 -2
  35. package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -1
  36. package/dist/intervals/index.d.ts +2 -2
  37. package/dist/intervals/index.d.ts.map +1 -1
  38. package/dist/intervals/index.js +3 -1
  39. package/dist/intervals/index.js.map +1 -1
  40. package/dist/intervals/intervalUtils.d.ts +1 -5
  41. package/dist/intervals/intervalUtils.d.ts.map +1 -1
  42. package/dist/intervals/intervalUtils.js.map +1 -1
  43. package/dist/intervals/sequenceInterval.d.ts +20 -9
  44. package/dist/intervals/sequenceInterval.d.ts.map +1 -1
  45. package/dist/intervals/sequenceInterval.js +82 -27
  46. package/dist/intervals/sequenceInterval.js.map +1 -1
  47. package/dist/packageVersion.d.ts +1 -1
  48. package/dist/packageVersion.d.ts.map +1 -1
  49. package/dist/packageVersion.js +1 -1
  50. package/dist/packageVersion.js.map +1 -1
  51. package/dist/revertibles.d.ts.map +1 -1
  52. package/dist/revertibles.js +8 -7
  53. package/dist/revertibles.js.map +1 -1
  54. package/lib/index.d.ts +1 -1
  55. package/lib/index.d.ts.map +1 -1
  56. package/lib/index.js +1 -1
  57. package/lib/index.js.map +1 -1
  58. package/lib/intervalCollection.d.ts +11 -13
  59. package/lib/intervalCollection.d.ts.map +1 -1
  60. package/lib/intervalCollection.js +98 -151
  61. package/lib/intervalCollection.js.map +1 -1
  62. package/lib/intervalCollectionMap.d.ts +4 -4
  63. package/lib/intervalCollectionMap.d.ts.map +1 -1
  64. package/lib/intervalCollectionMap.js +17 -50
  65. package/lib/intervalCollectionMap.js.map +1 -1
  66. package/lib/intervalCollectionMapInterfaces.d.ts +21 -15
  67. package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
  68. package/lib/intervalCollectionMapInterfaces.js.map +1 -1
  69. package/lib/intervalIndex/endpointInRangeIndex.d.ts.map +1 -1
  70. package/lib/intervalIndex/endpointInRangeIndex.js +3 -3
  71. package/lib/intervalIndex/endpointInRangeIndex.js.map +1 -1
  72. package/lib/intervalIndex/endpointIndex.d.ts.map +1 -1
  73. package/lib/intervalIndex/endpointIndex.js +3 -4
  74. package/lib/intervalIndex/endpointIndex.js.map +1 -1
  75. package/lib/intervalIndex/idIntervalIndex.d.ts.map +1 -1
  76. package/lib/intervalIndex/idIntervalIndex.js +0 -7
  77. package/lib/intervalIndex/idIntervalIndex.js.map +1 -1
  78. package/lib/intervalIndex/index.d.ts +0 -1
  79. package/lib/intervalIndex/index.d.ts.map +1 -1
  80. package/lib/intervalIndex/index.js +0 -1
  81. package/lib/intervalIndex/index.js.map +1 -1
  82. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
  83. package/lib/intervalIndex/overlappingIntervalsIndex.js +3 -3
  84. package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
  85. package/lib/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
  86. package/lib/intervalIndex/startpointInRangeIndex.js +3 -3
  87. package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -1
  88. package/lib/intervals/index.d.ts +2 -2
  89. package/lib/intervals/index.d.ts.map +1 -1
  90. package/lib/intervals/index.js +1 -1
  91. package/lib/intervals/index.js.map +1 -1
  92. package/lib/intervals/intervalUtils.d.ts +1 -5
  93. package/lib/intervals/intervalUtils.d.ts.map +1 -1
  94. package/lib/intervals/intervalUtils.js.map +1 -1
  95. package/lib/intervals/sequenceInterval.d.ts +20 -9
  96. package/lib/intervals/sequenceInterval.d.ts.map +1 -1
  97. package/lib/intervals/sequenceInterval.js +81 -28
  98. package/lib/intervals/sequenceInterval.js.map +1 -1
  99. package/lib/packageVersion.d.ts +1 -1
  100. package/lib/packageVersion.d.ts.map +1 -1
  101. package/lib/packageVersion.js +1 -1
  102. package/lib/packageVersion.js.map +1 -1
  103. package/lib/revertibles.d.ts.map +1 -1
  104. package/lib/revertibles.js +8 -7
  105. package/lib/revertibles.js.map +1 -1
  106. package/package.json +19 -18
  107. package/src/index.ts +0 -1
  108. package/src/intervalCollection.ts +135 -198
  109. package/src/intervalCollectionMap.ts +19 -61
  110. package/src/intervalCollectionMapInterfaces.ts +33 -29
  111. package/src/intervalIndex/endpointInRangeIndex.ts +3 -15
  112. package/src/intervalIndex/endpointIndex.ts +3 -17
  113. package/src/intervalIndex/idIntervalIndex.ts +0 -7
  114. package/src/intervalIndex/index.ts +0 -1
  115. package/src/intervalIndex/overlappingIntervalsIndex.ts +3 -12
  116. package/src/intervalIndex/startpointInRangeIndex.ts +3 -15
  117. package/src/intervals/index.ts +2 -1
  118. package/src/intervals/intervalUtils.ts +0 -7
  119. package/src/intervals/sequenceInterval.ts +124 -33
  120. package/src/packageVersion.ts +1 -1
  121. package/src/revertibles.ts +8 -7
  122. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +0 -11
  123. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +0 -1
  124. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +0 -38
  125. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +0 -1
  126. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +0 -11
  127. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +0 -1
  128. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +0 -34
  129. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +0 -1
  130. package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +0 -80
@@ -4,13 +4,12 @@
4
4
  */
5
5
  /* eslint-disable no-bitwise */
6
6
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
7
- import { assert } from "@fluidframework/core-utils/internal";
8
- import { DetachedReferencePosition, ReferenceType, SlidingPreference, UnassignedSequenceNumber, UniversalSequenceNumber, addProperties, getSlideToSegoff, refTypeIncludesFlag, reservedRangeLabelsKey, Side, endpointPosAndSide, PropertiesManager, createMap, } from "@fluidframework/merge-tree/internal";
7
+ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
8
+ import { DetachedReferencePosition, ReferenceType, SlidingPreference, getSlideToSegoff, refTypeIncludesFlag, reservedRangeLabelsKey, Side, endpointPosAndSide, createLocalReconnectingPerspective, } from "@fluidframework/merge-tree/internal";
9
9
  import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
10
10
  import { v4 as uuid } from "uuid";
11
11
  import { createIdIntervalIndex, EndpointIndex, OverlappingIntervalsIndex, } from "./intervalIndex/index.js";
12
- import { IntervalDeltaOpType, IntervalStickiness, IntervalType, createPositionReferenceFromSegoff, createSequenceInterval, endReferenceSlidingPreference, startReferenceSlidingPreference, } from "./intervals/index.js";
13
- export const reservedIntervalIdKey = "intervalId";
12
+ import { IntervalStickiness, IntervalType, createPositionReferenceFromSegoff, createSequenceInterval, endReferenceSlidingPreference, getSerializedProperties, startReferenceSlidingPreference, } from "./intervals/index.js";
14
13
  export function sidesFromStickiness(stickiness) {
15
14
  const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
16
15
  const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
@@ -88,11 +87,6 @@ export class LocalIntervalCollection {
88
87
  this.endIntervalIndex,
89
88
  ]);
90
89
  }
91
- createLegacyId(start, end) {
92
- // Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
93
- // without ID's.
94
- return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
95
- }
96
90
  /**
97
91
  * Validates that a serialized interval has the ID property. Creates an ID
98
92
  * if one does not already exist
@@ -100,26 +94,6 @@ export class LocalIntervalCollection {
100
94
  * @param serializedInterval - The interval to be checked
101
95
  * @returns The interval's existing or newly created id
102
96
  */
103
- ensureSerializedId(serializedInterval) {
104
- let id = serializedInterval.properties?.[reservedIntervalIdKey];
105
- if (id === undefined) {
106
- // Back-compat: 0.39 and earlier did not have IDs on intervals. If an interval from such a client
107
- // comes over the wire, create a non-unique one based on start/end.
108
- // This will allow all clients to refer to this interval consistently.
109
- id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
110
- const newProps = {
111
- [reservedIntervalIdKey]: id,
112
- };
113
- serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
114
- }
115
- // Make the ID immutable for safety's sake.
116
- Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
117
- configurable: false,
118
- enumerable: true,
119
- writable: false,
120
- });
121
- return id;
122
- }
123
97
  removeIntervalFromIndexes(interval) {
124
98
  for (const index of this.indexes) {
125
99
  index.remove(interval);
@@ -135,28 +109,16 @@ export class LocalIntervalCollection {
135
109
  this.removeIntervalFromIndexes(interval);
136
110
  this.removeIntervalListeners(interval);
137
111
  }
138
- createInterval(start, end, intervalType, op) {
139
- return createSequenceInterval(this.label, start, end, this.client, intervalType, op, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint);
140
- }
141
- addInterval(start, end, intervalType, props, op) {
142
- const interval = this.createInterval(start, end, intervalType, op);
143
- if (interval) {
144
- if (!interval.properties) {
145
- interval.properties = createMap();
146
- }
147
- if (props) {
148
- // This check is intended to prevent scenarios where a random interval is created and then
149
- // inserted into a collection. The aim is to ensure that the collection is created first
150
- // then the user can create/add intervals based on the collection
151
- if (props[reservedRangeLabelsKey] !== undefined &&
152
- props[reservedRangeLabelsKey][0] !== this.label) {
153
- throw new LoggingError("Adding an interval that belongs to another interval collection is not permitted");
154
- }
155
- interval.properties = addProperties(interval.properties, props);
156
- }
157
- interval.properties[reservedIntervalIdKey] ??= uuid();
158
- this.add(interval);
112
+ addInterval(id, start, end, props, op) {
113
+ // This check is intended to prevent scenarios where a random interval is created and then
114
+ // inserted into a collection. The aim is to ensure that the collection is created first
115
+ // then the user can create/add intervals based on the collection
116
+ if (props?.[reservedRangeLabelsKey] !== undefined &&
117
+ props[reservedRangeLabelsKey][0] !== this.label) {
118
+ throw new LoggingError("Adding an interval that belongs to another interval collection is not permitted");
159
119
  }
120
+ const interval = createSequenceInterval(this.label, id, start, end, this.client, IntervalType.SlideOnRemove, op, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint, props);
121
+ this.add(interval);
160
122
  return interval;
161
123
  }
162
124
  linkEndpointsToInterval(interval) {
@@ -227,52 +189,6 @@ export class LocalIntervalCollection {
227
189
  interval.removePositionChangeListeners();
228
190
  }
229
191
  }
230
- LocalIntervalCollection.legacyIdPrefix = "legacy";
231
- const rebase = (collection, op, localOpMetadata) => {
232
- const { localSeq } = localOpMetadata;
233
- const rebasedValue = collection.rebaseLocalInterval(op.opName, op.value, localSeq);
234
- if (rebasedValue === undefined) {
235
- return undefined;
236
- }
237
- const rebasedOp = { ...op, value: rebasedValue };
238
- return { rebasedOp, rebasedLocalOpMetadata: localOpMetadata };
239
- };
240
- export const opsMap = {
241
- [IntervalDeltaOpType.ADD]: {
242
- process: (collection, params, local, op, localOpMetadata) => {
243
- // if params is undefined, the interval was deleted during
244
- // rebasing
245
- if (!params) {
246
- return;
247
- }
248
- assert(op !== undefined, 0x3fb /* op should exist here */);
249
- collection.ackAdd(params, local, op, localOpMetadata);
250
- },
251
- rebase,
252
- },
253
- [IntervalDeltaOpType.DELETE]: {
254
- process: (collection, params, local, op) => {
255
- assert(op !== undefined, 0x3fc /* op should exist here */);
256
- collection.ackDelete(params, local, op);
257
- },
258
- rebase: (collection, op, localOpMetadata) => {
259
- // Deletion of intervals is based on id, so requires no rebasing.
260
- return { rebasedOp: op, rebasedLocalOpMetadata: localOpMetadata };
261
- },
262
- },
263
- [IntervalDeltaOpType.CHANGE]: {
264
- process: (collection, params, local, op, localOpMetadata) => {
265
- // if params is undefined, the interval was deleted during
266
- // rebasing
267
- if (!params) {
268
- return;
269
- }
270
- assert(op !== undefined, 0x3fd /* op should exist here */);
271
- collection.ackChange(params, local, op, localOpMetadata);
272
- },
273
- rebase,
274
- },
275
- };
276
192
  class IntervalCollectionIterator {
277
193
  constructor(collection, iteratesForward = true, start, end) {
278
194
  this.results = [];
@@ -339,6 +255,64 @@ export class IntervalCollection extends TypedEventEmitter {
339
255
  }
340
256
  return true;
341
257
  }
258
+ process(op, local, message, localOpMetadata) {
259
+ const { opName, value } = op;
260
+ switch (opName) {
261
+ case "add": {
262
+ this.ackAdd(value, local, message, localOpMetadata);
263
+ break;
264
+ }
265
+ case "delete": {
266
+ this.ackDelete(value, local, message);
267
+ break;
268
+ }
269
+ case "change": {
270
+ this.ackChange(value, local, message, localOpMetadata);
271
+ break;
272
+ }
273
+ default:
274
+ unreachableCase(opName);
275
+ }
276
+ }
277
+ resubmitMessage(op, localOpMetadata) {
278
+ const { opName, value } = op;
279
+ const { localSeq } = localOpMetadata;
280
+ const rebasedValue = opName === "delete" ? value : this.rebaseLocalInterval(opName, value, localSeq);
281
+ if (rebasedValue === undefined) {
282
+ return undefined;
283
+ }
284
+ this.submitDelta({ opName, value: rebasedValue }, localOpMetadata);
285
+ }
286
+ applyStashedOp(op) {
287
+ const { opName, value } = op;
288
+ const { id, properties } = getSerializedProperties(value);
289
+ switch (opName) {
290
+ case "add": {
291
+ this.add({
292
+ id,
293
+ // Todo: we should improve typing so we know add ops always have start and end
294
+ start: toSequencePlace(value.start, value.startSide),
295
+ end: toSequencePlace(value.end, value.endSide),
296
+ props: properties,
297
+ });
298
+ break;
299
+ }
300
+ case "change": {
301
+ this.change(id, {
302
+ start: toOptionalSequencePlace(value.start, value.startSide),
303
+ end: toOptionalSequencePlace(value.end, value.endSide),
304
+ props: properties,
305
+ });
306
+ break;
307
+ }
308
+ case "delete": {
309
+ this.removeIntervalById(id);
310
+ break;
311
+ }
312
+ default:
313
+ throw new Error("unknown ops should not be stashed");
314
+ }
315
+ }
342
316
  rebasePositionWithSegmentSlide(pos, seqNumberFrom, localSeq) {
343
317
  if (!this.client) {
344
318
  throw new LoggingError("mergeTree client must exist");
@@ -353,7 +327,7 @@ export class IntervalCollection extends TypedEventEmitter {
353
327
  }, localSeq);
354
328
  // if segment is undefined, it slid off the string
355
329
  assert(segment !== undefined, 0x54e /* No segment found */);
356
- const segoff = getSlideToSegoff({ segment, offset }, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint) ?? segment;
330
+ const segoff = getSlideToSegoff({ segment, offset }, undefined, createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq), this.options.mergeTreeReferencesCanSlideToEndpoint) ?? segment;
357
331
  // case happens when rebasing op, but concurrently entire string has been deleted
358
332
  if (segoff.segment === undefined || segoff.offset === undefined) {
359
333
  return DetachedReferencePosition;
@@ -394,18 +368,15 @@ export class IntervalCollection extends TypedEventEmitter {
394
368
  this.localCollection = new LocalIntervalCollection(client, label, this.options, (interval, previousInterval) => this.emitChange(interval, previousInterval, true, true));
395
369
  if (this.savedSerializedIntervals) {
396
370
  for (const serializedInterval of this.savedSerializedIntervals) {
397
- this.localCollection.ensureSerializedId(serializedInterval);
398
- const { start: startPos, end: endPos, intervalType, properties, startSide, endSide, } = serializedInterval;
371
+ const { id, properties } = getSerializedProperties(serializedInterval);
372
+ const { start: startPos, end: endPos, intervalType, startSide, endSide, } = serializedInterval;
399
373
  const start = typeof startPos === "number" && startSide !== undefined
400
374
  ? { pos: startPos, side: startSide }
401
375
  : startPos;
402
376
  const end = typeof endPos === "number" && endSide !== undefined
403
377
  ? { pos: endPos, side: endSide }
404
378
  : endPos;
405
- const interval = createSequenceInterval(label, start, end, client, intervalType, undefined, true, this.options.mergeTreeReferencesCanSlideToEndpoint);
406
- if (properties) {
407
- interval.properties = addProperties(interval.properties, properties);
408
- }
379
+ const interval = createSequenceInterval(label, id, start, end, client, intervalType, undefined, true, this.options.mergeTreeReferencesCanSlideToEndpoint, properties);
409
380
  this.localCollection.add(interval);
410
381
  }
411
382
  }
@@ -451,7 +422,7 @@ export class IntervalCollection extends TypedEventEmitter {
451
422
  /**
452
423
  * {@inheritdoc IIntervalCollection.add}
453
424
  */
454
- add({ start, end, props, }) {
425
+ add({ id, start, end, props, }) {
455
426
  if (!this.localCollection) {
456
427
  throw new LoggingError("attach must be called prior to adding intervals");
457
428
  }
@@ -460,24 +431,14 @@ export class IntervalCollection extends TypedEventEmitter {
460
431
  endPos !== undefined &&
461
432
  startSide !== undefined &&
462
433
  endSide !== undefined, 0x793 /* start and end cannot be undefined because they were not passed in as undefined */);
463
- const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
464
434
  this.assertStickinessEnabled(start, end);
465
- const interval = this.localCollection.addInterval(toSequencePlace(startPos, startSide), toSequencePlace(endPos, endSide), IntervalType.SlideOnRemove, props);
435
+ const interval = this.localCollection.addInterval(id ?? uuid(), toSequencePlace(startPos, startSide), toSequencePlace(endPos, endSide), props);
466
436
  if (interval) {
467
437
  if (!this.isCollaborating) {
468
438
  setSlideOnRemove(interval.start);
469
439
  setSlideOnRemove(interval.end);
470
440
  }
471
- const serializedInterval = {
472
- start: startPos,
473
- end: endPos,
474
- intervalType: IntervalType.SlideOnRemove,
475
- properties: { ...interval.properties },
476
- sequenceNumber: this.client?.getCurrentSeq() ?? 0,
477
- stickiness,
478
- startSide,
479
- endSide,
480
- };
441
+ const serializedInterval = interval.serialize();
481
442
  const localSeq = this.getNextLocalSeq();
482
443
  if (this.isCollaborating) {
483
444
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -554,29 +515,21 @@ export class IntervalCollection extends TypedEventEmitter {
554
515
  let deltaProps;
555
516
  let newInterval;
556
517
  if (props !== undefined) {
557
- interval.propertyManager ??= new PropertiesManager();
558
- deltaProps = interval.propertyManager.handleProperties({ props }, interval, this.isCollaborating ? UnassignedSequenceNumber : UniversalSequenceNumber, UniversalSequenceNumber, true);
518
+ deltaProps = interval.changeProperties(props);
559
519
  }
560
- if (start !== undefined && end !== undefined) {
520
+ const changeEndpoints = start !== undefined && end !== undefined;
521
+ if (changeEndpoints) {
561
522
  newInterval = this.localCollection.changeInterval(interval, start, end);
562
523
  if (!this.isCollaborating && newInterval !== undefined) {
563
524
  setSlideOnRemove(newInterval.start);
564
525
  setSlideOnRemove(newInterval.end);
565
526
  }
566
527
  }
567
- const serializedInterval = interval.serialize();
568
- const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
569
- const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
570
- serializedInterval.start = startPos;
571
- serializedInterval.end = endPos;
572
- serializedInterval.startSide = startSide;
573
- serializedInterval.endSide = endSide;
574
- serializedInterval.stickiness = stickiness;
575
528
  // Emit a property bag containing the ID and the other (if any) properties changed
576
- serializedInterval.properties = {
577
- [reservedIntervalIdKey]: interval.getIntervalId(),
578
- ...props,
579
- };
529
+ const serializedInterval = (newInterval ?? interval).serializeDelta({
530
+ props,
531
+ includeEndpoints: changeEndpoints,
532
+ });
580
533
  const localSeq = this.getNextLocalSeq();
581
534
  if (this.isCollaborating) {
582
535
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -626,7 +579,7 @@ export class IntervalCollection extends TypedEventEmitter {
626
579
  }
627
580
  removePendingChange(serializedInterval) {
628
581
  // Change ops always have an ID.
629
- const id = serializedInterval.properties?.[reservedIntervalIdKey];
582
+ const { id } = getSerializedProperties(serializedInterval);
630
583
  if (serializedInterval.start !== undefined) {
631
584
  this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
632
585
  }
@@ -668,7 +621,7 @@ export class IntervalCollection extends TypedEventEmitter {
668
621
  // Note that the ID is in the property bag only to allow us to find the interval.
669
622
  // This API cannot change the ID, and writing to the ID property will result in an exception. So we
670
623
  // strip it out of the properties here.
671
- const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties ?? {};
624
+ const { id, properties } = getSerializedProperties(serializedInterval);
672
625
  assert(id !== undefined, 0x3fe /* id must exist on the interval */);
673
626
  const interval = this.getIntervalById(id);
674
627
  if (!interval) {
@@ -676,11 +629,7 @@ export class IntervalCollection extends TypedEventEmitter {
676
629
  return;
677
630
  }
678
631
  if (local) {
679
- interval.propertyManager ??= new PropertiesManager();
680
- // Let the propertyManager prune its pending change-properties set.
681
- interval.propertyManager.ack(op.sequenceNumber, op.minimumSequenceNumber, {
682
- props: newProps,
683
- });
632
+ interval.ackPropertiesChange(properties, op);
684
633
  this.ackInterval(interval, op);
685
634
  }
686
635
  else {
@@ -702,15 +651,14 @@ export class IntervalCollection extends TypedEventEmitter {
702
651
  newInterval =
703
652
  this.localCollection.changeInterval(interval, toOptionalSequencePlace(start, serializedInterval.startSide ?? Side.Before), toOptionalSequencePlace(end, serializedInterval.endSide ?? Side.Before), op) ?? interval;
704
653
  }
705
- newInterval.propertyManager ??= new PropertiesManager();
706
- const deltaProps = newInterval.propertyManager.handleProperties({ props: newProps }, newInterval, op.sequenceNumber, op.minimumSequenceNumber, true);
654
+ const deltaProps = newInterval.changeProperties(properties, op);
707
655
  if (this.onDeserialize) {
708
656
  this.onDeserialize(newInterval);
709
657
  }
710
658
  if (newInterval !== interval) {
711
659
  this.emitChange(newInterval, interval, local, false, op);
712
660
  }
713
- const changedProperties = Object.keys(newProps).length > 0;
661
+ const changedProperties = Object.keys(properties).length > 0;
714
662
  if (changedProperties) {
715
663
  this.emit("propertyChanged", interval, deltaProps, local, op);
716
664
  this.emit("changed", interval, deltaProps, undefined, local, false);
@@ -747,9 +695,9 @@ export class IntervalCollection extends TypedEventEmitter {
747
695
  throw new LoggingError("attachSequence must be called");
748
696
  }
749
697
  const { intervalType, properties, stickiness, startSide, endSide } = serializedInterval;
698
+ const { id } = getSerializedProperties(serializedInterval);
750
699
  const { start: startRebased, end: endRebased } = this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
751
- const intervalId = properties?.[reservedIntervalIdKey];
752
- const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(intervalId);
700
+ const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(id);
753
701
  const rebased = {
754
702
  start: startRebased,
755
703
  end: endRebased,
@@ -762,9 +710,9 @@ export class IntervalCollection extends TypedEventEmitter {
762
710
  };
763
711
  if (opName === "change" &&
764
712
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when .hasPendingChangeStart returns false.
765
- (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
713
+ (this.hasPendingChangeStart(id) || this.hasPendingChangeEnd(id))) {
766
714
  this.removePendingChange(serializedInterval);
767
- this.addPendingChange(intervalId, rebased);
715
+ this.addPendingChange(id, rebased);
768
716
  }
769
717
  // if the interval slid off the string, rebase the op to be a noop and delete the interval.
770
718
  if (!this.options.mergeTreeReferencesCanSlideToEndpoint &&
@@ -792,7 +740,7 @@ export class IntervalCollection extends TypedEventEmitter {
792
740
  if (segoff.segment?.localRefs?.has(lref) !== true) {
793
741
  return undefined;
794
742
  }
795
- const newSegoff = getSlideToSegoff(segoff, slidingPreference, this.options.mergeTreeReferencesCanSlideToEndpoint);
743
+ const newSegoff = getSlideToSegoff(segoff, slidingPreference, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint);
796
744
  const value = segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
797
745
  ? undefined
798
746
  : newSegoff;
@@ -805,7 +753,7 @@ export class IntervalCollection extends TypedEventEmitter {
805
753
  }
806
754
  const newStart = this.getSlideToSegment(interval.start, startReferenceSlidingPreference(interval.stickiness));
807
755
  const newEnd = this.getSlideToSegment(interval.end, endReferenceSlidingPreference(interval.stickiness));
808
- const id = interval.properties[reservedIntervalIdKey];
756
+ const id = interval.getIntervalId();
809
757
  const hasPendingStartChange = this.hasPendingChangeStart(id);
810
758
  const hasPendingEndChange = this.hasPendingChangeEnd(id);
811
759
  if (!hasPendingStartChange) {
@@ -858,10 +806,10 @@ export class IntervalCollection extends TypedEventEmitter {
858
806
  }
859
807
  }
860
808
  ackAdd(serializedInterval, local, op, localOpMetadata) {
809
+ const { id, properties } = getSerializedProperties(serializedInterval);
861
810
  if (local) {
862
811
  assert(localOpMetadata !== undefined, 0x553 /* op metadata should be defined for local op */);
863
812
  this.localSeqToSerializedInterval.delete(localOpMetadata.localSeq);
864
- const id = serializedInterval.properties?.[reservedIntervalIdKey];
865
813
  const localInterval = this.getIntervalById(id);
866
814
  if (localInterval) {
867
815
  this.ackInterval(localInterval, op);
@@ -871,8 +819,7 @@ export class IntervalCollection extends TypedEventEmitter {
871
819
  if (!this.localCollection) {
872
820
  throw new LoggingError("attachSequence must be called");
873
821
  }
874
- this.localCollection.ensureSerializedId(serializedInterval);
875
- const interval = this.localCollection.addInterval(toSequencePlace(serializedInterval.start, serializedInterval.startSide ?? Side.Before), toSequencePlace(serializedInterval.end, serializedInterval.endSide ?? Side.Before), serializedInterval.intervalType, serializedInterval.properties, op);
822
+ const interval = this.localCollection.addInterval(id, toSequencePlace(serializedInterval.start, serializedInterval.startSide ?? Side.Before), toSequencePlace(serializedInterval.end, serializedInterval.endSide ?? Side.Before), properties, op);
876
823
  if (interval) {
877
824
  if (this.onDeserialize) {
878
825
  this.onDeserialize(interval);
@@ -891,7 +838,7 @@ export class IntervalCollection extends TypedEventEmitter {
891
838
  if (!this.localCollection) {
892
839
  throw new LoggingError("attach must be called prior to deleting intervals");
893
840
  }
894
- const id = this.localCollection.ensureSerializedId(serializedInterval);
841
+ const { id } = getSerializedProperties(serializedInterval);
895
842
  const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
896
843
  if (interval) {
897
844
  this.deleteExistingInterval(interval, local, op);