@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
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
9
9
  import { IEvent } from "@fluidframework/core-interfaces";
10
- import { assert } from "@fluidframework/core-utils/internal";
10
+ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
11
11
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
12
12
  import {
13
13
  Client,
@@ -17,24 +17,19 @@ import {
17
17
  PropertySet,
18
18
  ReferenceType,
19
19
  SlidingPreference,
20
- UnassignedSequenceNumber,
21
- UniversalSequenceNumber,
22
- addProperties,
23
20
  getSlideToSegoff,
24
21
  refTypeIncludesFlag,
25
22
  reservedRangeLabelsKey,
26
23
  Side,
27
24
  SequencePlace,
28
25
  endpointPosAndSide,
29
- PropertiesManager,
30
26
  type ISegmentInternal,
31
- createMap,
27
+ createLocalReconnectingPerspective,
32
28
  } from "@fluidframework/merge-tree/internal";
33
29
  import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
34
30
  import { v4 as uuid } from "uuid";
35
31
 
36
32
  import {
37
- IIntervalCollectionOperation,
38
33
  IMapMessageLocalMetadata,
39
34
  SequenceOptions,
40
35
  type IIntervalCollectionTypeOperationValue,
@@ -53,7 +48,6 @@ import {
53
48
  import {
54
49
  CompressedSerializedInterval,
55
50
  ISerializedInterval,
56
- IntervalDeltaOpType,
57
51
  IntervalStickiness,
58
52
  IntervalType,
59
53
  SequenceInterval,
@@ -62,13 +56,11 @@ import {
62
56
  createPositionReferenceFromSegoff,
63
57
  createSequenceInterval,
64
58
  endReferenceSlidingPreference,
59
+ getSerializedProperties,
65
60
  startReferenceSlidingPreference,
66
61
  type ISerializableInterval,
67
- type ISerializableIntervalPrivate,
68
62
  } from "./intervals/index.js";
69
63
 
70
- export const reservedIntervalIdKey = "intervalId";
71
-
72
64
  export type ISerializedIntervalCollectionV1 = ISerializedInterval[];
73
65
 
74
66
  export interface ISerializedIntervalCollectionV2 {
@@ -165,7 +157,6 @@ export function computeStickinessFromSide(
165
157
  }
166
158
 
167
159
  export class LocalIntervalCollection {
168
- private static readonly legacyIdPrefix = "legacy";
169
160
  public readonly overlappingIntervalsIndex: ISequenceOverlappingIntervalsIndex;
170
161
  public readonly idIntervalIndex: IIdIntervalIndex;
171
162
  public readonly endIntervalIndex: IEndpointIndex;
@@ -191,15 +182,6 @@ export class LocalIntervalCollection {
191
182
  ]);
192
183
  }
193
184
 
194
- public createLegacyId(
195
- start: number | "start" | "end",
196
- end: number | "start" | "end",
197
- ): string {
198
- // Create a non-unique ID based on start and end to be used on intervals that come from legacy clients
199
- // without ID's.
200
- return `${LocalIntervalCollection.legacyIdPrefix}${start}-${end}`;
201
- }
202
-
203
185
  /**
204
186
  * Validates that a serialized interval has the ID property. Creates an ID
205
187
  * if one does not already exist
@@ -207,27 +189,6 @@ export class LocalIntervalCollection {
207
189
  * @param serializedInterval - The interval to be checked
208
190
  * @returns The interval's existing or newly created id
209
191
  */
210
- public ensureSerializedId(serializedInterval: ISerializedInterval): string {
211
- let id: string | undefined = serializedInterval.properties?.[reservedIntervalIdKey];
212
- if (id === undefined) {
213
- // Back-compat: 0.39 and earlier did not have IDs on intervals. If an interval from such a client
214
- // comes over the wire, create a non-unique one based on start/end.
215
- // This will allow all clients to refer to this interval consistently.
216
- id = this.createLegacyId(serializedInterval.start, serializedInterval.end);
217
- const newProps = {
218
- [reservedIntervalIdKey]: id,
219
- };
220
- serializedInterval.properties = addProperties(serializedInterval.properties, newProps);
221
- }
222
- // Make the ID immutable for safety's sake.
223
- Object.defineProperty(serializedInterval.properties, reservedIntervalIdKey, {
224
- configurable: false,
225
- enumerable: true,
226
- writable: false,
227
- });
228
-
229
- return id;
230
- }
231
192
 
232
193
  private removeIntervalFromIndexes(interval: SequenceIntervalClass) {
233
194
  for (const index of this.indexes) {
@@ -248,54 +209,38 @@ export class LocalIntervalCollection {
248
209
  this.removeIntervalListeners(interval);
249
210
  }
250
211
 
251
- public createInterval(
212
+ public addInterval(
213
+ id: string,
252
214
  start: SequencePlace,
253
215
  end: SequencePlace,
254
- intervalType: IntervalType,
216
+ props?: PropertySet,
255
217
  op?: ISequencedDocumentMessage,
256
- ): SequenceIntervalClass {
257
- return createSequenceInterval(
218
+ ) {
219
+ // This check is intended to prevent scenarios where a random interval is created and then
220
+ // inserted into a collection. The aim is to ensure that the collection is created first
221
+ // then the user can create/add intervals based on the collection
222
+ if (
223
+ props?.[reservedRangeLabelsKey] !== undefined &&
224
+ props[reservedRangeLabelsKey][0] !== this.label
225
+ ) {
226
+ throw new LoggingError(
227
+ "Adding an interval that belongs to another interval collection is not permitted",
228
+ );
229
+ }
230
+ const interval: SequenceIntervalClass = createSequenceInterval(
258
231
  this.label,
232
+ id,
259
233
  start,
260
234
  end,
261
235
  this.client,
262
- intervalType,
236
+ IntervalType.SlideOnRemove,
263
237
  op,
264
238
  undefined,
265
239
  this.options.mergeTreeReferencesCanSlideToEndpoint,
240
+ props,
266
241
  );
267
- }
268
242
 
269
- public addInterval(
270
- start: SequencePlace,
271
- end: SequencePlace,
272
- intervalType: IntervalType,
273
- props?: PropertySet,
274
- op?: ISequencedDocumentMessage,
275
- ) {
276
- const interval: SequenceIntervalClass = this.createInterval(start, end, intervalType, op);
277
- if (interval) {
278
- if (!interval.properties) {
279
- interval.properties = createMap<any>();
280
- }
281
-
282
- if (props) {
283
- // This check is intended to prevent scenarios where a random interval is created and then
284
- // inserted into a collection. The aim is to ensure that the collection is created first
285
- // then the user can create/add intervals based on the collection
286
- if (
287
- props[reservedRangeLabelsKey] !== undefined &&
288
- props[reservedRangeLabelsKey][0] !== this.label
289
- ) {
290
- throw new LoggingError(
291
- "Adding an interval that belongs to another interval collection is not permitted",
292
- );
293
- }
294
- interval.properties = addProperties(interval.properties, props);
295
- }
296
- interval.properties[reservedIntervalIdKey] ??= uuid();
297
- this.add(interval);
298
- }
243
+ this.add(interval);
299
244
  return interval;
300
245
  }
301
246
 
@@ -405,55 +350,6 @@ export class LocalIntervalCollection {
405
350
  }
406
351
  }
407
352
 
408
- const rebase: IIntervalCollectionOperation["rebase"] = (collection, op, localOpMetadata) => {
409
- const { localSeq } = localOpMetadata;
410
- const rebasedValue = collection.rebaseLocalInterval(op.opName, op.value, localSeq);
411
- if (rebasedValue === undefined) {
412
- return undefined;
413
- }
414
- const rebasedOp = { ...op, value: rebasedValue };
415
- return { rebasedOp, rebasedLocalOpMetadata: localOpMetadata };
416
- };
417
-
418
- export const opsMap: Record<IntervalDeltaOpType, IIntervalCollectionOperation> = {
419
- [IntervalDeltaOpType.ADD]: {
420
- process: (collection, params, local, op, localOpMetadata) => {
421
- // if params is undefined, the interval was deleted during
422
- // rebasing
423
- if (!params) {
424
- return;
425
- }
426
- assert(op !== undefined, 0x3fb /* op should exist here */);
427
- collection.ackAdd(params, local, op, localOpMetadata);
428
- },
429
- rebase,
430
- },
431
-
432
- [IntervalDeltaOpType.DELETE]: {
433
- process: (collection, params, local, op) => {
434
- assert(op !== undefined, 0x3fc /* op should exist here */);
435
- collection.ackDelete(params, local, op);
436
- },
437
- rebase: (collection, op, localOpMetadata) => {
438
- // Deletion of intervals is based on id, so requires no rebasing.
439
- return { rebasedOp: op, rebasedLocalOpMetadata: localOpMetadata };
440
- },
441
- },
442
-
443
- [IntervalDeltaOpType.CHANGE]: {
444
- process: (collection, params, local, op, localOpMetadata) => {
445
- // if params is undefined, the interval was deleted during
446
- // rebasing
447
- if (!params) {
448
- return;
449
- }
450
- assert(op !== undefined, 0x3fd /* op should exist here */);
451
- collection.ackChange(params, local, op, localOpMetadata);
452
- },
453
- rebase,
454
- },
455
- };
456
-
457
353
  /**
458
354
  * @legacy
459
355
  * @alpha
@@ -1179,6 +1075,79 @@ export class IntervalCollection
1179
1075
  return true;
1180
1076
  }
1181
1077
 
1078
+ public process(
1079
+ op: IIntervalCollectionTypeOperationValue,
1080
+ local: boolean,
1081
+ message: ISequencedDocumentMessage,
1082
+ localOpMetadata: IMapMessageLocalMetadata,
1083
+ ) {
1084
+ const { opName, value } = op;
1085
+ switch (opName) {
1086
+ case "add": {
1087
+ this.ackAdd(value, local, message, localOpMetadata);
1088
+ break;
1089
+ }
1090
+
1091
+ case "delete": {
1092
+ this.ackDelete(value, local, message);
1093
+ break;
1094
+ }
1095
+
1096
+ case "change": {
1097
+ this.ackChange(value, local, message, localOpMetadata);
1098
+ break;
1099
+ }
1100
+ default:
1101
+ unreachableCase(opName);
1102
+ }
1103
+ }
1104
+
1105
+ public resubmitMessage(
1106
+ op: IIntervalCollectionTypeOperationValue,
1107
+ localOpMetadata: IMapMessageLocalMetadata,
1108
+ ): void {
1109
+ const { opName, value } = op;
1110
+ const { localSeq } = localOpMetadata;
1111
+ const rebasedValue =
1112
+ opName === "delete" ? value : this.rebaseLocalInterval(opName, value, localSeq);
1113
+ if (rebasedValue === undefined) {
1114
+ return undefined;
1115
+ }
1116
+
1117
+ this.submitDelta({ opName, value: rebasedValue as any }, localOpMetadata);
1118
+ }
1119
+
1120
+ public applyStashedOp(op: IIntervalCollectionTypeOperationValue): void {
1121
+ const { opName, value } = op;
1122
+ const { id, properties } = getSerializedProperties(value);
1123
+ switch (opName) {
1124
+ case "add": {
1125
+ this.add({
1126
+ id,
1127
+ // Todo: we should improve typing so we know add ops always have start and end
1128
+ start: toSequencePlace(value.start, value.startSide),
1129
+ end: toSequencePlace(value.end, value.endSide),
1130
+ props: properties,
1131
+ });
1132
+ break;
1133
+ }
1134
+ case "change": {
1135
+ this.change(id, {
1136
+ start: toOptionalSequencePlace(value.start, value.startSide),
1137
+ end: toOptionalSequencePlace(value.end, value.endSide),
1138
+ props: properties,
1139
+ });
1140
+ break;
1141
+ }
1142
+ case "delete": {
1143
+ this.removeIntervalById(id);
1144
+ break;
1145
+ }
1146
+ default:
1147
+ throw new Error("unknown ops should not be stashed");
1148
+ }
1149
+ }
1150
+
1182
1151
  private rebasePositionWithSegmentSlide(
1183
1152
  pos: number | "start" | "end",
1184
1153
  seqNumberFrom: number,
@@ -1209,6 +1178,7 @@ export class IntervalCollection
1209
1178
  getSlideToSegoff(
1210
1179
  { segment, offset },
1211
1180
  undefined,
1181
+ createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq),
1212
1182
  this.options.mergeTreeReferencesCanSlideToEndpoint,
1213
1183
  ) ?? segment;
1214
1184
 
@@ -1274,12 +1244,11 @@ export class IntervalCollection
1274
1244
  );
1275
1245
  if (this.savedSerializedIntervals) {
1276
1246
  for (const serializedInterval of this.savedSerializedIntervals) {
1277
- this.localCollection.ensureSerializedId(serializedInterval);
1247
+ const { id, properties } = getSerializedProperties(serializedInterval);
1278
1248
  const {
1279
1249
  start: startPos,
1280
1250
  end: endPos,
1281
1251
  intervalType,
1282
- properties,
1283
1252
  startSide,
1284
1253
  endSide,
1285
1254
  } = serializedInterval;
@@ -1293,6 +1262,7 @@ export class IntervalCollection
1293
1262
  : endPos;
1294
1263
  const interval = createSequenceInterval(
1295
1264
  label,
1265
+ id,
1296
1266
  start,
1297
1267
  end,
1298
1268
  client,
@@ -1300,10 +1270,8 @@ export class IntervalCollection
1300
1270
  undefined,
1301
1271
  true,
1302
1272
  this.options.mergeTreeReferencesCanSlideToEndpoint,
1273
+ properties,
1303
1274
  );
1304
- if (properties) {
1305
- interval.properties = addProperties(interval.properties, properties);
1306
- }
1307
1275
  this.localCollection.add(interval);
1308
1276
  }
1309
1277
  }
@@ -1345,7 +1313,7 @@ export class IntervalCollection
1345
1313
  /**
1346
1314
  * {@inheritdoc IIntervalCollection.getIntervalById}
1347
1315
  */
1348
- public getIntervalById(id: string): ISerializableIntervalPrivate | undefined {
1316
+ public getIntervalById(id: string): SequenceIntervalClass | undefined {
1349
1317
  if (!this.localCollection) {
1350
1318
  throw new LoggingError("attach must be called before accessing intervals");
1351
1319
  }
@@ -1367,10 +1335,12 @@ export class IntervalCollection
1367
1335
  * {@inheritdoc IIntervalCollection.add}
1368
1336
  */
1369
1337
  public add({
1338
+ id,
1370
1339
  start,
1371
1340
  end,
1372
1341
  props,
1373
1342
  }: {
1343
+ id?: string;
1374
1344
  start: SequencePlace;
1375
1345
  end: SequencePlace;
1376
1346
  props?: PropertySet;
@@ -1389,14 +1359,12 @@ export class IntervalCollection
1389
1359
  0x793 /* start and end cannot be undefined because they were not passed in as undefined */,
1390
1360
  );
1391
1361
 
1392
- const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
1393
-
1394
1362
  this.assertStickinessEnabled(start, end);
1395
1363
 
1396
1364
  const interval: SequenceIntervalClass = this.localCollection.addInterval(
1365
+ id ?? uuid(),
1397
1366
  toSequencePlace(startPos, startSide),
1398
1367
  toSequencePlace(endPos, endSide),
1399
- IntervalType.SlideOnRemove,
1400
1368
  props,
1401
1369
  );
1402
1370
 
@@ -1405,16 +1373,7 @@ export class IntervalCollection
1405
1373
  setSlideOnRemove(interval.start);
1406
1374
  setSlideOnRemove(interval.end);
1407
1375
  }
1408
- const serializedInterval: ISerializedInterval = {
1409
- start: startPos,
1410
- end: endPos,
1411
- intervalType: IntervalType.SlideOnRemove,
1412
- properties: { ...interval.properties },
1413
- sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1414
- stickiness,
1415
- startSide,
1416
- endSide,
1417
- };
1376
+ const serializedInterval: ISerializedInterval = interval.serialize();
1418
1377
  const localSeq = this.getNextLocalSeq();
1419
1378
  if (this.isCollaborating) {
1420
1379
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -1518,35 +1477,24 @@ export class IntervalCollection
1518
1477
  let deltaProps: PropertySet | undefined;
1519
1478
  let newInterval: SequenceIntervalClass | undefined;
1520
1479
  if (props !== undefined) {
1521
- interval.propertyManager ??= new PropertiesManager();
1522
- deltaProps = interval.propertyManager.handleProperties(
1523
- { props },
1524
- interval,
1525
- this.isCollaborating ? UnassignedSequenceNumber : UniversalSequenceNumber,
1526
- UniversalSequenceNumber,
1527
- true,
1528
- );
1480
+ deltaProps = interval.changeProperties(props);
1529
1481
  }
1530
- if (start !== undefined && end !== undefined) {
1482
+ const changeEndpoints = start !== undefined && end !== undefined;
1483
+ if (changeEndpoints) {
1531
1484
  newInterval = this.localCollection.changeInterval(interval, start, end);
1532
1485
  if (!this.isCollaborating && newInterval !== undefined) {
1533
1486
  setSlideOnRemove(newInterval.start);
1534
1487
  setSlideOnRemove(newInterval.end);
1535
1488
  }
1536
1489
  }
1537
- const serializedInterval: SerializedIntervalDelta = interval.serialize();
1538
- const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
1539
- const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
1540
- serializedInterval.start = startPos;
1541
- serializedInterval.end = endPos;
1542
- serializedInterval.startSide = startSide;
1543
- serializedInterval.endSide = endSide;
1544
- serializedInterval.stickiness = stickiness;
1545
1490
  // Emit a property bag containing the ID and the other (if any) properties changed
1546
- serializedInterval.properties = {
1547
- [reservedIntervalIdKey]: interval.getIntervalId(),
1548
- ...props,
1549
- };
1491
+ const serializedInterval: SerializedIntervalDelta = (
1492
+ newInterval ?? interval
1493
+ ).serializeDelta({
1494
+ props,
1495
+ includeEndpoints: changeEndpoints,
1496
+ });
1497
+
1550
1498
  const localSeq = this.getNextLocalSeq();
1551
1499
  if (this.isCollaborating) {
1552
1500
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
@@ -1615,7 +1563,7 @@ export class IntervalCollection
1615
1563
 
1616
1564
  private removePendingChange(serializedInterval: SerializedIntervalDelta) {
1617
1565
  // Change ops always have an ID.
1618
- const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
1566
+ const { id } = getSerializedProperties(serializedInterval);
1619
1567
  if (serializedInterval.start !== undefined) {
1620
1568
  this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
1621
1569
  }
@@ -1655,7 +1603,7 @@ export class IntervalCollection
1655
1603
  }
1656
1604
 
1657
1605
  public ackChange(
1658
- serializedInterval: ISerializedInterval,
1606
+ serializedInterval: SerializedIntervalDelta,
1659
1607
  local: boolean,
1660
1608
  op: ISequencedDocumentMessage,
1661
1609
  localOpMetadata: IMapMessageLocalMetadata | undefined,
@@ -1677,20 +1625,16 @@ export class IntervalCollection
1677
1625
  // Note that the ID is in the property bag only to allow us to find the interval.
1678
1626
  // This API cannot change the ID, and writing to the ID property will result in an exception. So we
1679
1627
  // strip it out of the properties here.
1680
- const { [reservedIntervalIdKey]: id, ...newProps } = serializedInterval.properties ?? {};
1628
+ const { id, properties } = getSerializedProperties(serializedInterval);
1681
1629
  assert(id !== undefined, 0x3fe /* id must exist on the interval */);
1682
- const interval: ISerializableIntervalPrivate | undefined = this.getIntervalById(id);
1630
+ const interval: SequenceIntervalClass | undefined = this.getIntervalById(id);
1683
1631
  if (!interval) {
1684
1632
  // The interval has been removed locally; no-op.
1685
1633
  return;
1686
1634
  }
1687
1635
 
1688
1636
  if (local) {
1689
- interval.propertyManager ??= new PropertiesManager();
1690
- // Let the propertyManager prune its pending change-properties set.
1691
- interval.propertyManager.ack(op.sequenceNumber, op.minimumSequenceNumber, {
1692
- props: newProps,
1693
- });
1637
+ interval.ackPropertiesChange(properties, op);
1694
1638
 
1695
1639
  this.ackInterval(interval, op);
1696
1640
  } else {
@@ -1718,14 +1662,8 @@ export class IntervalCollection
1718
1662
  op,
1719
1663
  ) ?? interval;
1720
1664
  }
1721
- newInterval.propertyManager ??= new PropertiesManager();
1722
- const deltaProps = newInterval.propertyManager.handleProperties(
1723
- { props: newProps },
1724
- newInterval,
1725
- op.sequenceNumber,
1726
- op.minimumSequenceNumber,
1727
- true,
1728
- );
1665
+ const deltaProps = newInterval.changeProperties(properties, op);
1666
+
1729
1667
  if (this.onDeserialize) {
1730
1668
  this.onDeserialize(newInterval);
1731
1669
  }
@@ -1734,7 +1672,7 @@ export class IntervalCollection
1734
1672
  this.emitChange(newInterval, interval, local, false, op);
1735
1673
  }
1736
1674
 
1737
- const changedProperties = Object.keys(newProps).length > 0;
1675
+ const changedProperties = Object.keys(properties).length > 0;
1738
1676
  if (changedProperties) {
1739
1677
  this.emit("propertyChanged", interval, deltaProps, local, op);
1740
1678
  this.emit("changed", interval, deltaProps, undefined, local, false);
@@ -1780,12 +1718,11 @@ export class IntervalCollection
1780
1718
  }
1781
1719
 
1782
1720
  const { intervalType, properties, stickiness, startSide, endSide } = serializedInterval;
1783
-
1721
+ const { id } = getSerializedProperties(serializedInterval);
1784
1722
  const { start: startRebased, end: endRebased } =
1785
1723
  this.localSeqToRebasedInterval.get(localSeq) ?? this.computeRebasedPositions(localSeq);
1786
1724
 
1787
- const intervalId = properties?.[reservedIntervalIdKey];
1788
- const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(intervalId);
1725
+ const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(id);
1789
1726
 
1790
1727
  const rebased: SerializedIntervalDelta = {
1791
1728
  start: startRebased,
@@ -1801,10 +1738,10 @@ export class IntervalCollection
1801
1738
  if (
1802
1739
  opName === "change" &&
1803
1740
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when .hasPendingChangeStart returns false.
1804
- (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))
1741
+ (this.hasPendingChangeStart(id) || this.hasPendingChangeEnd(id))
1805
1742
  ) {
1806
1743
  this.removePendingChange(serializedInterval);
1807
- this.addPendingChange(intervalId, rebased);
1744
+ this.addPendingChange(id, rebased);
1808
1745
  }
1809
1746
 
1810
1747
  // if the interval slid off the string, rebase the op to be a noop and delete the interval.
@@ -1850,6 +1787,7 @@ export class IntervalCollection
1850
1787
  const newSegoff = getSlideToSegoff(
1851
1788
  segoff,
1852
1789
  slidingPreference,
1790
+ undefined,
1853
1791
  this.options.mergeTreeReferencesCanSlideToEndpoint,
1854
1792
  );
1855
1793
  const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
@@ -1876,7 +1814,7 @@ export class IntervalCollection
1876
1814
  endReferenceSlidingPreference(interval.stickiness),
1877
1815
  );
1878
1816
 
1879
- const id = interval.properties[reservedIntervalIdKey];
1817
+ const id = interval.getIntervalId();
1880
1818
  const hasPendingStartChange = this.hasPendingChangeStart(id);
1881
1819
  const hasPendingEndChange = this.hasPendingChangeEnd(id);
1882
1820
 
@@ -1960,13 +1898,14 @@ export class IntervalCollection
1960
1898
  op: ISequencedDocumentMessage,
1961
1899
  localOpMetadata: IMapMessageLocalMetadata | undefined,
1962
1900
  ) {
1901
+ const { id, properties } = getSerializedProperties(serializedInterval);
1902
+
1963
1903
  if (local) {
1964
1904
  assert(
1965
1905
  localOpMetadata !== undefined,
1966
1906
  0x553 /* op metadata should be defined for local op */,
1967
1907
  );
1968
1908
  this.localSeqToSerializedInterval.delete(localOpMetadata.localSeq);
1969
- const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
1970
1909
  const localInterval = this.getIntervalById(id);
1971
1910
  if (localInterval) {
1972
1911
  this.ackInterval(localInterval, op);
@@ -1978,13 +1917,11 @@ export class IntervalCollection
1978
1917
  throw new LoggingError("attachSequence must be called");
1979
1918
  }
1980
1919
 
1981
- this.localCollection.ensureSerializedId(serializedInterval);
1982
-
1983
1920
  const interval: SequenceIntervalClass = this.localCollection.addInterval(
1921
+ id,
1984
1922
  toSequencePlace(serializedInterval.start, serializedInterval.startSide ?? Side.Before),
1985
1923
  toSequencePlace(serializedInterval.end, serializedInterval.endSide ?? Side.Before),
1986
- serializedInterval.intervalType,
1987
- serializedInterval.properties,
1924
+ properties,
1988
1925
  op,
1989
1926
  );
1990
1927
 
@@ -2000,7 +1937,7 @@ export class IntervalCollection
2000
1937
  }
2001
1938
 
2002
1939
  public ackDelete(
2003
- serializedInterval: ISerializedInterval,
1940
+ serializedInterval: SerializedIntervalDelta,
2004
1941
  local: boolean,
2005
1942
  op: ISequencedDocumentMessage,
2006
1943
  ): void {
@@ -2015,7 +1952,7 @@ export class IntervalCollection
2015
1952
  throw new LoggingError("attach must be called prior to deleting intervals");
2016
1953
  }
2017
1954
 
2018
- const id = this.localCollection.ensureSerializedId(serializedInterval);
1955
+ const { id } = getSerializedProperties(serializedInterval);
2019
1956
  const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
2020
1957
  if (interval) {
2021
1958
  this.deleteExistingInterval(interval, local, op);