@fluidframework/sequence 2.41.0 → 2.43.0-343119
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.
- package/CHANGELOG.md +4 -0
- package/api-report/sequence.legacy.alpha.api.md +13 -5
- package/dist/intervalCollection.d.ts +14 -19
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +113 -128
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalCollectionMap.d.ts +3 -3
- package/dist/intervalCollectionMap.d.ts.map +1 -1
- package/dist/intervalCollectionMap.js.map +1 -1
- package/dist/intervalCollectionMapInterfaces.d.ts +22 -4
- package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -1
- package/dist/intervalCollectionMapInterfaces.js.map +1 -1
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +4 -4
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
- package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
- package/dist/intervals/intervalUtils.d.ts +12 -1
- package/dist/intervals/intervalUtils.d.ts.map +1 -1
- package/dist/intervals/intervalUtils.js.map +1 -1
- package/dist/intervals/sequenceInterval.d.ts +18 -5
- package/dist/intervals/sequenceInterval.d.ts.map +1 -1
- package/dist/intervals/sequenceInterval.js +2 -1
- package/dist/intervals/sequenceInterval.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/revertibles.d.ts.map +1 -1
- package/dist/revertibles.js +6 -3
- package/dist/revertibles.js.map +1 -1
- package/dist/sequence.d.ts +2 -1
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +6 -3
- package/dist/sequence.js.map +1 -1
- package/lib/intervalCollection.d.ts +14 -19
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +114 -129
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalCollectionMap.d.ts +3 -3
- package/lib/intervalCollectionMap.d.ts.map +1 -1
- package/lib/intervalCollectionMap.js.map +1 -1
- package/lib/intervalCollectionMapInterfaces.d.ts +22 -4
- package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
- package/lib/intervalCollectionMapInterfaces.js.map +1 -1
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +4 -4
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
- package/lib/intervalIndex/overlappingIntervalsIndex.js +1 -1
- package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
- package/lib/intervals/intervalUtils.d.ts +12 -1
- package/lib/intervals/intervalUtils.d.ts.map +1 -1
- package/lib/intervals/intervalUtils.js.map +1 -1
- package/lib/intervals/sequenceInterval.d.ts +18 -5
- package/lib/intervals/sequenceInterval.d.ts.map +1 -1
- package/lib/intervals/sequenceInterval.js +2 -1
- package/lib/intervals/sequenceInterval.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/revertibles.d.ts.map +1 -1
- package/lib/revertibles.js +6 -3
- package/lib/revertibles.js.map +1 -1
- package/lib/sequence.d.ts +2 -1
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +6 -3
- package/lib/sequence.js.map +1 -1
- package/package.json +17 -17
- package/src/intervalCollection.ts +191 -195
- package/src/intervalCollectionMap.ts +4 -11
- package/src/intervalCollectionMapInterfaces.ts +25 -5
- package/src/intervalIndex/overlappingIntervalsIndex.ts +15 -11
- package/src/intervals/intervalUtils.ts +12 -1
- package/src/intervals/sequenceInterval.ts +22 -4
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +7 -6
- package/src/sequence.ts +11 -13
|
@@ -25,14 +25,18 @@ import {
|
|
|
25
25
|
endpointPosAndSide,
|
|
26
26
|
type ISegmentInternal,
|
|
27
27
|
createLocalReconnectingPerspective,
|
|
28
|
+
DoublyLinkedList,
|
|
29
|
+
type ListNode,
|
|
28
30
|
} from "@fluidframework/merge-tree/internal";
|
|
29
31
|
import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
|
|
30
32
|
import { v4 as uuid } from "uuid";
|
|
31
33
|
|
|
32
34
|
import {
|
|
33
|
-
|
|
35
|
+
IntervalMessageLocalMetadata,
|
|
34
36
|
SequenceOptions,
|
|
35
37
|
type IIntervalCollectionTypeOperationValue,
|
|
38
|
+
type IntervalAddLocalMetadata,
|
|
39
|
+
type IntervalChangeLocalMetadata,
|
|
36
40
|
} from "./intervalCollectionMapInterfaces.js";
|
|
37
41
|
import {
|
|
38
42
|
createIdIntervalIndex,
|
|
@@ -600,6 +604,9 @@ export interface ISequenceIntervalCollection
|
|
|
600
604
|
{ start, end, props }: { start?: SequencePlace; end?: SequencePlace; props?: PropertySet },
|
|
601
605
|
): SequenceInterval | undefined;
|
|
602
606
|
|
|
607
|
+
/**
|
|
608
|
+
* @deprecated This api is not meant or necessary for external consumption and will be removed in subsequent release
|
|
609
|
+
*/
|
|
603
610
|
attachDeserializer(onDeserialize: DeserializeCallback): void;
|
|
604
611
|
/**
|
|
605
612
|
* @returns an iterator over all intervals in this collection.
|
|
@@ -686,6 +693,56 @@ export interface ISequenceIntervalCollection
|
|
|
686
693
|
nextInterval(pos: number): SequenceInterval | undefined;
|
|
687
694
|
}
|
|
688
695
|
|
|
696
|
+
type PendingChanges = Partial<
|
|
697
|
+
Record<
|
|
698
|
+
string,
|
|
699
|
+
{
|
|
700
|
+
/**
|
|
701
|
+
* The local metadatas are stores in order of submission, FIFO.
|
|
702
|
+
* This matches how ops are ordered, and should maintained
|
|
703
|
+
* across, submit, process, resubmit, and rollback.
|
|
704
|
+
*/
|
|
705
|
+
local: DoublyLinkedList<IntervalMessageLocalMetadata>;
|
|
706
|
+
/**
|
|
707
|
+
* The endpointChanges are unordered, and are used to determine
|
|
708
|
+
* if any local changes also change the endpoints. The nodes of the
|
|
709
|
+
* list are also stored in the individual change op metadatas, and
|
|
710
|
+
* are removed as those metadatas are handled.
|
|
711
|
+
*/
|
|
712
|
+
endpointChanges?: DoublyLinkedList<
|
|
713
|
+
IntervalAddLocalMetadata | IntervalChangeLocalMetadata
|
|
714
|
+
>;
|
|
715
|
+
}
|
|
716
|
+
>
|
|
717
|
+
>;
|
|
718
|
+
|
|
719
|
+
function removeMetadataFromPendingChanges(
|
|
720
|
+
localOpMetadataNode: ListNode<IntervalMessageLocalMetadata> | unknown,
|
|
721
|
+
): IntervalMessageLocalMetadata {
|
|
722
|
+
const acked = (localOpMetadataNode as ListNode<IntervalMessageLocalMetadata>)?.remove()
|
|
723
|
+
?.data;
|
|
724
|
+
assert(acked !== undefined, "local change must exist");
|
|
725
|
+
acked.endpointChangesNode?.remove();
|
|
726
|
+
return acked;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function clearEmptyPendingEntry(pendingChanges: PendingChanges, id: string) {
|
|
730
|
+
const pending = pendingChanges[id];
|
|
731
|
+
assert(pending !== undefined, "pending must exist for local process");
|
|
732
|
+
if (pending.local.empty) {
|
|
733
|
+
assert(
|
|
734
|
+
pending.endpointChanges?.empty !== false,
|
|
735
|
+
"endpointChanges must be empty if not pending changes",
|
|
736
|
+
);
|
|
737
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
738
|
+
delete pendingChanges[id];
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function hasEndpointChanges(serialized: SerializedIntervalDelta) {
|
|
743
|
+
return serialized.start !== undefined && serialized.end !== undefined;
|
|
744
|
+
}
|
|
745
|
+
|
|
689
746
|
/**
|
|
690
747
|
* {@inheritdoc IIntervalCollection}
|
|
691
748
|
*/
|
|
@@ -697,37 +754,37 @@ export class IntervalCollection
|
|
|
697
754
|
private localCollection: LocalIntervalCollection | undefined;
|
|
698
755
|
private onDeserialize: DeserializeCallback | undefined;
|
|
699
756
|
private client: Client | undefined;
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
ISerializedInterval | SerializedIntervalDelta
|
|
703
|
-
>();
|
|
704
|
-
private readonly localSeqToRebasedInterval = new Map<
|
|
705
|
-
number,
|
|
706
|
-
ISerializedInterval | SerializedIntervalDelta
|
|
707
|
-
>();
|
|
708
|
-
private readonly pendingChangesStart: Map<string, ISerializedIntervalCollectionV1> = new Map<
|
|
709
|
-
string,
|
|
710
|
-
ISerializedIntervalCollectionV1
|
|
711
|
-
>();
|
|
712
|
-
private readonly pendingChangesEnd: Map<string, ISerializedIntervalCollectionV1> = new Map<
|
|
713
|
-
string,
|
|
714
|
-
ISerializedIntervalCollectionV1
|
|
715
|
-
>();
|
|
757
|
+
|
|
758
|
+
private readonly pending: PendingChanges = {};
|
|
716
759
|
|
|
717
760
|
public get attached(): boolean {
|
|
718
761
|
return !!this.localCollection;
|
|
719
762
|
}
|
|
720
763
|
|
|
764
|
+
private readonly submitDelta: (
|
|
765
|
+
op: IIntervalCollectionTypeOperationValue,
|
|
766
|
+
md: IntervalMessageLocalMetadata,
|
|
767
|
+
) => void;
|
|
768
|
+
|
|
721
769
|
constructor(
|
|
722
|
-
|
|
723
|
-
op: IIntervalCollectionTypeOperationValue,
|
|
724
|
-
md: IMapMessageLocalMetadata,
|
|
725
|
-
) => void,
|
|
770
|
+
submitDelta: (op: IIntervalCollectionTypeOperationValue, md: unknown) => void,
|
|
726
771
|
serializedIntervals: ISerializedIntervalCollectionV1 | ISerializedIntervalCollectionV2,
|
|
727
772
|
private readonly options: Partial<SequenceOptions> = {},
|
|
728
773
|
) {
|
|
729
774
|
super();
|
|
730
775
|
|
|
776
|
+
this.submitDelta = (op, md) => {
|
|
777
|
+
const { id } = getSerializedProperties(op.value);
|
|
778
|
+
const pending = (this.pending[id] ??= {
|
|
779
|
+
local: new DoublyLinkedList(),
|
|
780
|
+
});
|
|
781
|
+
if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
|
|
782
|
+
const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
|
|
783
|
+
md.endpointChangesNode = endpointChanges.push(md).last;
|
|
784
|
+
}
|
|
785
|
+
submitDelta(op, pending.local.push(md).last);
|
|
786
|
+
};
|
|
787
|
+
|
|
731
788
|
this.savedSerializedIntervals = Array.isArray(serializedIntervals)
|
|
732
789
|
? serializedIntervals
|
|
733
790
|
: serializedIntervals.intervals.map((i) =>
|
|
@@ -769,14 +826,12 @@ export class IntervalCollection
|
|
|
769
826
|
return true;
|
|
770
827
|
}
|
|
771
828
|
|
|
772
|
-
public rollback(
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
) {
|
|
776
|
-
const { opName, value } = op;
|
|
829
|
+
public rollback(op: IIntervalCollectionTypeOperationValue, maybeMetadata: unknown) {
|
|
830
|
+
const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
|
|
831
|
+
const { value } = op;
|
|
777
832
|
const { id, properties } = getSerializedProperties(value);
|
|
778
|
-
const {
|
|
779
|
-
switch (
|
|
833
|
+
const { type } = localOpMetadata;
|
|
834
|
+
switch (type) {
|
|
780
835
|
case "add": {
|
|
781
836
|
const interval = this.getIntervalById(id);
|
|
782
837
|
if (interval) {
|
|
@@ -785,9 +840,8 @@ export class IntervalCollection
|
|
|
785
840
|
break;
|
|
786
841
|
}
|
|
787
842
|
case "change": {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const endpointsChanged = value.start !== undefined && value.end !== undefined;
|
|
843
|
+
const { previous } = localOpMetadata;
|
|
844
|
+
const endpointsChanged = hasEndpointChanges(value);
|
|
791
845
|
const start = endpointsChanged
|
|
792
846
|
? toOptionalSequencePlace(previous.start, previous.startSide)
|
|
793
847
|
: undefined;
|
|
@@ -800,14 +854,10 @@ export class IntervalCollection
|
|
|
800
854
|
props: Object.keys(properties).length > 0 ? properties : undefined,
|
|
801
855
|
rollback: true,
|
|
802
856
|
});
|
|
803
|
-
this.localSeqToSerializedInterval.delete(localSeq);
|
|
804
|
-
if (endpointsChanged) {
|
|
805
|
-
this.removePendingChange(value);
|
|
806
|
-
}
|
|
807
857
|
break;
|
|
808
858
|
}
|
|
809
859
|
case "delete": {
|
|
810
|
-
|
|
860
|
+
const { previous } = localOpMetadata;
|
|
811
861
|
this.add({
|
|
812
862
|
id,
|
|
813
863
|
start: toSequencePlace(previous.start, previous.startSide),
|
|
@@ -818,20 +868,37 @@ export class IntervalCollection
|
|
|
818
868
|
break;
|
|
819
869
|
}
|
|
820
870
|
default:
|
|
821
|
-
unreachableCase(
|
|
871
|
+
unreachableCase(type);
|
|
822
872
|
}
|
|
873
|
+
|
|
874
|
+
clearEmptyPendingEntry(this.pending, id);
|
|
823
875
|
}
|
|
824
876
|
|
|
825
877
|
public process(
|
|
826
878
|
op: IIntervalCollectionTypeOperationValue,
|
|
827
879
|
local: boolean,
|
|
828
880
|
message: ISequencedDocumentMessage,
|
|
829
|
-
|
|
881
|
+
maybeMetadata: unknown,
|
|
830
882
|
) {
|
|
883
|
+
const localOpMetadata = local
|
|
884
|
+
? removeMetadataFromPendingChanges(maybeMetadata)
|
|
885
|
+
: undefined;
|
|
886
|
+
|
|
831
887
|
const { opName, value } = op;
|
|
888
|
+
assert(
|
|
889
|
+
(local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type,
|
|
890
|
+
"must be same type",
|
|
891
|
+
);
|
|
832
892
|
switch (opName) {
|
|
833
893
|
case "add": {
|
|
834
|
-
this.ackAdd(
|
|
894
|
+
this.ackAdd(
|
|
895
|
+
value,
|
|
896
|
+
local,
|
|
897
|
+
message,
|
|
898
|
+
// this cast is safe because of the above assert which
|
|
899
|
+
// validates the op and metadata types match for local changes
|
|
900
|
+
localOpMetadata as IntervalAddLocalMetadata | undefined,
|
|
901
|
+
);
|
|
835
902
|
break;
|
|
836
903
|
}
|
|
837
904
|
|
|
@@ -841,24 +908,36 @@ export class IntervalCollection
|
|
|
841
908
|
}
|
|
842
909
|
|
|
843
910
|
case "change": {
|
|
844
|
-
this.ackChange(value, local, message
|
|
911
|
+
this.ackChange(value, local, message);
|
|
845
912
|
break;
|
|
846
913
|
}
|
|
847
914
|
default:
|
|
848
915
|
unreachableCase(opName);
|
|
849
916
|
}
|
|
917
|
+
|
|
918
|
+
if (local) {
|
|
919
|
+
const { id } = getSerializedProperties(value);
|
|
920
|
+
clearEmptyPendingEntry(this.pending, id);
|
|
921
|
+
}
|
|
850
922
|
}
|
|
851
923
|
|
|
852
924
|
public resubmitMessage(
|
|
853
925
|
op: IIntervalCollectionTypeOperationValue,
|
|
854
|
-
|
|
926
|
+
maybeMetadata: unknown,
|
|
855
927
|
): void {
|
|
856
928
|
const { opName, value } = op;
|
|
857
|
-
|
|
929
|
+
|
|
930
|
+
const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
|
|
931
|
+
|
|
858
932
|
const rebasedValue =
|
|
859
|
-
|
|
933
|
+
localOpMetadata.endpointChangesNode === undefined
|
|
934
|
+
? value
|
|
935
|
+
: this.rebaseLocalInterval(localOpMetadata);
|
|
936
|
+
|
|
860
937
|
if (rebasedValue === undefined) {
|
|
861
|
-
|
|
938
|
+
const { id } = getSerializedProperties(value);
|
|
939
|
+
clearEmptyPendingEntry(this.pending, id);
|
|
940
|
+
return;
|
|
862
941
|
}
|
|
863
942
|
|
|
864
943
|
this.submitDelta({ opName, value: rebasedValue as any }, localOpMetadata);
|
|
@@ -909,28 +988,28 @@ export class IntervalCollection
|
|
|
909
988
|
}
|
|
910
989
|
|
|
911
990
|
const { clientId } = this.client.getCollabWindow();
|
|
912
|
-
const { segment, offset } =
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
991
|
+
const { segment, offset } =
|
|
992
|
+
this.client.getContainingSegment(
|
|
993
|
+
pos,
|
|
994
|
+
{
|
|
995
|
+
referenceSequenceNumber: seqNumberFrom,
|
|
996
|
+
clientId: this.client.getLongClientId(clientId),
|
|
997
|
+
},
|
|
998
|
+
localSeq,
|
|
999
|
+
) ?? {};
|
|
920
1000
|
|
|
921
1001
|
// if segment is undefined, it slid off the string
|
|
922
|
-
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
1002
|
+
assert(segment !== undefined && offset !== undefined, 0x54e /* No segment found */);
|
|
923
1003
|
|
|
924
|
-
const segoff =
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
) ?? segment;
|
|
1004
|
+
const segoff = getSlideToSegoff(
|
|
1005
|
+
{ segment, offset },
|
|
1006
|
+
undefined,
|
|
1007
|
+
createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq),
|
|
1008
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1009
|
+
);
|
|
931
1010
|
|
|
932
1011
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
933
|
-
if (segoff
|
|
1012
|
+
if (segoff?.segment === undefined || segoff.offset === undefined) {
|
|
934
1013
|
return DetachedReferencePosition;
|
|
935
1014
|
}
|
|
936
1015
|
|
|
@@ -942,17 +1021,13 @@ export class IntervalCollection
|
|
|
942
1021
|
}
|
|
943
1022
|
|
|
944
1023
|
private computeRebasedPositions(
|
|
945
|
-
|
|
1024
|
+
localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
|
|
946
1025
|
): ISerializedInterval | SerializedIntervalDelta {
|
|
947
1026
|
assert(
|
|
948
1027
|
this.client !== undefined,
|
|
949
1028
|
0x550 /* Client should be defined when computing rebased position */,
|
|
950
1029
|
);
|
|
951
|
-
const original =
|
|
952
|
-
assert(
|
|
953
|
-
original !== undefined,
|
|
954
|
-
0x551 /* Failed to store pending serialized interval info for this localSeq. */,
|
|
955
|
-
);
|
|
1030
|
+
const { localSeq, original } = localOpMetadata;
|
|
956
1031
|
const rebased = { ...original };
|
|
957
1032
|
const { start, end, sequenceNumber } = original;
|
|
958
1033
|
if (start !== undefined) {
|
|
@@ -977,8 +1052,12 @@ export class IntervalCollection
|
|
|
977
1052
|
this.client = client;
|
|
978
1053
|
if (client) {
|
|
979
1054
|
client.on("normalize", () => {
|
|
980
|
-
for (const
|
|
981
|
-
|
|
1055
|
+
for (const pending of Object.values(this.pending)) {
|
|
1056
|
+
if (pending?.endpointChanges !== undefined) {
|
|
1057
|
+
for (const local of pending.endpointChanges) {
|
|
1058
|
+
local.data.rebased = this.computeRebasedPositions(local.data);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
982
1061
|
}
|
|
983
1062
|
});
|
|
984
1063
|
}
|
|
@@ -1110,8 +1189,10 @@ export class IntervalCollection
|
|
|
1110
1189
|
|
|
1111
1190
|
this.assertStickinessEnabled(start, end);
|
|
1112
1191
|
|
|
1192
|
+
const intervalId = id ?? uuid();
|
|
1193
|
+
|
|
1113
1194
|
const interval: SequenceIntervalClass = this.localCollection.addInterval(
|
|
1114
|
-
|
|
1195
|
+
intervalId,
|
|
1115
1196
|
toSequencePlace(startPos, startSide),
|
|
1116
1197
|
toSequencePlace(endPos, endSide),
|
|
1117
1198
|
props,
|
|
@@ -1127,15 +1208,15 @@ export class IntervalCollection
|
|
|
1127
1208
|
const serializedInterval: ISerializedInterval = interval.serialize();
|
|
1128
1209
|
const localSeq = this.getNextLocalSeq();
|
|
1129
1210
|
if (this.isCollaborating && rollback !== true) {
|
|
1130
|
-
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1131
|
-
|
|
1132
1211
|
this.submitDelta(
|
|
1133
1212
|
{
|
|
1134
1213
|
opName: "add",
|
|
1135
1214
|
value: serializedInterval,
|
|
1136
1215
|
},
|
|
1137
1216
|
{
|
|
1217
|
+
type: "add",
|
|
1138
1218
|
localSeq,
|
|
1219
|
+
original: serializedInterval,
|
|
1139
1220
|
},
|
|
1140
1221
|
);
|
|
1141
1222
|
}
|
|
@@ -1166,14 +1247,16 @@ export class IntervalCollection
|
|
|
1166
1247
|
if (interval) {
|
|
1167
1248
|
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1168
1249
|
if (local && rollback !== true) {
|
|
1250
|
+
const value = interval.serialize();
|
|
1169
1251
|
this.submitDelta(
|
|
1170
1252
|
{
|
|
1171
1253
|
opName: "delete",
|
|
1172
|
-
value
|
|
1254
|
+
value,
|
|
1173
1255
|
},
|
|
1174
1256
|
{
|
|
1257
|
+
type: "delete",
|
|
1175
1258
|
localSeq: this.getNextLocalSeq(),
|
|
1176
|
-
previous:
|
|
1259
|
+
previous: value,
|
|
1177
1260
|
},
|
|
1178
1261
|
);
|
|
1179
1262
|
} else {
|
|
@@ -1258,18 +1341,19 @@ export class IntervalCollection
|
|
|
1258
1341
|
).serializeDelta({ props, includeEndpoints: changeEndpoints });
|
|
1259
1342
|
const localSeq = this.getNextLocalSeq();
|
|
1260
1343
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1344
|
+
const metadata: IntervalChangeLocalMetadata = {
|
|
1345
|
+
type: "change",
|
|
1346
|
+
localSeq,
|
|
1347
|
+
previous: interval.serialize(),
|
|
1348
|
+
original: serializedInterval,
|
|
1349
|
+
};
|
|
1263
1350
|
|
|
1264
1351
|
this.submitDelta(
|
|
1265
1352
|
{
|
|
1266
1353
|
opName: "change",
|
|
1267
1354
|
value: serializedInterval,
|
|
1268
1355
|
},
|
|
1269
|
-
|
|
1270
|
-
localSeq,
|
|
1271
|
-
previous: interval.serialize(),
|
|
1272
|
-
},
|
|
1356
|
+
metadata,
|
|
1273
1357
|
);
|
|
1274
1358
|
}
|
|
1275
1359
|
if (deltaProps !== undefined) {
|
|
@@ -1298,98 +1382,26 @@ export class IntervalCollection
|
|
|
1298
1382
|
return this.client?.getCollabWindow().collaborating ?? false;
|
|
1299
1383
|
}
|
|
1300
1384
|
|
|
1301
|
-
private
|
|
1302
|
-
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
if (serializedInterval.start !== undefined) {
|
|
1306
|
-
this.addPendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
|
|
1307
|
-
}
|
|
1308
|
-
if (serializedInterval.end !== undefined) {
|
|
1309
|
-
this.addPendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
private addPendingChangeHelper(
|
|
1314
|
-
id: string,
|
|
1315
|
-
pendingChanges: Map<string, SerializedIntervalDelta[]>,
|
|
1316
|
-
serializedInterval: SerializedIntervalDelta,
|
|
1317
|
-
) {
|
|
1318
|
-
let entries: SerializedIntervalDelta[] | undefined = pendingChanges.get(id);
|
|
1319
|
-
if (!entries) {
|
|
1320
|
-
entries = [];
|
|
1321
|
-
pendingChanges.set(id, entries);
|
|
1322
|
-
}
|
|
1323
|
-
entries.push(serializedInterval);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
private removePendingChange(serializedInterval: SerializedIntervalDelta) {
|
|
1327
|
-
// Change ops always have an ID.
|
|
1328
|
-
const { id } = getSerializedProperties(serializedInterval);
|
|
1329
|
-
if (serializedInterval.start !== undefined) {
|
|
1330
|
-
this.removePendingChangeHelper(id, this.pendingChangesStart, serializedInterval);
|
|
1331
|
-
}
|
|
1332
|
-
if (serializedInterval.end !== undefined) {
|
|
1333
|
-
this.removePendingChangeHelper(id, this.pendingChangesEnd, serializedInterval);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
private removePendingChangeHelper(
|
|
1338
|
-
id: string,
|
|
1339
|
-
pendingChanges: Map<string, SerializedIntervalDelta[]>,
|
|
1340
|
-
serializedInterval: SerializedIntervalDelta,
|
|
1341
|
-
) {
|
|
1342
|
-
const entries = pendingChanges.get(id);
|
|
1343
|
-
if (entries) {
|
|
1344
|
-
const pendingChange = entries.shift();
|
|
1345
|
-
if (entries.length === 0) {
|
|
1346
|
-
pendingChanges.delete(id);
|
|
1347
|
-
}
|
|
1348
|
-
if (
|
|
1349
|
-
pendingChange?.start !== serializedInterval.start ||
|
|
1350
|
-
pendingChange?.end !== serializedInterval.end
|
|
1351
|
-
) {
|
|
1352
|
-
throw new LoggingError("Mismatch in pending changes");
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
private hasPendingChangeStart(id: string) {
|
|
1358
|
-
const entries = this.pendingChangesStart.get(id);
|
|
1359
|
-
return entries && entries.length !== 0;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
private hasPendingChangeEnd(id: string) {
|
|
1363
|
-
const entries = this.pendingChangesEnd.get(id);
|
|
1364
|
-
return entries && entries.length !== 0;
|
|
1385
|
+
private hasPendingEndpointChanges(id: string) {
|
|
1386
|
+
return this.pending[id]?.endpointChanges?.empty === false;
|
|
1365
1387
|
}
|
|
1366
1388
|
|
|
1367
1389
|
public ackChange(
|
|
1368
1390
|
serializedInterval: SerializedIntervalDelta,
|
|
1369
1391
|
local: boolean,
|
|
1370
1392
|
op: ISequencedDocumentMessage,
|
|
1371
|
-
localOpMetadata: IMapMessageLocalMetadata | undefined,
|
|
1372
1393
|
) {
|
|
1373
1394
|
if (!this.localCollection) {
|
|
1374
1395
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1375
1396
|
}
|
|
1376
1397
|
|
|
1377
|
-
if (local) {
|
|
1378
|
-
assert(
|
|
1379
|
-
localOpMetadata !== undefined,
|
|
1380
|
-
0x552 /* op metadata should be defined for local op */,
|
|
1381
|
-
);
|
|
1382
|
-
this.localSeqToSerializedInterval.delete(localOpMetadata?.localSeq);
|
|
1383
|
-
// This is an ack from the server. Remove the pending change.
|
|
1384
|
-
this.removePendingChange(serializedInterval);
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
1398
|
// Note that the ID is in the property bag only to allow us to find the interval.
|
|
1388
1399
|
// This API cannot change the ID, and writing to the ID property will result in an exception. So we
|
|
1389
1400
|
// strip it out of the properties here.
|
|
1390
1401
|
const { id, properties } = getSerializedProperties(serializedInterval);
|
|
1391
1402
|
assert(id !== undefined, 0x3fe /* id must exist on the interval */);
|
|
1392
1403
|
const interval: SequenceIntervalClass | undefined = this.getIntervalById(id);
|
|
1404
|
+
|
|
1393
1405
|
if (!interval) {
|
|
1394
1406
|
// The interval has been removed locally; no-op.
|
|
1395
1407
|
return;
|
|
@@ -1405,10 +1417,8 @@ export class IntervalCollection
|
|
|
1405
1417
|
let start: number | "start" | "end" | undefined;
|
|
1406
1418
|
let end: number | "start" | "end" | undefined;
|
|
1407
1419
|
// Track pending start/end independently of one another.
|
|
1408
|
-
if (!this.
|
|
1420
|
+
if (!this.hasPendingEndpointChanges(id)) {
|
|
1409
1421
|
start = serializedInterval.start;
|
|
1410
|
-
}
|
|
1411
|
-
if (!this.hasPendingChangeEnd(id)) {
|
|
1412
1422
|
end = serializedInterval.end;
|
|
1413
1423
|
}
|
|
1414
1424
|
|
|
@@ -1467,22 +1477,22 @@ export class IntervalCollection
|
|
|
1467
1477
|
*
|
|
1468
1478
|
*/
|
|
1469
1479
|
public rebaseLocalInterval(
|
|
1470
|
-
|
|
1471
|
-
serializedInterval: SerializedIntervalDelta,
|
|
1472
|
-
localSeq: number,
|
|
1480
|
+
localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
|
|
1473
1481
|
): SerializedIntervalDelta | undefined {
|
|
1482
|
+
const original = localOpMetadata.original;
|
|
1474
1483
|
if (!this.client) {
|
|
1475
1484
|
// If there's no associated mergeTree client, the originally submitted op is still correct.
|
|
1476
|
-
return
|
|
1485
|
+
return original;
|
|
1477
1486
|
}
|
|
1478
1487
|
if (!this.attached) {
|
|
1479
1488
|
throw new LoggingError("attachSequence must be called");
|
|
1480
1489
|
}
|
|
1481
1490
|
|
|
1482
|
-
const {
|
|
1483
|
-
const {
|
|
1484
|
-
const {
|
|
1485
|
-
|
|
1491
|
+
const { localSeq } = localOpMetadata;
|
|
1492
|
+
const { intervalType, properties, stickiness, startSide, endSide } = original;
|
|
1493
|
+
const { id } = getSerializedProperties(original);
|
|
1494
|
+
const { start: startRebased, end: endRebased } = (localOpMetadata.rebased ??=
|
|
1495
|
+
this.computeRebasedPositions(localOpMetadata));
|
|
1486
1496
|
|
|
1487
1497
|
const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(id);
|
|
1488
1498
|
|
|
@@ -1497,15 +1507,6 @@ export class IntervalCollection
|
|
|
1497
1507
|
endSide,
|
|
1498
1508
|
};
|
|
1499
1509
|
|
|
1500
|
-
if (
|
|
1501
|
-
opName === "change" &&
|
|
1502
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when .hasPendingChangeStart returns false.
|
|
1503
|
-
(this.hasPendingChangeStart(id) || this.hasPendingChangeEnd(id))
|
|
1504
|
-
) {
|
|
1505
|
-
this.removePendingChange(serializedInterval);
|
|
1506
|
-
this.addPendingChange(id, rebased);
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1510
|
// if the interval slid off the string, rebase the op to be a noop and delete the interval.
|
|
1510
1511
|
if (
|
|
1511
1512
|
!this.options.mergeTreeReferencesCanSlideToEndpoint &&
|
|
@@ -1535,28 +1536,27 @@ export class IntervalCollection
|
|
|
1535
1536
|
private getSlideToSegment(
|
|
1536
1537
|
lref: LocalReferencePosition,
|
|
1537
1538
|
slidingPreference: SlidingPreference,
|
|
1538
|
-
): { segment: ISegment
|
|
1539
|
+
): { segment: ISegment; offset: number } | undefined {
|
|
1539
1540
|
if (!this.client) {
|
|
1540
1541
|
throw new LoggingError("client does not exist");
|
|
1541
1542
|
}
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1543
|
+
const segment: ISegmentInternal | undefined = lref.getSegment();
|
|
1544
|
+
if (segment === undefined) {
|
|
1545
|
+
return undefined;
|
|
1546
|
+
}
|
|
1547
|
+
const segoff = {
|
|
1548
|
+
segment,
|
|
1544
1549
|
offset: lref.getOffset(),
|
|
1545
1550
|
};
|
|
1546
|
-
if (segoff.segment
|
|
1551
|
+
if (segoff.segment.localRefs?.has(lref) !== true) {
|
|
1547
1552
|
return undefined;
|
|
1548
1553
|
}
|
|
1549
|
-
|
|
1554
|
+
return getSlideToSegoff(
|
|
1550
1555
|
segoff,
|
|
1551
1556
|
slidingPreference,
|
|
1552
1557
|
undefined,
|
|
1553
1558
|
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1554
1559
|
);
|
|
1555
|
-
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
1556
|
-
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
1557
|
-
? undefined
|
|
1558
|
-
: newSegoff;
|
|
1559
|
-
return value;
|
|
1560
1560
|
}
|
|
1561
1561
|
|
|
1562
1562
|
private ackInterval(interval: SequenceIntervalClass, op: ISequencedDocumentMessage): void {
|
|
@@ -1577,19 +1577,16 @@ export class IntervalCollection
|
|
|
1577
1577
|
);
|
|
1578
1578
|
|
|
1579
1579
|
const id = interval.getIntervalId();
|
|
1580
|
-
const
|
|
1581
|
-
const hasPendingEndChange = this.hasPendingChangeEnd(id);
|
|
1580
|
+
const hasPendingChange = this.hasPendingEndpointChanges(id);
|
|
1582
1581
|
|
|
1583
|
-
if (!
|
|
1582
|
+
if (!hasPendingChange) {
|
|
1584
1583
|
setSlideOnRemove(interval.start);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
if (!hasPendingEndChange) {
|
|
1588
1584
|
setSlideOnRemove(interval.end);
|
|
1589
1585
|
}
|
|
1590
1586
|
|
|
1591
|
-
const needsStartUpdate =
|
|
1592
|
-
|
|
1587
|
+
const needsStartUpdate =
|
|
1588
|
+
newStart?.segment !== interval.start.getSegment() && !hasPendingChange;
|
|
1589
|
+
const needsEndUpdate = newEnd?.segment !== interval.end.getSegment() && !hasPendingChange;
|
|
1593
1590
|
|
|
1594
1591
|
if (needsStartUpdate || needsEndUpdate) {
|
|
1595
1592
|
if (!this.localCollection) {
|
|
@@ -1658,7 +1655,7 @@ export class IntervalCollection
|
|
|
1658
1655
|
serializedInterval: ISerializedInterval,
|
|
1659
1656
|
local: boolean,
|
|
1660
1657
|
op: ISequencedDocumentMessage,
|
|
1661
|
-
localOpMetadata:
|
|
1658
|
+
localOpMetadata: IntervalAddLocalMetadata | undefined,
|
|
1662
1659
|
) {
|
|
1663
1660
|
const { id, properties } = getSerializedProperties(serializedInterval);
|
|
1664
1661
|
|
|
@@ -1667,7 +1664,6 @@ export class IntervalCollection
|
|
|
1667
1664
|
localOpMetadata !== undefined,
|
|
1668
1665
|
0x553 /* op metadata should be defined for local op */,
|
|
1669
1666
|
);
|
|
1670
|
-
this.localSeqToSerializedInterval.delete(localOpMetadata.localSeq);
|
|
1671
1667
|
const localInterval = this.getIntervalById(id);
|
|
1672
1668
|
if (localInterval) {
|
|
1673
1669
|
this.ackInterval(localInterval, op);
|