@fluidframework/sequence 2.43.0-343119 → 2.50.0-345060

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 (58) 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 +98 -84
  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 +14 -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.js +1 -1
  24. package/dist/packageVersion.js.map +1 -1
  25. package/dist/sequence.js +1 -1
  26. package/dist/sequence.js.map +1 -1
  27. package/lib/intervalCollection.d.ts +4 -9
  28. package/lib/intervalCollection.d.ts.map +1 -1
  29. package/lib/intervalCollection.js +99 -83
  30. package/lib/intervalCollection.js.map +1 -1
  31. package/lib/intervalCollectionMap.d.ts +1 -1
  32. package/lib/intervalCollectionMap.d.ts.map +1 -1
  33. package/lib/intervalCollectionMap.js +2 -2
  34. package/lib/intervalCollectionMap.js.map +1 -1
  35. package/lib/intervalCollectionMapInterfaces.d.ts +14 -6
  36. package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
  37. package/lib/intervalCollectionMapInterfaces.js.map +1 -1
  38. package/lib/intervals/intervalUtils.d.ts +4 -3
  39. package/lib/intervals/intervalUtils.d.ts.map +1 -1
  40. package/lib/intervals/intervalUtils.js +15 -3
  41. package/lib/intervals/intervalUtils.js.map +1 -1
  42. package/lib/intervals/sequenceInterval.d.ts +21 -7
  43. package/lib/intervals/sequenceInterval.d.ts.map +1 -1
  44. package/lib/intervals/sequenceInterval.js +88 -16
  45. package/lib/intervals/sequenceInterval.js.map +1 -1
  46. package/lib/packageVersion.d.ts +1 -1
  47. package/lib/packageVersion.js +1 -1
  48. package/lib/packageVersion.js.map +1 -1
  49. package/lib/sequence.js +1 -1
  50. package/lib/sequence.js.map +1 -1
  51. package/package.json +19 -19
  52. package/src/intervalCollection.ts +128 -145
  53. package/src/intervalCollectionMap.ts +6 -2
  54. package/src/intervalCollectionMapInterfaces.ts +12 -5
  55. package/src/intervals/intervalUtils.ts +31 -3
  56. package/src/intervals/sequenceInterval.ts +135 -72
  57. package/src/packageVersion.ts +1 -1
  58. package/src/sequence.ts +1 -1
@@ -7,16 +7,19 @@
7
7
 
8
8
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
9
9
  import { IEvent } from "@fluidframework/core-interfaces";
10
- import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
10
+ import {
11
+ assert,
12
+ DoublyLinkedList,
13
+ unreachableCase,
14
+ type ListNode,
15
+ } from "@fluidframework/core-utils/internal";
11
16
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
12
17
  import {
13
18
  Client,
14
- DetachedReferencePosition,
15
19
  ISegment,
16
20
  LocalReferencePosition,
17
21
  PropertySet,
18
22
  ReferenceType,
19
- SlidingPreference,
20
23
  getSlideToSegoff,
21
24
  refTypeIncludesFlag,
22
25
  reservedRangeLabelsKey,
@@ -25,8 +28,7 @@ import {
25
28
  endpointPosAndSide,
26
29
  type ISegmentInternal,
27
30
  createLocalReconnectingPerspective,
28
- DoublyLinkedList,
29
- type ListNode,
31
+ SlidingPreference,
30
32
  } from "@fluidframework/merge-tree/internal";
31
33
  import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
32
34
  import { v4 as uuid } from "uuid";
@@ -57,9 +59,7 @@ import {
57
59
  SerializedIntervalDelta,
58
60
  createPositionReferenceFromSegoff,
59
61
  createSequenceInterval,
60
- endReferenceSlidingPreference,
61
62
  getSerializedProperties,
62
- startReferenceSlidingPreference,
63
63
  } from "./intervals/index.js";
64
64
 
65
65
  export type ISerializedIntervalCollectionV1 = ISerializedInterval[];
@@ -70,7 +70,7 @@ export interface ISerializedIntervalCollectionV2 {
70
70
  intervals: CompressedSerializedInterval[];
71
71
  }
72
72
 
73
- export function sidesFromStickiness(stickiness: IntervalStickiness) {
73
+ function sidesFromStickiness(stickiness: IntervalStickiness) {
74
74
  const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
75
75
  const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
76
76
 
@@ -138,25 +138,6 @@ export function toOptionalSequencePlace(
138
138
  return typeof pos === "number" && side !== undefined ? { pos, side } : pos;
139
139
  }
140
140
 
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
141
  export class LocalIntervalCollection {
161
142
  public readonly overlappingIntervalsIndex: ISequenceOverlappingIntervalsIndex;
162
143
  public readonly idIntervalIndex: IIdIntervalIndex;
@@ -721,25 +702,27 @@ function removeMetadataFromPendingChanges(
721
702
  ): IntervalMessageLocalMetadata {
722
703
  const acked = (localOpMetadataNode as ListNode<IntervalMessageLocalMetadata>)?.remove()
723
704
  ?.data;
724
- assert(acked !== undefined, "local change must exist");
705
+ assert(acked !== undefined, 0xbbe /* local change must exist */);
725
706
  acked.endpointChangesNode?.remove();
726
707
  return acked;
727
708
  }
728
709
 
729
710
  function clearEmptyPendingEntry(pendingChanges: PendingChanges, id: string) {
730
711
  const pending = pendingChanges[id];
731
- assert(pending !== undefined, "pending must exist for local process");
712
+ assert(pending !== undefined, 0xbbf /* pending must exist for local process */);
732
713
  if (pending.local.empty) {
733
714
  assert(
734
715
  pending.endpointChanges?.empty !== false,
735
- "endpointChanges must be empty if not pending changes",
716
+ 0xbc0 /* endpointChanges must be empty if not pending changes */,
736
717
  );
737
718
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
738
719
  delete pendingChanges[id];
739
720
  }
740
721
  }
741
722
 
742
- function hasEndpointChanges(serialized: SerializedIntervalDelta) {
723
+ function hasEndpointChanges(
724
+ serialized: SerializedIntervalDelta,
725
+ ): serialized is ISerializedInterval {
743
726
  return serialized.start !== undefined && serialized.end !== undefined;
744
727
  }
745
728
 
@@ -781,6 +764,7 @@ export class IntervalCollection
781
764
  if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
782
765
  const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
783
766
  md.endpointChangesNode = endpointChanges.push(md).last;
767
+ md.rebased = undefined;
784
768
  }
785
769
  submitDelta(op, pending.local.push(md).last);
786
770
  };
@@ -887,7 +871,7 @@ export class IntervalCollection
887
871
  const { opName, value } = op;
888
872
  assert(
889
873
  (local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type,
890
- "must be same type",
874
+ 0xbc1 /* must be same type */,
891
875
  );
892
876
  switch (opName) {
893
877
  case "add": {
@@ -924,6 +908,7 @@ export class IntervalCollection
924
908
  public resubmitMessage(
925
909
  op: IIntervalCollectionTypeOperationValue,
926
910
  maybeMetadata: unknown,
911
+ squash: boolean,
927
912
  ): void {
928
913
  const { opName, value } = op;
929
914
 
@@ -932,7 +917,7 @@ export class IntervalCollection
932
917
  const rebasedValue =
933
918
  localOpMetadata.endpointChangesNode === undefined
934
919
  ? value
935
- : this.rebaseLocalInterval(localOpMetadata);
920
+ : this.rebaseLocalInterval(value, localOpMetadata, squash);
936
921
 
937
922
  if (rebasedValue === undefined) {
938
923
  const { id } = getSerializedProperties(value);
@@ -974,69 +959,69 @@ export class IntervalCollection
974
959
  }
975
960
  }
976
961
 
977
- private rebasePositionWithSegmentSlide(
978
- pos: number | "start" | "end",
979
- seqNumberFrom: number,
962
+ private rebaseReferenceWithSegmentSlide(
963
+ ref: LocalReferencePosition,
980
964
  localSeq: number,
981
- ): number | "start" | "end" | undefined {
965
+ squash: boolean,
966
+ ): { segment: ISegment; offset: number } | undefined {
982
967
  if (!this.client) {
983
968
  throw new LoggingError("mergeTree client must exist");
984
969
  }
985
970
 
986
- if (pos === "start" || pos === "end") {
987
- return pos;
988
- }
989
-
990
971
  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 */);
972
+ const segment: ISegmentInternal | undefined = ref.getSegment();
973
+ if (segment?.endpointType) {
974
+ return { segment, offset: 0 };
975
+ }
976
+ const offset = ref.getOffset();
1003
977
 
1004
978
  const segoff = getSlideToSegoff(
1005
- { segment, offset },
1006
- undefined,
1007
- createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq),
1008
- this.options.mergeTreeReferencesCanSlideToEndpoint,
979
+ segment === undefined ? undefined : { segment, offset },
980
+ ref.slidingPreference,
981
+ createLocalReconnectingPerspective(
982
+ this.client.getCurrentSeq(),
983
+ clientId,
984
+ localSeq,
985
+ squash,
986
+ ),
987
+ ref.canSlideToEndpoint,
1009
988
  );
1010
989
 
1011
990
  // case happens when rebasing op, but concurrently entire string has been deleted
1012
- if (segoff?.segment === undefined || segoff.offset === undefined) {
1013
- return DetachedReferencePosition;
991
+ if (segoff === undefined) {
992
+ if (ref.canSlideToEndpoint !== true) {
993
+ return undefined;
994
+ }
995
+ return {
996
+ segment:
997
+ ref.slidingPreference === SlidingPreference.FORWARD
998
+ ? this.client.endOfTree
999
+ : this.client.startOfTree,
1000
+ offset: 0,
1001
+ };
1014
1002
  }
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;
1003
+ return segoff;
1021
1004
  }
1022
1005
 
1023
1006
  private computeRebasedPositions(
1024
1007
  localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
1025
- ): ISerializedInterval | SerializedIntervalDelta {
1008
+ squash: boolean,
1009
+ ): Record<"start" | "end", { segment: ISegmentInternal; offset: number }> | "detached" {
1026
1010
  assert(
1027
1011
  this.client !== undefined,
1028
1012
  0x550 /* Client should be defined when computing rebased position */,
1029
1013
  );
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);
1014
+
1015
+ const { localSeq, interval } = localOpMetadata;
1016
+ const start = this.rebaseReferenceWithSegmentSlide(interval.start, localSeq, squash);
1017
+ if (start === undefined) {
1018
+ return "detached";
1035
1019
  }
1036
- if (end !== undefined) {
1037
- rebased.end = this.rebasePositionWithSegmentSlide(end, sequenceNumber, localSeq);
1020
+ const end = this.rebaseReferenceWithSegmentSlide(interval.end, localSeq, squash);
1021
+ if (end === undefined) {
1022
+ return "detached";
1038
1023
  }
1039
- return rebased;
1024
+ return { start, end };
1040
1025
  }
1041
1026
 
1042
1027
  public attachGraph(client: Client, label: string) {
@@ -1051,11 +1036,11 @@ export class IntervalCollection
1051
1036
  // Instantiate the local interval collection based on the saved intervals
1052
1037
  this.client = client;
1053
1038
  if (client) {
1054
- client.on("normalize", () => {
1039
+ client.on("normalize", (squash) => {
1055
1040
  for (const pending of Object.values(this.pending)) {
1056
1041
  if (pending?.endpointChanges !== undefined) {
1057
1042
  for (const local of pending.endpointChanges) {
1058
- local.data.rebased = this.computeRebasedPositions(local.data);
1043
+ local.data.rebased = this.computeRebasedPositions(local.data, squash);
1059
1044
  }
1060
1045
  }
1061
1046
  }
@@ -1216,7 +1201,7 @@ export class IntervalCollection
1216
1201
  {
1217
1202
  type: "add",
1218
1203
  localSeq,
1219
- original: serializedInterval,
1204
+ interval,
1220
1205
  },
1221
1206
  );
1222
1207
  }
@@ -1345,7 +1330,7 @@ export class IntervalCollection
1345
1330
  type: "change",
1346
1331
  localSeq,
1347
1332
  previous: interval.serialize(),
1348
- original: serializedInterval,
1333
+ interval: newInterval ?? interval,
1349
1334
  };
1350
1335
 
1351
1336
  this.submitDelta(
@@ -1369,8 +1354,10 @@ export class IntervalCollection
1369
1354
  }
1370
1355
  if (newInterval) {
1371
1356
  this.emitChange(newInterval, interval, true, false);
1372
- this.client?.removeLocalReferencePosition(interval.start);
1373
- this.client?.removeLocalReferencePosition(interval.end);
1357
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1358
+ interval.start.properties!.interval = undefined;
1359
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1360
+ interval.end.properties!.interval = undefined;
1374
1361
  }
1375
1362
  return newInterval;
1376
1363
  }
@@ -1477,65 +1464,71 @@ export class IntervalCollection
1477
1464
  *
1478
1465
  */
1479
1466
  public rebaseLocalInterval(
1467
+ original: SerializedIntervalDelta,
1480
1468
  localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
1469
+ squash: boolean,
1481
1470
  ): SerializedIntervalDelta | undefined {
1482
- const original = localOpMetadata.original;
1483
- if (!this.client) {
1471
+ if (!this.client || !hasEndpointChanges(original)) {
1484
1472
  // If there's no associated mergeTree client, the originally submitted op is still correct.
1485
1473
  return original;
1486
1474
  }
1487
- if (!this.attached) {
1475
+ if (!this.attached || this.localCollection === undefined) {
1488
1476
  throw new LoggingError("attachSequence must be called");
1489
1477
  }
1490
1478
 
1491
- const { localSeq } = localOpMetadata;
1492
- const { intervalType, properties, stickiness, startSide, endSide } = original;
1479
+ const { localSeq, interval } = localOpMetadata;
1493
1480
  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
- };
1481
+ const rebasedEndpoint = (localOpMetadata.rebased ??= this.computeRebasedPositions(
1482
+ localOpMetadata,
1483
+ squash,
1484
+ ));
1485
+ const localInterval = this.localCollection.idIntervalIndex.getIntervalById(id);
1509
1486
 
1510
1487
  // 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) {
1488
+ if (rebasedEndpoint === "detached") {
1489
+ if (
1490
+ localInterval !== undefined &&
1491
+ (localInterval === interval || localOpMetadata.type === "add")
1492
+ ) {
1516
1493
  this.localCollection?.removeExistingInterval(localInterval);
1517
1494
  }
1518
1495
  return undefined;
1519
1496
  }
1520
1497
 
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
- );
1498
+ const { start, end } = rebasedEndpoint;
1499
+ if (
1500
+ interval.start.getSegment() !== start.segment ||
1501
+ interval.start.getOffset() !== start.offset ||
1502
+ interval.end.getSegment() !== end.segment ||
1503
+ interval.end.getOffset() !== end.offset
1504
+ ) {
1505
+ if (localInterval === interval) {
1506
+ this.localCollection.removeExistingInterval(localInterval);
1507
+ }
1508
+ const old = interval.clone();
1509
+ interval.moveEndpointReferences(rebasedEndpoint);
1510
+ if (localInterval === interval) {
1511
+ this.localCollection.add(interval);
1512
+ this.emitChange(interval, old, true, true);
1513
+ }
1514
+ this.client.removeLocalReferencePosition(old.start);
1515
+ this.client.removeLocalReferencePosition(old.end);
1531
1516
  }
1532
1517
 
1533
- return rebased;
1518
+ return {
1519
+ ...original,
1520
+ start:
1521
+ start.segment.endpointType ??
1522
+ this.client.findReconnectionPosition(start.segment, localSeq) + start.offset,
1523
+ end:
1524
+ end.segment.endpointType ??
1525
+ this.client.findReconnectionPosition(end.segment, localSeq) + end.offset,
1526
+ sequenceNumber: this.client?.getCurrentSeq() ?? 0,
1527
+ };
1534
1528
  }
1535
1529
 
1536
1530
  private getSlideToSegment(
1537
1531
  lref: LocalReferencePosition,
1538
- slidingPreference: SlidingPreference,
1539
1532
  ): { segment: ISegment; offset: number } | undefined {
1540
1533
  if (!this.client) {
1541
1534
  throw new LoggingError("client does not exist");
@@ -1553,9 +1546,9 @@ export class IntervalCollection
1553
1546
  }
1554
1547
  return getSlideToSegoff(
1555
1548
  segoff,
1556
- slidingPreference,
1549
+ lref.slidingPreference,
1557
1550
  undefined,
1558
- this.options.mergeTreeReferencesCanSlideToEndpoint,
1551
+ lref.canSlideToEndpoint,
1559
1552
  );
1560
1553
  }
1561
1554
 
@@ -1567,14 +1560,8 @@ export class IntervalCollection
1567
1560
  return;
1568
1561
  }
1569
1562
 
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
- );
1563
+ const newStart = this.getSlideToSegment(interval.start);
1564
+ const newEnd = this.getSlideToSegment(interval.end);
1578
1565
 
1579
1566
  const id = interval.getIntervalId();
1580
1567
  const hasPendingChange = this.hasPendingEndpointChanges(id);
@@ -1606,16 +1593,14 @@ export class IntervalCollection
1606
1593
 
1607
1594
  if (needsStartUpdate) {
1608
1595
  const props = interval.start.properties;
1609
- interval.start = createPositionReferenceFromSegoff(
1610
- this.client,
1611
- newStart,
1612
- interval.start.refType,
1596
+ interval.start = createPositionReferenceFromSegoff({
1597
+ client: this.client,
1598
+ segoff: newStart,
1599
+ refType: interval.start.refType,
1613
1600
  op,
1614
- undefined,
1615
- undefined,
1616
- startReferenceSlidingPreference(interval.stickiness),
1617
- startReferenceSlidingPreference(interval.stickiness) === SlidingPreference.BACKWARD,
1618
- );
1601
+ slidingPreference: interval.start.slidingPreference,
1602
+ canSlideToEndpoint: interval.start.canSlideToEndpoint,
1603
+ });
1619
1604
  if (props) {
1620
1605
  interval.start.addProperties(props);
1621
1606
  }
@@ -1627,16 +1612,14 @@ export class IntervalCollection
1627
1612
  }
1628
1613
  if (needsEndUpdate) {
1629
1614
  const props = interval.end.properties;
1630
- interval.end = createPositionReferenceFromSegoff(
1631
- this.client,
1632
- newEnd,
1633
- interval.end.refType,
1615
+ interval.end = createPositionReferenceFromSegoff({
1616
+ client: this.client,
1617
+ segoff: newEnd,
1618
+ refType: interval.end.refType,
1634
1619
  op,
1635
- undefined,
1636
- undefined,
1637
- endReferenceSlidingPreference(interval.stickiness),
1638
- endReferenceSlidingPreference(interval.stickiness) === SlidingPreference.FORWARD,
1639
- );
1620
+ slidingPreference: interval.end.slidingPreference,
1621
+ canSlideToEndpoint: interval.end.canSlideToEndpoint,
1622
+ });
1640
1623
  if (props) {
1641
1624
  interval.end.addProperties(props);
1642
1625
  }
@@ -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;
@@ -3,8 +3,9 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import type { ListNode } from "@fluidframework/core-utils/internal";
6
7
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
7
- import { IMergeTreeOptions, ListNode } from "@fluidframework/merge-tree/internal";
8
+ import { IMergeTreeOptions, type ISegmentInternal } from "@fluidframework/merge-tree/internal";
8
9
 
9
10
  import type {
10
11
  IntervalCollection,
@@ -15,28 +16,34 @@ import {
15
16
  ISerializedInterval,
16
17
  IntervalDeltaOpType,
17
18
  SerializedIntervalDelta,
19
+ type SequenceIntervalClass,
18
20
  } from "./intervals/index.js";
19
21
 
20
22
  export interface IntervalAddLocalMetadata {
21
23
  type: typeof IntervalDeltaOpType.ADD;
22
24
  localSeq: number;
23
25
  endpointChangesNode?: ListNode<IntervalAddLocalMetadata | IntervalChangeLocalMetadata>;
24
- rebased?: ISerializedInterval;
25
- original: ISerializedInterval;
26
+ rebased?:
27
+ | Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
28
+ | "detached";
29
+ interval: SequenceIntervalClass;
26
30
  }
27
31
  export interface IntervalChangeLocalMetadata {
28
32
  type: typeof IntervalDeltaOpType.CHANGE;
29
33
  localSeq: number;
30
34
  previous: ISerializedInterval;
31
35
  endpointChangesNode?: ListNode<IntervalChangeLocalMetadata | IntervalChangeLocalMetadata>;
32
- rebased?: SerializedIntervalDelta;
33
- original: SerializedIntervalDelta;
36
+ rebased?:
37
+ | Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
38
+ | "detached";
39
+ interval: SequenceIntervalClass;
34
40
  }
35
41
  export interface IntervalDeleteLocalMetadata {
36
42
  type: typeof IntervalDeltaOpType.DELETE;
37
43
  localSeq: number;
38
44
  previous: ISerializedInterval;
39
45
  endpointChangesNode?: undefined;
46
+ interval?: undefined;
40
47
  }
41
48
  export type IntervalMessageLocalMetadata =
42
49
  | 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
+ }