@fluidframework/sequence 2.43.0-343119 → 2.43.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 (60) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/api-report/sequence.legacy.alpha.api.md +2 -2
  3. package/dist/intervalCollection.d.ts +4 -9
  4. package/dist/intervalCollection.d.ts.map +1 -1
  5. package/dist/intervalCollection.js +96 -82
  6. package/dist/intervalCollection.js.map +1 -1
  7. package/dist/intervalCollectionMap.d.ts +1 -1
  8. package/dist/intervalCollectionMap.d.ts.map +1 -1
  9. package/dist/intervalCollectionMap.js +2 -2
  10. package/dist/intervalCollectionMap.js.map +1 -1
  11. package/dist/intervalCollectionMapInterfaces.d.ts +13 -6
  12. package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -1
  13. package/dist/intervalCollectionMapInterfaces.js.map +1 -1
  14. package/dist/intervals/intervalUtils.d.ts +4 -3
  15. package/dist/intervals/intervalUtils.d.ts.map +1 -1
  16. package/dist/intervals/intervalUtils.js +16 -3
  17. package/dist/intervals/intervalUtils.js.map +1 -1
  18. package/dist/intervals/sequenceInterval.d.ts +21 -7
  19. package/dist/intervals/sequenceInterval.d.ts.map +1 -1
  20. package/dist/intervals/sequenceInterval.js +88 -16
  21. package/dist/intervals/sequenceInterval.js.map +1 -1
  22. package/dist/packageVersion.d.ts +1 -1
  23. package/dist/packageVersion.d.ts.map +1 -1
  24. package/dist/packageVersion.js +1 -1
  25. package/dist/packageVersion.js.map +1 -1
  26. package/dist/sequence.js +1 -1
  27. package/dist/sequence.js.map +1 -1
  28. package/lib/intervalCollection.d.ts +4 -9
  29. package/lib/intervalCollection.d.ts.map +1 -1
  30. package/lib/intervalCollection.js +98 -82
  31. package/lib/intervalCollection.js.map +1 -1
  32. package/lib/intervalCollectionMap.d.ts +1 -1
  33. package/lib/intervalCollectionMap.d.ts.map +1 -1
  34. package/lib/intervalCollectionMap.js +2 -2
  35. package/lib/intervalCollectionMap.js.map +1 -1
  36. package/lib/intervalCollectionMapInterfaces.d.ts +13 -6
  37. package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
  38. package/lib/intervalCollectionMapInterfaces.js.map +1 -1
  39. package/lib/intervals/intervalUtils.d.ts +4 -3
  40. package/lib/intervals/intervalUtils.d.ts.map +1 -1
  41. package/lib/intervals/intervalUtils.js +15 -3
  42. package/lib/intervals/intervalUtils.js.map +1 -1
  43. package/lib/intervals/sequenceInterval.d.ts +21 -7
  44. package/lib/intervals/sequenceInterval.d.ts.map +1 -1
  45. package/lib/intervals/sequenceInterval.js +88 -16
  46. package/lib/intervals/sequenceInterval.js.map +1 -1
  47. package/lib/packageVersion.d.ts +1 -1
  48. package/lib/packageVersion.d.ts.map +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/lib/sequence.js +1 -1
  52. package/lib/sequence.js.map +1 -1
  53. package/package.json +18 -18
  54. package/src/intervalCollection.ts +122 -142
  55. package/src/intervalCollectionMap.ts +6 -2
  56. package/src/intervalCollectionMapInterfaces.ts +15 -5
  57. package/src/intervals/intervalUtils.ts +31 -3
  58. package/src/intervals/sequenceInterval.ts +135 -72
  59. package/src/packageVersion.ts +1 -1
  60. package/src/sequence.ts +1 -1
@@ -11,12 +11,10 @@ import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
11
11
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
12
12
  import {
13
13
  Client,
14
- DetachedReferencePosition,
15
14
  ISegment,
16
15
  LocalReferencePosition,
17
16
  PropertySet,
18
17
  ReferenceType,
19
- SlidingPreference,
20
18
  getSlideToSegoff,
21
19
  refTypeIncludesFlag,
22
20
  reservedRangeLabelsKey,
@@ -27,6 +25,7 @@ import {
27
25
  createLocalReconnectingPerspective,
28
26
  DoublyLinkedList,
29
27
  type ListNode,
28
+ SlidingPreference,
30
29
  } from "@fluidframework/merge-tree/internal";
31
30
  import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
32
31
  import { v4 as uuid } from "uuid";
@@ -57,9 +56,7 @@ import {
57
56
  SerializedIntervalDelta,
58
57
  createPositionReferenceFromSegoff,
59
58
  createSequenceInterval,
60
- endReferenceSlidingPreference,
61
59
  getSerializedProperties,
62
- startReferenceSlidingPreference,
63
60
  } from "./intervals/index.js";
64
61
 
65
62
  export type ISerializedIntervalCollectionV1 = ISerializedInterval[];
@@ -70,7 +67,7 @@ export interface ISerializedIntervalCollectionV2 {
70
67
  intervals: CompressedSerializedInterval[];
71
68
  }
72
69
 
73
- export function sidesFromStickiness(stickiness: IntervalStickiness) {
70
+ function sidesFromStickiness(stickiness: IntervalStickiness) {
74
71
  const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
75
72
  const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
76
73
 
@@ -138,25 +135,6 @@ export function toOptionalSequencePlace(
138
135
  return typeof pos === "number" && side !== undefined ? { pos, side } : pos;
139
136
  }
140
137
 
141
- export function computeStickinessFromSide(
142
- startPos: number | "start" | "end" | undefined = -1,
143
- startSide: Side = Side.Before,
144
- endPos: number | "start" | "end" | undefined = -1,
145
- endSide: Side = Side.Before,
146
- ): IntervalStickiness {
147
- let stickiness: IntervalStickiness = IntervalStickiness.NONE;
148
-
149
- if (startSide === Side.After || startPos === "start") {
150
- stickiness |= IntervalStickiness.START;
151
- }
152
-
153
- if (endSide === Side.Before || endPos === "end") {
154
- stickiness |= IntervalStickiness.END;
155
- }
156
-
157
- return stickiness as IntervalStickiness;
158
- }
159
-
160
138
  export class LocalIntervalCollection {
161
139
  public readonly overlappingIntervalsIndex: ISequenceOverlappingIntervalsIndex;
162
140
  public readonly idIntervalIndex: IIdIntervalIndex;
@@ -721,25 +699,27 @@ function removeMetadataFromPendingChanges(
721
699
  ): IntervalMessageLocalMetadata {
722
700
  const acked = (localOpMetadataNode as ListNode<IntervalMessageLocalMetadata>)?.remove()
723
701
  ?.data;
724
- assert(acked !== undefined, "local change must exist");
702
+ assert(acked !== undefined, 0xbbe /* local change must exist */);
725
703
  acked.endpointChangesNode?.remove();
726
704
  return acked;
727
705
  }
728
706
 
729
707
  function clearEmptyPendingEntry(pendingChanges: PendingChanges, id: string) {
730
708
  const pending = pendingChanges[id];
731
- assert(pending !== undefined, "pending must exist for local process");
709
+ assert(pending !== undefined, 0xbbf /* pending must exist for local process */);
732
710
  if (pending.local.empty) {
733
711
  assert(
734
712
  pending.endpointChanges?.empty !== false,
735
- "endpointChanges must be empty if not pending changes",
713
+ 0xbc0 /* endpointChanges must be empty if not pending changes */,
736
714
  );
737
715
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
738
716
  delete pendingChanges[id];
739
717
  }
740
718
  }
741
719
 
742
- function hasEndpointChanges(serialized: SerializedIntervalDelta) {
720
+ function hasEndpointChanges(
721
+ serialized: SerializedIntervalDelta,
722
+ ): serialized is ISerializedInterval {
743
723
  return serialized.start !== undefined && serialized.end !== undefined;
744
724
  }
745
725
 
@@ -781,6 +761,7 @@ export class IntervalCollection
781
761
  if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
782
762
  const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
783
763
  md.endpointChangesNode = endpointChanges.push(md).last;
764
+ md.rebased = undefined;
784
765
  }
785
766
  submitDelta(op, pending.local.push(md).last);
786
767
  };
@@ -887,7 +868,7 @@ export class IntervalCollection
887
868
  const { opName, value } = op;
888
869
  assert(
889
870
  (local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type,
890
- "must be same type",
871
+ 0xbc1 /* must be same type */,
891
872
  );
892
873
  switch (opName) {
893
874
  case "add": {
@@ -924,6 +905,7 @@ export class IntervalCollection
924
905
  public resubmitMessage(
925
906
  op: IIntervalCollectionTypeOperationValue,
926
907
  maybeMetadata: unknown,
908
+ squash: boolean,
927
909
  ): void {
928
910
  const { opName, value } = op;
929
911
 
@@ -932,7 +914,7 @@ export class IntervalCollection
932
914
  const rebasedValue =
933
915
  localOpMetadata.endpointChangesNode === undefined
934
916
  ? value
935
- : this.rebaseLocalInterval(localOpMetadata);
917
+ : this.rebaseLocalInterval(value, localOpMetadata, squash);
936
918
 
937
919
  if (rebasedValue === undefined) {
938
920
  const { id } = getSerializedProperties(value);
@@ -974,69 +956,69 @@ export class IntervalCollection
974
956
  }
975
957
  }
976
958
 
977
- private rebasePositionWithSegmentSlide(
978
- pos: number | "start" | "end",
979
- seqNumberFrom: number,
959
+ private rebaseReferenceWithSegmentSlide(
960
+ ref: LocalReferencePosition,
980
961
  localSeq: number,
981
- ): number | "start" | "end" | undefined {
962
+ squash: boolean,
963
+ ): { segment: ISegment; offset: number } | undefined {
982
964
  if (!this.client) {
983
965
  throw new LoggingError("mergeTree client must exist");
984
966
  }
985
967
 
986
- if (pos === "start" || pos === "end") {
987
- return pos;
988
- }
989
-
990
968
  const { clientId } = this.client.getCollabWindow();
991
- const { segment, offset } =
992
- this.client.getContainingSegment(
993
- pos,
994
- {
995
- referenceSequenceNumber: seqNumberFrom,
996
- clientId: this.client.getLongClientId(clientId),
997
- },
998
- localSeq,
999
- ) ?? {};
1000
-
1001
- // if segment is undefined, it slid off the string
1002
- assert(segment !== undefined && offset !== undefined, 0x54e /* No segment found */);
969
+ const segment: ISegmentInternal | undefined = ref.getSegment();
970
+ if (segment?.endpointType) {
971
+ return { segment, offset: 0 };
972
+ }
973
+ const offset = ref.getOffset();
1003
974
 
1004
975
  const segoff = getSlideToSegoff(
1005
- { segment, offset },
1006
- undefined,
1007
- createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq),
1008
- this.options.mergeTreeReferencesCanSlideToEndpoint,
976
+ segment === undefined ? undefined : { segment, offset },
977
+ ref.slidingPreference,
978
+ createLocalReconnectingPerspective(
979
+ this.client.getCurrentSeq(),
980
+ clientId,
981
+ localSeq,
982
+ squash,
983
+ ),
984
+ ref.canSlideToEndpoint,
1009
985
  );
1010
986
 
1011
987
  // case happens when rebasing op, but concurrently entire string has been deleted
1012
- if (segoff?.segment === undefined || segoff.offset === undefined) {
1013
- return DetachedReferencePosition;
988
+ if (segoff === undefined) {
989
+ if (ref.canSlideToEndpoint !== true) {
990
+ return undefined;
991
+ }
992
+ return {
993
+ segment:
994
+ ref.slidingPreference === SlidingPreference.FORWARD
995
+ ? this.client.endOfTree
996
+ : this.client.startOfTree,
997
+ offset: 0,
998
+ };
1014
999
  }
1015
-
1016
- assert(
1017
- offset !== undefined && 0 <= offset && offset < segment.cachedLength,
1018
- 0x54f /* Invalid offset */,
1019
- );
1020
- return this.client.findReconnectionPosition(segoff.segment, localSeq) + segoff.offset;
1000
+ return segoff;
1021
1001
  }
1022
1002
 
1023
1003
  private computeRebasedPositions(
1024
1004
  localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
1025
- ): ISerializedInterval | SerializedIntervalDelta {
1005
+ squash: boolean,
1006
+ ): Record<"start" | "end", { segment: ISegmentInternal; offset: number }> | "detached" {
1026
1007
  assert(
1027
1008
  this.client !== undefined,
1028
1009
  0x550 /* Client should be defined when computing rebased position */,
1029
1010
  );
1030
- const { localSeq, original } = localOpMetadata;
1031
- const rebased = { ...original };
1032
- const { start, end, sequenceNumber } = original;
1033
- if (start !== undefined) {
1034
- rebased.start = this.rebasePositionWithSegmentSlide(start, sequenceNumber, localSeq);
1011
+
1012
+ const { localSeq, interval } = localOpMetadata;
1013
+ const start = this.rebaseReferenceWithSegmentSlide(interval.start, localSeq, squash);
1014
+ if (start === undefined) {
1015
+ return "detached";
1035
1016
  }
1036
- if (end !== undefined) {
1037
- rebased.end = this.rebasePositionWithSegmentSlide(end, sequenceNumber, localSeq);
1017
+ const end = this.rebaseReferenceWithSegmentSlide(interval.end, localSeq, squash);
1018
+ if (end === undefined) {
1019
+ return "detached";
1038
1020
  }
1039
- return rebased;
1021
+ return { start, end };
1040
1022
  }
1041
1023
 
1042
1024
  public attachGraph(client: Client, label: string) {
@@ -1051,11 +1033,11 @@ export class IntervalCollection
1051
1033
  // Instantiate the local interval collection based on the saved intervals
1052
1034
  this.client = client;
1053
1035
  if (client) {
1054
- client.on("normalize", () => {
1036
+ client.on("normalize", (squash) => {
1055
1037
  for (const pending of Object.values(this.pending)) {
1056
1038
  if (pending?.endpointChanges !== undefined) {
1057
1039
  for (const local of pending.endpointChanges) {
1058
- local.data.rebased = this.computeRebasedPositions(local.data);
1040
+ local.data.rebased = this.computeRebasedPositions(local.data, squash);
1059
1041
  }
1060
1042
  }
1061
1043
  }
@@ -1216,7 +1198,7 @@ export class IntervalCollection
1216
1198
  {
1217
1199
  type: "add",
1218
1200
  localSeq,
1219
- original: serializedInterval,
1201
+ interval,
1220
1202
  },
1221
1203
  );
1222
1204
  }
@@ -1345,7 +1327,7 @@ export class IntervalCollection
1345
1327
  type: "change",
1346
1328
  localSeq,
1347
1329
  previous: interval.serialize(),
1348
- original: serializedInterval,
1330
+ interval: newInterval ?? interval,
1349
1331
  };
1350
1332
 
1351
1333
  this.submitDelta(
@@ -1369,8 +1351,10 @@ export class IntervalCollection
1369
1351
  }
1370
1352
  if (newInterval) {
1371
1353
  this.emitChange(newInterval, interval, true, false);
1372
- this.client?.removeLocalReferencePosition(interval.start);
1373
- this.client?.removeLocalReferencePosition(interval.end);
1354
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1355
+ interval.start.properties!.interval = undefined;
1356
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1357
+ interval.end.properties!.interval = undefined;
1374
1358
  }
1375
1359
  return newInterval;
1376
1360
  }
@@ -1477,65 +1461,71 @@ export class IntervalCollection
1477
1461
  *
1478
1462
  */
1479
1463
  public rebaseLocalInterval(
1464
+ original: SerializedIntervalDelta,
1480
1465
  localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
1466
+ squash: boolean,
1481
1467
  ): SerializedIntervalDelta | undefined {
1482
- const original = localOpMetadata.original;
1483
- if (!this.client) {
1468
+ if (!this.client || !hasEndpointChanges(original)) {
1484
1469
  // If there's no associated mergeTree client, the originally submitted op is still correct.
1485
1470
  return original;
1486
1471
  }
1487
- if (!this.attached) {
1472
+ if (!this.attached || this.localCollection === undefined) {
1488
1473
  throw new LoggingError("attachSequence must be called");
1489
1474
  }
1490
1475
 
1491
- const { localSeq } = localOpMetadata;
1492
- const { intervalType, properties, stickiness, startSide, endSide } = original;
1476
+ const { localSeq, interval } = localOpMetadata;
1493
1477
  const { id } = getSerializedProperties(original);
1494
- const { start: startRebased, end: endRebased } = (localOpMetadata.rebased ??=
1495
- this.computeRebasedPositions(localOpMetadata));
1496
-
1497
- const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(id);
1498
-
1499
- const rebased: SerializedIntervalDelta = {
1500
- start: startRebased,
1501
- end: endRebased,
1502
- intervalType,
1503
- sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1504
- properties,
1505
- stickiness,
1506
- startSide,
1507
- endSide,
1508
- };
1478
+ const rebasedEndpoint = (localOpMetadata.rebased ??= this.computeRebasedPositions(
1479
+ localOpMetadata,
1480
+ squash,
1481
+ ));
1482
+ const localInterval = this.localCollection.idIntervalIndex.getIntervalById(id);
1509
1483
 
1510
1484
  // if the interval slid off the string, rebase the op to be a noop and delete the interval.
1511
- if (
1512
- !this.options.mergeTreeReferencesCanSlideToEndpoint &&
1513
- (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition)
1514
- ) {
1515
- if (localInterval) {
1485
+ if (rebasedEndpoint === "detached") {
1486
+ if (
1487
+ localInterval !== undefined &&
1488
+ (localInterval === interval || localOpMetadata.type === "add")
1489
+ ) {
1516
1490
  this.localCollection?.removeExistingInterval(localInterval);
1517
1491
  }
1518
1492
  return undefined;
1519
1493
  }
1520
1494
 
1521
- if (localInterval !== undefined) {
1522
- // The rebased op may place this interval's endpoints on different segments. Calling `changeInterval` here
1523
- // updates the local client's state to be consistent with the emitted op.
1524
- this.localCollection?.changeInterval(
1525
- localInterval,
1526
- toOptionalSequencePlace(startRebased, startSide ?? Side.Before),
1527
- toOptionalSequencePlace(endRebased, endSide ?? Side.Before),
1528
- undefined,
1529
- localSeq,
1530
- );
1495
+ const { start, end } = rebasedEndpoint;
1496
+ if (
1497
+ interval.start.getSegment() !== start.segment ||
1498
+ interval.start.getOffset() !== start.offset ||
1499
+ interval.end.getSegment() !== end.segment ||
1500
+ interval.end.getOffset() !== end.offset
1501
+ ) {
1502
+ if (localInterval === interval) {
1503
+ this.localCollection.removeExistingInterval(localInterval);
1504
+ }
1505
+ const old = interval.clone();
1506
+ interval.moveEndpointReferences(rebasedEndpoint);
1507
+ if (localInterval === interval) {
1508
+ this.localCollection.add(interval);
1509
+ this.emitChange(interval, old, true, true);
1510
+ }
1511
+ this.client.removeLocalReferencePosition(old.start);
1512
+ this.client.removeLocalReferencePosition(old.end);
1531
1513
  }
1532
1514
 
1533
- return rebased;
1515
+ return {
1516
+ ...original,
1517
+ start:
1518
+ start.segment.endpointType ??
1519
+ this.client.findReconnectionPosition(start.segment, localSeq) + start.offset,
1520
+ end:
1521
+ end.segment.endpointType ??
1522
+ this.client.findReconnectionPosition(end.segment, localSeq) + end.offset,
1523
+ sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1524
+ };
1534
1525
  }
1535
1526
 
1536
1527
  private getSlideToSegment(
1537
1528
  lref: LocalReferencePosition,
1538
- slidingPreference: SlidingPreference,
1539
1529
  ): { segment: ISegment; offset: number } | undefined {
1540
1530
  if (!this.client) {
1541
1531
  throw new LoggingError("client does not exist");
@@ -1553,9 +1543,9 @@ export class IntervalCollection
1553
1543
  }
1554
1544
  return getSlideToSegoff(
1555
1545
  segoff,
1556
- slidingPreference,
1546
+ lref.slidingPreference,
1557
1547
  undefined,
1558
- this.options.mergeTreeReferencesCanSlideToEndpoint,
1548
+ lref.canSlideToEndpoint,
1559
1549
  );
1560
1550
  }
1561
1551
 
@@ -1567,14 +1557,8 @@ export class IntervalCollection
1567
1557
  return;
1568
1558
  }
1569
1559
 
1570
- const newStart = this.getSlideToSegment(
1571
- interval.start,
1572
- startReferenceSlidingPreference(interval.stickiness),
1573
- );
1574
- const newEnd = this.getSlideToSegment(
1575
- interval.end,
1576
- endReferenceSlidingPreference(interval.stickiness),
1577
- );
1560
+ const newStart = this.getSlideToSegment(interval.start);
1561
+ const newEnd = this.getSlideToSegment(interval.end);
1578
1562
 
1579
1563
  const id = interval.getIntervalId();
1580
1564
  const hasPendingChange = this.hasPendingEndpointChanges(id);
@@ -1606,16 +1590,14 @@ export class IntervalCollection
1606
1590
 
1607
1591
  if (needsStartUpdate) {
1608
1592
  const props = interval.start.properties;
1609
- interval.start = createPositionReferenceFromSegoff(
1610
- this.client,
1611
- newStart,
1612
- interval.start.refType,
1593
+ interval.start = createPositionReferenceFromSegoff({
1594
+ client: this.client,
1595
+ segoff: newStart,
1596
+ refType: interval.start.refType,
1613
1597
  op,
1614
- undefined,
1615
- undefined,
1616
- startReferenceSlidingPreference(interval.stickiness),
1617
- startReferenceSlidingPreference(interval.stickiness) === SlidingPreference.BACKWARD,
1618
- );
1598
+ slidingPreference: interval.start.slidingPreference,
1599
+ canSlideToEndpoint: interval.start.canSlideToEndpoint,
1600
+ });
1619
1601
  if (props) {
1620
1602
  interval.start.addProperties(props);
1621
1603
  }
@@ -1627,16 +1609,14 @@ export class IntervalCollection
1627
1609
  }
1628
1610
  if (needsEndUpdate) {
1629
1611
  const props = interval.end.properties;
1630
- interval.end = createPositionReferenceFromSegoff(
1631
- this.client,
1632
- newEnd,
1633
- interval.end.refType,
1612
+ interval.end = createPositionReferenceFromSegoff({
1613
+ client: this.client,
1614
+ segoff: newEnd,
1615
+ refType: interval.end.refType,
1634
1616
  op,
1635
- undefined,
1636
- undefined,
1637
- endReferenceSlidingPreference(interval.stickiness),
1638
- endReferenceSlidingPreference(interval.stickiness) === SlidingPreference.FORWARD,
1639
- );
1617
+ slidingPreference: interval.end.slidingPreference,
1618
+ canSlideToEndpoint: interval.end.canSlideToEndpoint,
1619
+ });
1640
1620
  if (props) {
1641
1621
  interval.end.addProperties(props);
1642
1622
  }
@@ -188,12 +188,16 @@ export class IntervalCollectionMap {
188
188
  * also sent if we are asked to resubmit the message.
189
189
  * @returns True if the operation was submitted, false otherwise.
190
190
  */
191
- public tryResubmitMessage(content: unknown, localOpMetadata: unknown): boolean {
191
+ public tryResubmitMessage(
192
+ content: unknown,
193
+ localOpMetadata: unknown,
194
+ squash: boolean,
195
+ ): boolean {
192
196
  if (isMapOperation(content)) {
193
197
  const { value, key } = content;
194
198
  const localValue = this.data.get(key);
195
199
  assert(localValue !== undefined, 0x3f8 /* Local value expected on resubmission */);
196
- localValue.resubmitMessage(value, localOpMetadata);
200
+ localValue.resubmitMessage(value, localOpMetadata, squash);
197
201
  return true;
198
202
  }
199
203
  return false;
@@ -4,7 +4,11 @@
4
4
  */
5
5
 
6
6
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
7
- import { IMergeTreeOptions, ListNode } from "@fluidframework/merge-tree/internal";
7
+ import {
8
+ IMergeTreeOptions,
9
+ ListNode,
10
+ type ISegmentInternal,
11
+ } from "@fluidframework/merge-tree/internal";
8
12
 
9
13
  import type {
10
14
  IntervalCollection,
@@ -15,28 +19,34 @@ import {
15
19
  ISerializedInterval,
16
20
  IntervalDeltaOpType,
17
21
  SerializedIntervalDelta,
22
+ type SequenceIntervalClass,
18
23
  } from "./intervals/index.js";
19
24
 
20
25
  export interface IntervalAddLocalMetadata {
21
26
  type: typeof IntervalDeltaOpType.ADD;
22
27
  localSeq: number;
23
28
  endpointChangesNode?: ListNode<IntervalAddLocalMetadata | IntervalChangeLocalMetadata>;
24
- rebased?: ISerializedInterval;
25
- original: ISerializedInterval;
29
+ rebased?:
30
+ | Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
31
+ | "detached";
32
+ interval: SequenceIntervalClass;
26
33
  }
27
34
  export interface IntervalChangeLocalMetadata {
28
35
  type: typeof IntervalDeltaOpType.CHANGE;
29
36
  localSeq: number;
30
37
  previous: ISerializedInterval;
31
38
  endpointChangesNode?: ListNode<IntervalChangeLocalMetadata | IntervalChangeLocalMetadata>;
32
- rebased?: SerializedIntervalDelta;
33
- original: SerializedIntervalDelta;
39
+ rebased?:
40
+ | Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
41
+ | "detached";
42
+ interval: SequenceIntervalClass;
34
43
  }
35
44
  export interface IntervalDeleteLocalMetadata {
36
45
  type: typeof IntervalDeltaOpType.DELETE;
37
46
  localSeq: number;
38
47
  previous: ISerializedInterval;
39
48
  endpointChangesNode?: undefined;
49
+ interval?: undefined;
40
50
  }
41
51
  export type IntervalMessageLocalMetadata =
42
52
  | IntervalAddLocalMetadata
@@ -57,7 +57,7 @@ export interface IInterval {
57
57
  end: SequencePlace | undefined,
58
58
  op?: ISequencedDocumentMessage,
59
59
  localSeq?: number,
60
- useNewSlidingBehavior?: boolean,
60
+ canSlideToEndpoint?: boolean,
61
61
  ): IInterval | undefined;
62
62
  /**
63
63
  * @returns whether this interval overlaps with `b`.
@@ -264,8 +264,12 @@ export const IntervalStickiness = {
264
264
  export type IntervalStickiness = (typeof IntervalStickiness)[keyof typeof IntervalStickiness];
265
265
 
266
266
  export function startReferenceSlidingPreference(
267
- stickiness: IntervalStickiness,
267
+ startPos: number | "start" | "end" | undefined,
268
+ startSide: Side,
269
+ endPos: number | "start" | "end" | undefined,
270
+ endSide: Side,
268
271
  ): SlidingPreference {
272
+ const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
269
273
  // if any start stickiness, prefer sliding backwards
270
274
  return (stickiness & IntervalStickiness.START) === 0
271
275
  ? SlidingPreference.FORWARD
@@ -273,10 +277,34 @@ export function startReferenceSlidingPreference(
273
277
  }
274
278
 
275
279
  export function endReferenceSlidingPreference(
276
- stickiness: IntervalStickiness,
280
+ startPos: number | "start" | "end" | undefined,
281
+ startSide: Side,
282
+ endPos: number | "start" | "end" | undefined,
283
+ endSide: Side,
277
284
  ): SlidingPreference {
285
+ const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
286
+
278
287
  // if any end stickiness, prefer sliding forwards
279
288
  return (stickiness & IntervalStickiness.END) === 0
280
289
  ? SlidingPreference.BACKWARD
281
290
  : SlidingPreference.FORWARD;
282
291
  }
292
+
293
+ export function computeStickinessFromSide(
294
+ startPos: number | "start" | "end" | undefined,
295
+ startSide: Side,
296
+ endPos: number | "start" | "end" | undefined,
297
+ endSide: Side,
298
+ ): IntervalStickiness {
299
+ let stickiness: IntervalStickiness = IntervalStickiness.NONE;
300
+
301
+ if (startSide === Side.After || startPos === "start") {
302
+ stickiness |= IntervalStickiness.START;
303
+ }
304
+
305
+ if (endSide === Side.Before || endPos === "end") {
306
+ stickiness |= IntervalStickiness.END;
307
+ }
308
+
309
+ return stickiness as IntervalStickiness;
310
+ }