@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
@@ -5,12 +5,12 @@
5
5
  /* eslint-disable no-bitwise */
6
6
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
7
7
  import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
8
- import { DetachedReferencePosition, ReferenceType, SlidingPreference, getSlideToSegoff, refTypeIncludesFlag, reservedRangeLabelsKey, Side, endpointPosAndSide, createLocalReconnectingPerspective, DoublyLinkedList, } from "@fluidframework/merge-tree/internal";
8
+ import { ReferenceType, getSlideToSegoff, refTypeIncludesFlag, reservedRangeLabelsKey, Side, endpointPosAndSide, createLocalReconnectingPerspective, DoublyLinkedList, SlidingPreference, } 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 { IntervalStickiness, IntervalType, createPositionReferenceFromSegoff, createSequenceInterval, endReferenceSlidingPreference, getSerializedProperties, startReferenceSlidingPreference, } from "./intervals/index.js";
13
- export function sidesFromStickiness(stickiness) {
12
+ import { IntervalStickiness, IntervalType, createPositionReferenceFromSegoff, createSequenceInterval, getSerializedProperties, } from "./intervals/index.js";
13
+ function sidesFromStickiness(stickiness) {
14
14
  const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
15
15
  const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
16
16
  return { startSide, endSide };
@@ -60,16 +60,6 @@ export function toSequencePlace(pos, side) {
60
60
  export function toOptionalSequencePlace(pos, side) {
61
61
  return typeof pos === "number" && side !== undefined ? { pos, side } : pos;
62
62
  }
63
- export function computeStickinessFromSide(startPos = -1, startSide = Side.Before, endPos = -1, endSide = Side.Before) {
64
- let stickiness = IntervalStickiness.NONE;
65
- if (startSide === Side.After || startPos === "start") {
66
- stickiness |= IntervalStickiness.START;
67
- }
68
- if (endSide === Side.Before || endPos === "end") {
69
- stickiness |= IntervalStickiness.END;
70
- }
71
- return stickiness;
72
- }
73
63
  export class LocalIntervalCollection {
74
64
  constructor(client, label, options,
75
65
  /** Callback invoked each time one of the endpoints of an interval slides. */
@@ -211,15 +201,15 @@ class IntervalCollectionIterator {
211
201
  function removeMetadataFromPendingChanges(localOpMetadataNode) {
212
202
  const acked = localOpMetadataNode?.remove()
213
203
  ?.data;
214
- assert(acked !== undefined, "local change must exist");
204
+ assert(acked !== undefined, 0xbbe /* local change must exist */);
215
205
  acked.endpointChangesNode?.remove();
216
206
  return acked;
217
207
  }
218
208
  function clearEmptyPendingEntry(pendingChanges, id) {
219
209
  const pending = pendingChanges[id];
220
- assert(pending !== undefined, "pending must exist for local process");
210
+ assert(pending !== undefined, 0xbbf /* pending must exist for local process */);
221
211
  if (pending.local.empty) {
222
- assert(pending.endpointChanges?.empty !== false, "endpointChanges must be empty if not pending changes");
212
+ assert(pending.endpointChanges?.empty !== false, 0xbc0 /* endpointChanges must be empty if not pending changes */);
223
213
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
224
214
  delete pendingChanges[id];
225
215
  }
@@ -246,6 +236,7 @@ export class IntervalCollection extends TypedEventEmitter {
246
236
  if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
247
237
  const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
248
238
  md.endpointChangesNode = endpointChanges.push(md).last;
239
+ md.rebased = undefined;
249
240
  }
250
241
  submitDelta(op, pending.local.push(md).last);
251
242
  };
@@ -332,7 +323,7 @@ export class IntervalCollection extends TypedEventEmitter {
332
323
  ? removeMetadataFromPendingChanges(maybeMetadata)
333
324
  : undefined;
334
325
  const { opName, value } = op;
335
- assert((local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type, "must be same type");
326
+ assert((local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type, 0xbc1 /* must be same type */);
336
327
  switch (opName) {
337
328
  case "add": {
338
329
  this.ackAdd(value, local, message,
@@ -357,12 +348,12 @@ export class IntervalCollection extends TypedEventEmitter {
357
348
  clearEmptyPendingEntry(this.pending, id);
358
349
  }
359
350
  }
360
- resubmitMessage(op, maybeMetadata) {
351
+ resubmitMessage(op, maybeMetadata, squash) {
361
352
  const { opName, value } = op;
362
353
  const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
363
354
  const rebasedValue = localOpMetadata.endpointChangesNode === undefined
364
355
  ? value
365
- : this.rebaseLocalInterval(localOpMetadata);
356
+ : this.rebaseLocalInterval(value, localOpMetadata, squash);
366
357
  if (rebasedValue === undefined) {
367
358
  const { id } = getSerializedProperties(value);
368
359
  clearEmptyPendingEntry(this.pending, id);
@@ -400,40 +391,43 @@ export class IntervalCollection extends TypedEventEmitter {
400
391
  throw new Error("unknown ops should not be stashed");
401
392
  }
402
393
  }
403
- rebasePositionWithSegmentSlide(pos, seqNumberFrom, localSeq) {
394
+ rebaseReferenceWithSegmentSlide(ref, localSeq, squash) {
404
395
  if (!this.client) {
405
396
  throw new LoggingError("mergeTree client must exist");
406
397
  }
407
- if (pos === "start" || pos === "end") {
408
- return pos;
409
- }
410
398
  const { clientId } = this.client.getCollabWindow();
411
- const { segment, offset } = this.client.getContainingSegment(pos, {
412
- referenceSequenceNumber: seqNumberFrom,
413
- clientId: this.client.getLongClientId(clientId),
414
- }, localSeq) ?? {};
415
- // if segment is undefined, it slid off the string
416
- assert(segment !== undefined && offset !== undefined, 0x54e /* No segment found */);
417
- const segoff = getSlideToSegoff({ segment, offset }, undefined, createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq), this.options.mergeTreeReferencesCanSlideToEndpoint);
399
+ const segment = ref.getSegment();
400
+ if (segment?.endpointType) {
401
+ return { segment, offset: 0 };
402
+ }
403
+ const offset = ref.getOffset();
404
+ const segoff = getSlideToSegoff(segment === undefined ? undefined : { segment, offset }, ref.slidingPreference, createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq, squash), ref.canSlideToEndpoint);
418
405
  // case happens when rebasing op, but concurrently entire string has been deleted
419
- if (segoff?.segment === undefined || segoff.offset === undefined) {
420
- return DetachedReferencePosition;
406
+ if (segoff === undefined) {
407
+ if (ref.canSlideToEndpoint !== true) {
408
+ return undefined;
409
+ }
410
+ return {
411
+ segment: ref.slidingPreference === SlidingPreference.FORWARD
412
+ ? this.client.endOfTree
413
+ : this.client.startOfTree,
414
+ offset: 0,
415
+ };
421
416
  }
422
- assert(offset !== undefined && 0 <= offset && offset < segment.cachedLength, 0x54f /* Invalid offset */);
423
- return this.client.findReconnectionPosition(segoff.segment, localSeq) + segoff.offset;
417
+ return segoff;
424
418
  }
425
- computeRebasedPositions(localOpMetadata) {
419
+ computeRebasedPositions(localOpMetadata, squash) {
426
420
  assert(this.client !== undefined, 0x550 /* Client should be defined when computing rebased position */);
427
- const { localSeq, original } = localOpMetadata;
428
- const rebased = { ...original };
429
- const { start, end, sequenceNumber } = original;
430
- if (start !== undefined) {
431
- rebased.start = this.rebasePositionWithSegmentSlide(start, sequenceNumber, localSeq);
421
+ const { localSeq, interval } = localOpMetadata;
422
+ const start = this.rebaseReferenceWithSegmentSlide(interval.start, localSeq, squash);
423
+ if (start === undefined) {
424
+ return "detached";
432
425
  }
433
- if (end !== undefined) {
434
- rebased.end = this.rebasePositionWithSegmentSlide(end, sequenceNumber, localSeq);
426
+ const end = this.rebaseReferenceWithSegmentSlide(interval.end, localSeq, squash);
427
+ if (end === undefined) {
428
+ return "detached";
435
429
  }
436
- return rebased;
430
+ return { start, end };
437
431
  }
438
432
  attachGraph(client, label) {
439
433
  if (this.attached) {
@@ -445,11 +439,11 @@ export class IntervalCollection extends TypedEventEmitter {
445
439
  // Instantiate the local interval collection based on the saved intervals
446
440
  this.client = client;
447
441
  if (client) {
448
- client.on("normalize", () => {
442
+ client.on("normalize", (squash) => {
449
443
  for (const pending of Object.values(this.pending)) {
450
444
  if (pending?.endpointChanges !== undefined) {
451
445
  for (const local of pending.endpointChanges) {
452
- local.data.rebased = this.computeRebasedPositions(local.data);
446
+ local.data.rebased = this.computeRebasedPositions(local.data, squash);
453
447
  }
454
448
  }
455
449
  }
@@ -538,7 +532,7 @@ export class IntervalCollection extends TypedEventEmitter {
538
532
  }, {
539
533
  type: "add",
540
534
  localSeq,
541
- original: serializedInterval,
535
+ interval,
542
536
  });
543
537
  }
544
538
  }
@@ -628,7 +622,7 @@ export class IntervalCollection extends TypedEventEmitter {
628
622
  type: "change",
629
623
  localSeq,
630
624
  previous: interval.serialize(),
631
- original: serializedInterval,
625
+ interval: newInterval ?? interval,
632
626
  };
633
627
  this.submitDelta({
634
628
  opName: "change",
@@ -641,8 +635,10 @@ export class IntervalCollection extends TypedEventEmitter {
641
635
  }
642
636
  if (newInterval) {
643
637
  this.emitChange(newInterval, interval, true, false);
644
- this.client?.removeLocalReferencePosition(interval.start);
645
- this.client?.removeLocalReferencePosition(interval.end);
638
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
639
+ interval.start.properties.interval = undefined;
640
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
641
+ interval.end.properties.interval = undefined;
646
642
  }
647
643
  return newInterval;
648
644
  }
@@ -725,47 +721,53 @@ export class IntervalCollection extends TypedEventEmitter {
725
721
  * to a range that no longer exists, and the interval was unable to slide.
726
722
  *
727
723
  */
728
- rebaseLocalInterval(localOpMetadata) {
729
- const original = localOpMetadata.original;
730
- if (!this.client) {
724
+ rebaseLocalInterval(original, localOpMetadata, squash) {
725
+ if (!this.client || !hasEndpointChanges(original)) {
731
726
  // If there's no associated mergeTree client, the originally submitted op is still correct.
732
727
  return original;
733
728
  }
734
- if (!this.attached) {
729
+ if (!this.attached || this.localCollection === undefined) {
735
730
  throw new LoggingError("attachSequence must be called");
736
731
  }
737
- const { localSeq } = localOpMetadata;
738
- const { intervalType, properties, stickiness, startSide, endSide } = original;
732
+ const { localSeq, interval } = localOpMetadata;
739
733
  const { id } = getSerializedProperties(original);
740
- const { start: startRebased, end: endRebased } = (localOpMetadata.rebased ??=
741
- this.computeRebasedPositions(localOpMetadata));
742
- const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(id);
743
- const rebased = {
744
- start: startRebased,
745
- end: endRebased,
746
- intervalType,
747
- sequenceNumber: this.client?.getCurrentSeq() ?? 0,
748
- properties,
749
- stickiness,
750
- startSide,
751
- endSide,
752
- };
734
+ const rebasedEndpoint = (localOpMetadata.rebased ??= this.computeRebasedPositions(localOpMetadata, squash));
735
+ const localInterval = this.localCollection.idIntervalIndex.getIntervalById(id);
753
736
  // if the interval slid off the string, rebase the op to be a noop and delete the interval.
754
- if (!this.options.mergeTreeReferencesCanSlideToEndpoint &&
755
- (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition)) {
756
- if (localInterval) {
737
+ if (rebasedEndpoint === "detached") {
738
+ if (localInterval !== undefined &&
739
+ (localInterval === interval || localOpMetadata.type === "add")) {
757
740
  this.localCollection?.removeExistingInterval(localInterval);
758
741
  }
759
742
  return undefined;
760
743
  }
761
- if (localInterval !== undefined) {
762
- // The rebased op may place this interval's endpoints on different segments. Calling `changeInterval` here
763
- // updates the local client's state to be consistent with the emitted op.
764
- this.localCollection?.changeInterval(localInterval, toOptionalSequencePlace(startRebased, startSide ?? Side.Before), toOptionalSequencePlace(endRebased, endSide ?? Side.Before), undefined, localSeq);
744
+ const { start, end } = rebasedEndpoint;
745
+ if (interval.start.getSegment() !== start.segment ||
746
+ interval.start.getOffset() !== start.offset ||
747
+ interval.end.getSegment() !== end.segment ||
748
+ interval.end.getOffset() !== end.offset) {
749
+ if (localInterval === interval) {
750
+ this.localCollection.removeExistingInterval(localInterval);
751
+ }
752
+ const old = interval.clone();
753
+ interval.moveEndpointReferences(rebasedEndpoint);
754
+ if (localInterval === interval) {
755
+ this.localCollection.add(interval);
756
+ this.emitChange(interval, old, true, true);
757
+ }
758
+ this.client.removeLocalReferencePosition(old.start);
759
+ this.client.removeLocalReferencePosition(old.end);
765
760
  }
766
- return rebased;
761
+ return {
762
+ ...original,
763
+ start: start.segment.endpointType ??
764
+ this.client.findReconnectionPosition(start.segment, localSeq) + start.offset,
765
+ end: end.segment.endpointType ??
766
+ this.client.findReconnectionPosition(end.segment, localSeq) + end.offset,
767
+ sequenceNumber: this.client?.getCurrentSeq() ?? 0,
768
+ };
767
769
  }
768
- getSlideToSegment(lref, slidingPreference) {
770
+ getSlideToSegment(lref) {
769
771
  if (!this.client) {
770
772
  throw new LoggingError("client does not exist");
771
773
  }
@@ -780,15 +782,15 @@ export class IntervalCollection extends TypedEventEmitter {
780
782
  if (segoff.segment.localRefs?.has(lref) !== true) {
781
783
  return undefined;
782
784
  }
783
- return getSlideToSegoff(segoff, slidingPreference, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint);
785
+ return getSlideToSegoff(segoff, lref.slidingPreference, undefined, lref.canSlideToEndpoint);
784
786
  }
785
787
  ackInterval(interval, op) {
786
788
  if (!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove) &&
787
789
  !refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove)) {
788
790
  return;
789
791
  }
790
- const newStart = this.getSlideToSegment(interval.start, startReferenceSlidingPreference(interval.stickiness));
791
- const newEnd = this.getSlideToSegment(interval.end, endReferenceSlidingPreference(interval.stickiness));
792
+ const newStart = this.getSlideToSegment(interval.start);
793
+ const newEnd = this.getSlideToSegment(interval.end);
792
794
  const id = interval.getIntervalId();
793
795
  const hasPendingChange = this.hasPendingEndpointChanges(id);
794
796
  if (!hasPendingChange) {
@@ -812,7 +814,14 @@ export class IntervalCollection extends TypedEventEmitter {
812
814
  }
813
815
  if (needsStartUpdate) {
814
816
  const props = interval.start.properties;
815
- interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op, undefined, undefined, startReferenceSlidingPreference(interval.stickiness), startReferenceSlidingPreference(interval.stickiness) === SlidingPreference.BACKWARD);
817
+ interval.start = createPositionReferenceFromSegoff({
818
+ client: this.client,
819
+ segoff: newStart,
820
+ refType: interval.start.refType,
821
+ op,
822
+ slidingPreference: interval.start.slidingPreference,
823
+ canSlideToEndpoint: interval.start.canSlideToEndpoint,
824
+ });
816
825
  if (props) {
817
826
  interval.start.addProperties(props);
818
827
  }
@@ -824,7 +833,14 @@ export class IntervalCollection extends TypedEventEmitter {
824
833
  }
825
834
  if (needsEndUpdate) {
826
835
  const props = interval.end.properties;
827
- interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op, undefined, undefined, endReferenceSlidingPreference(interval.stickiness), endReferenceSlidingPreference(interval.stickiness) === SlidingPreference.FORWARD);
836
+ interval.end = createPositionReferenceFromSegoff({
837
+ client: this.client,
838
+ segoff: newEnd,
839
+ refType: interval.end.refType,
840
+ op,
841
+ slidingPreference: interval.end.slidingPreference,
842
+ canSlideToEndpoint: interval.end.canSlideToEndpoint,
843
+ });
828
844
  if (props) {
829
845
  interval.end.addProperties(props);
830
846
  }