@fluidframework/sequence 2.42.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/api-report/sequence.legacy.alpha.api.md +13 -5
- package/dist/intervalCollection.d.ts +14 -15
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +109 -104
- 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 -5
- 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.map +1 -1
- package/dist/sequence.js +1 -1
- package/dist/sequence.js.map +1 -1
- package/lib/intervalCollection.d.ts +14 -15
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +110 -105
- 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 -5
- 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.map +1 -1
- package/lib/sequence.js +1 -1
- package/lib/sequence.js.map +1 -1
- package/package.json +17 -17
- package/src/intervalCollection.ts +185 -159
- package/src/intervalCollectionMap.ts +4 -11
- package/src/intervalCollectionMapInterfaces.ts +25 -6
- 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 +5 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/sequence",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.43.0-343119",
|
|
4
4
|
"description": "Distributed sequence",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -91,33 +91,33 @@
|
|
|
91
91
|
"temp-directory": "nyc/.nyc_output"
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@fluid-internal/client-utils": "
|
|
95
|
-
"@fluidframework/core-interfaces": "
|
|
96
|
-
"@fluidframework/core-utils": "
|
|
97
|
-
"@fluidframework/datastore-definitions": "
|
|
98
|
-
"@fluidframework/driver-definitions": "
|
|
99
|
-
"@fluidframework/merge-tree": "
|
|
100
|
-
"@fluidframework/runtime-definitions": "
|
|
101
|
-
"@fluidframework/runtime-utils": "
|
|
102
|
-
"@fluidframework/shared-object-base": "
|
|
103
|
-
"@fluidframework/telemetry-utils": "
|
|
94
|
+
"@fluid-internal/client-utils": "2.43.0-343119",
|
|
95
|
+
"@fluidframework/core-interfaces": "2.43.0-343119",
|
|
96
|
+
"@fluidframework/core-utils": "2.43.0-343119",
|
|
97
|
+
"@fluidframework/datastore-definitions": "2.43.0-343119",
|
|
98
|
+
"@fluidframework/driver-definitions": "2.43.0-343119",
|
|
99
|
+
"@fluidframework/merge-tree": "2.43.0-343119",
|
|
100
|
+
"@fluidframework/runtime-definitions": "2.43.0-343119",
|
|
101
|
+
"@fluidframework/runtime-utils": "2.43.0-343119",
|
|
102
|
+
"@fluidframework/shared-object-base": "2.43.0-343119",
|
|
103
|
+
"@fluidframework/telemetry-utils": "2.43.0-343119",
|
|
104
104
|
"double-ended-queue": "^2.1.0-0",
|
|
105
105
|
"uuid": "^9.0.0"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"@arethetypeswrong/cli": "^0.17.1",
|
|
109
109
|
"@biomejs/biome": "~1.9.3",
|
|
110
|
-
"@fluid-internal/mocha-test-setup": "
|
|
111
|
-
"@fluid-private/stochastic-test-utils": "
|
|
112
|
-
"@fluid-private/test-dds-utils": "
|
|
110
|
+
"@fluid-internal/mocha-test-setup": "2.43.0-343119",
|
|
111
|
+
"@fluid-private/stochastic-test-utils": "2.43.0-343119",
|
|
112
|
+
"@fluid-private/test-dds-utils": "2.43.0-343119",
|
|
113
113
|
"@fluid-tools/benchmark": "^0.51.0",
|
|
114
114
|
"@fluid-tools/build-cli": "^0.55.0",
|
|
115
115
|
"@fluidframework/build-common": "^2.0.3",
|
|
116
116
|
"@fluidframework/build-tools": "^0.55.0",
|
|
117
|
-
"@fluidframework/container-definitions": "
|
|
117
|
+
"@fluidframework/container-definitions": "2.43.0-343119",
|
|
118
118
|
"@fluidframework/eslint-config-fluid": "^5.7.4",
|
|
119
|
-
"@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.
|
|
120
|
-
"@fluidframework/test-runtime-utils": "
|
|
119
|
+
"@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.42.0",
|
|
120
|
+
"@fluidframework/test-runtime-utils": "2.43.0-343119",
|
|
121
121
|
"@microsoft/api-extractor": "7.52.8",
|
|
122
122
|
"@types/diff": "^3.5.1",
|
|
123
123
|
"@types/double-ended-queue": "^2.1.0",
|
|
@@ -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,33 +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 pendingChanges: Map<string, ISerializedIntervalCollectionV1> = new Map<
|
|
709
|
-
string,
|
|
710
|
-
ISerializedIntervalCollectionV1
|
|
711
|
-
>();
|
|
757
|
+
|
|
758
|
+
private readonly pending: PendingChanges = {};
|
|
712
759
|
|
|
713
760
|
public get attached(): boolean {
|
|
714
761
|
return !!this.localCollection;
|
|
715
762
|
}
|
|
716
763
|
|
|
764
|
+
private readonly submitDelta: (
|
|
765
|
+
op: IIntervalCollectionTypeOperationValue,
|
|
766
|
+
md: IntervalMessageLocalMetadata,
|
|
767
|
+
) => void;
|
|
768
|
+
|
|
717
769
|
constructor(
|
|
718
|
-
|
|
719
|
-
op: IIntervalCollectionTypeOperationValue,
|
|
720
|
-
md: IMapMessageLocalMetadata,
|
|
721
|
-
) => void,
|
|
770
|
+
submitDelta: (op: IIntervalCollectionTypeOperationValue, md: unknown) => void,
|
|
722
771
|
serializedIntervals: ISerializedIntervalCollectionV1 | ISerializedIntervalCollectionV2,
|
|
723
772
|
private readonly options: Partial<SequenceOptions> = {},
|
|
724
773
|
) {
|
|
725
774
|
super();
|
|
726
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
|
+
|
|
727
788
|
this.savedSerializedIntervals = Array.isArray(serializedIntervals)
|
|
728
789
|
? serializedIntervals
|
|
729
790
|
: serializedIntervals.intervals.map((i) =>
|
|
@@ -765,14 +826,12 @@ export class IntervalCollection
|
|
|
765
826
|
return true;
|
|
766
827
|
}
|
|
767
828
|
|
|
768
|
-
public rollback(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
) {
|
|
772
|
-
const { opName, value } = op;
|
|
829
|
+
public rollback(op: IIntervalCollectionTypeOperationValue, maybeMetadata: unknown) {
|
|
830
|
+
const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
|
|
831
|
+
const { value } = op;
|
|
773
832
|
const { id, properties } = getSerializedProperties(value);
|
|
774
|
-
const {
|
|
775
|
-
switch (
|
|
833
|
+
const { type } = localOpMetadata;
|
|
834
|
+
switch (type) {
|
|
776
835
|
case "add": {
|
|
777
836
|
const interval = this.getIntervalById(id);
|
|
778
837
|
if (interval) {
|
|
@@ -781,9 +840,8 @@ export class IntervalCollection
|
|
|
781
840
|
break;
|
|
782
841
|
}
|
|
783
842
|
case "change": {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
const endpointsChanged = value.start !== undefined && value.end !== undefined;
|
|
843
|
+
const { previous } = localOpMetadata;
|
|
844
|
+
const endpointsChanged = hasEndpointChanges(value);
|
|
787
845
|
const start = endpointsChanged
|
|
788
846
|
? toOptionalSequencePlace(previous.start, previous.startSide)
|
|
789
847
|
: undefined;
|
|
@@ -796,14 +854,10 @@ export class IntervalCollection
|
|
|
796
854
|
props: Object.keys(properties).length > 0 ? properties : undefined,
|
|
797
855
|
rollback: true,
|
|
798
856
|
});
|
|
799
|
-
this.localSeqToSerializedInterval.delete(localSeq);
|
|
800
|
-
if (endpointsChanged) {
|
|
801
|
-
this.removePendingChange(value);
|
|
802
|
-
}
|
|
803
857
|
break;
|
|
804
858
|
}
|
|
805
859
|
case "delete": {
|
|
806
|
-
|
|
860
|
+
const { previous } = localOpMetadata;
|
|
807
861
|
this.add({
|
|
808
862
|
id,
|
|
809
863
|
start: toSequencePlace(previous.start, previous.startSide),
|
|
@@ -814,20 +868,37 @@ export class IntervalCollection
|
|
|
814
868
|
break;
|
|
815
869
|
}
|
|
816
870
|
default:
|
|
817
|
-
unreachableCase(
|
|
871
|
+
unreachableCase(type);
|
|
818
872
|
}
|
|
873
|
+
|
|
874
|
+
clearEmptyPendingEntry(this.pending, id);
|
|
819
875
|
}
|
|
820
876
|
|
|
821
877
|
public process(
|
|
822
878
|
op: IIntervalCollectionTypeOperationValue,
|
|
823
879
|
local: boolean,
|
|
824
880
|
message: ISequencedDocumentMessage,
|
|
825
|
-
|
|
881
|
+
maybeMetadata: unknown,
|
|
826
882
|
) {
|
|
883
|
+
const localOpMetadata = local
|
|
884
|
+
? removeMetadataFromPendingChanges(maybeMetadata)
|
|
885
|
+
: undefined;
|
|
886
|
+
|
|
827
887
|
const { opName, value } = op;
|
|
888
|
+
assert(
|
|
889
|
+
(local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type,
|
|
890
|
+
"must be same type",
|
|
891
|
+
);
|
|
828
892
|
switch (opName) {
|
|
829
893
|
case "add": {
|
|
830
|
-
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
|
+
);
|
|
831
902
|
break;
|
|
832
903
|
}
|
|
833
904
|
|
|
@@ -837,23 +908,36 @@ export class IntervalCollection
|
|
|
837
908
|
}
|
|
838
909
|
|
|
839
910
|
case "change": {
|
|
840
|
-
this.ackChange(value, local, message
|
|
911
|
+
this.ackChange(value, local, message);
|
|
841
912
|
break;
|
|
842
913
|
}
|
|
843
914
|
default:
|
|
844
915
|
unreachableCase(opName);
|
|
845
916
|
}
|
|
917
|
+
|
|
918
|
+
if (local) {
|
|
919
|
+
const { id } = getSerializedProperties(value);
|
|
920
|
+
clearEmptyPendingEntry(this.pending, id);
|
|
921
|
+
}
|
|
846
922
|
}
|
|
847
923
|
|
|
848
924
|
public resubmitMessage(
|
|
849
925
|
op: IIntervalCollectionTypeOperationValue,
|
|
850
|
-
|
|
926
|
+
maybeMetadata: unknown,
|
|
851
927
|
): void {
|
|
852
928
|
const { opName, value } = op;
|
|
929
|
+
|
|
930
|
+
const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
|
|
931
|
+
|
|
853
932
|
const rebasedValue =
|
|
854
|
-
|
|
933
|
+
localOpMetadata.endpointChangesNode === undefined
|
|
934
|
+
? value
|
|
935
|
+
: this.rebaseLocalInterval(localOpMetadata);
|
|
936
|
+
|
|
855
937
|
if (rebasedValue === undefined) {
|
|
856
|
-
|
|
938
|
+
const { id } = getSerializedProperties(value);
|
|
939
|
+
clearEmptyPendingEntry(this.pending, id);
|
|
940
|
+
return;
|
|
857
941
|
}
|
|
858
942
|
|
|
859
943
|
this.submitDelta({ opName, value: rebasedValue as any }, localOpMetadata);
|
|
@@ -904,28 +988,28 @@ export class IntervalCollection
|
|
|
904
988
|
}
|
|
905
989
|
|
|
906
990
|
const { clientId } = this.client.getCollabWindow();
|
|
907
|
-
const { segment, offset } =
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
991
|
+
const { segment, offset } =
|
|
992
|
+
this.client.getContainingSegment(
|
|
993
|
+
pos,
|
|
994
|
+
{
|
|
995
|
+
referenceSequenceNumber: seqNumberFrom,
|
|
996
|
+
clientId: this.client.getLongClientId(clientId),
|
|
997
|
+
},
|
|
998
|
+
localSeq,
|
|
999
|
+
) ?? {};
|
|
915
1000
|
|
|
916
1001
|
// if segment is undefined, it slid off the string
|
|
917
|
-
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
1002
|
+
assert(segment !== undefined && offset !== undefined, 0x54e /* No segment found */);
|
|
918
1003
|
|
|
919
|
-
const segoff =
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
) ?? segment;
|
|
1004
|
+
const segoff = getSlideToSegoff(
|
|
1005
|
+
{ segment, offset },
|
|
1006
|
+
undefined,
|
|
1007
|
+
createLocalReconnectingPerspective(this.client.getCurrentSeq(), clientId, localSeq),
|
|
1008
|
+
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1009
|
+
);
|
|
926
1010
|
|
|
927
1011
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
928
|
-
if (segoff
|
|
1012
|
+
if (segoff?.segment === undefined || segoff.offset === undefined) {
|
|
929
1013
|
return DetachedReferencePosition;
|
|
930
1014
|
}
|
|
931
1015
|
|
|
@@ -937,17 +1021,13 @@ export class IntervalCollection
|
|
|
937
1021
|
}
|
|
938
1022
|
|
|
939
1023
|
private computeRebasedPositions(
|
|
940
|
-
|
|
1024
|
+
localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
|
|
941
1025
|
): ISerializedInterval | SerializedIntervalDelta {
|
|
942
1026
|
assert(
|
|
943
1027
|
this.client !== undefined,
|
|
944
1028
|
0x550 /* Client should be defined when computing rebased position */,
|
|
945
1029
|
);
|
|
946
|
-
const original =
|
|
947
|
-
assert(
|
|
948
|
-
original !== undefined,
|
|
949
|
-
0x551 /* Failed to store pending serialized interval info for this localSeq. */,
|
|
950
|
-
);
|
|
1030
|
+
const { localSeq, original } = localOpMetadata;
|
|
951
1031
|
const rebased = { ...original };
|
|
952
1032
|
const { start, end, sequenceNumber } = original;
|
|
953
1033
|
if (start !== undefined) {
|
|
@@ -972,8 +1052,12 @@ export class IntervalCollection
|
|
|
972
1052
|
this.client = client;
|
|
973
1053
|
if (client) {
|
|
974
1054
|
client.on("normalize", () => {
|
|
975
|
-
for (const
|
|
976
|
-
|
|
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
|
+
}
|
|
977
1061
|
}
|
|
978
1062
|
});
|
|
979
1063
|
}
|
|
@@ -1124,16 +1208,15 @@ export class IntervalCollection
|
|
|
1124
1208
|
const serializedInterval: ISerializedInterval = interval.serialize();
|
|
1125
1209
|
const localSeq = this.getNextLocalSeq();
|
|
1126
1210
|
if (this.isCollaborating && rollback !== true) {
|
|
1127
|
-
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1128
|
-
|
|
1129
1211
|
this.submitDelta(
|
|
1130
1212
|
{
|
|
1131
1213
|
opName: "add",
|
|
1132
1214
|
value: serializedInterval,
|
|
1133
1215
|
},
|
|
1134
1216
|
{
|
|
1217
|
+
type: "add",
|
|
1135
1218
|
localSeq,
|
|
1136
|
-
|
|
1219
|
+
original: serializedInterval,
|
|
1137
1220
|
},
|
|
1138
1221
|
);
|
|
1139
1222
|
}
|
|
@@ -1164,15 +1247,16 @@ export class IntervalCollection
|
|
|
1164
1247
|
if (interval) {
|
|
1165
1248
|
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1166
1249
|
if (local && rollback !== true) {
|
|
1250
|
+
const value = interval.serialize();
|
|
1167
1251
|
this.submitDelta(
|
|
1168
1252
|
{
|
|
1169
1253
|
opName: "delete",
|
|
1170
|
-
value
|
|
1254
|
+
value,
|
|
1171
1255
|
},
|
|
1172
1256
|
{
|
|
1257
|
+
type: "delete",
|
|
1173
1258
|
localSeq: this.getNextLocalSeq(),
|
|
1174
|
-
previous:
|
|
1175
|
-
intervalId: interval.getIntervalId(),
|
|
1259
|
+
previous: value,
|
|
1176
1260
|
},
|
|
1177
1261
|
);
|
|
1178
1262
|
} else {
|
|
@@ -1257,19 +1341,19 @@ export class IntervalCollection
|
|
|
1257
1341
|
).serializeDelta({ props, includeEndpoints: changeEndpoints });
|
|
1258
1342
|
const localSeq = this.getNextLocalSeq();
|
|
1259
1343
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1344
|
+
const metadata: IntervalChangeLocalMetadata = {
|
|
1345
|
+
type: "change",
|
|
1346
|
+
localSeq,
|
|
1347
|
+
previous: interval.serialize(),
|
|
1348
|
+
original: serializedInterval,
|
|
1349
|
+
};
|
|
1262
1350
|
|
|
1263
1351
|
this.submitDelta(
|
|
1264
1352
|
{
|
|
1265
1353
|
opName: "change",
|
|
1266
1354
|
value: serializedInterval,
|
|
1267
1355
|
},
|
|
1268
|
-
|
|
1269
|
-
localSeq,
|
|
1270
|
-
previous: interval.serialize(),
|
|
1271
|
-
intervalId: id,
|
|
1272
|
-
},
|
|
1356
|
+
metadata,
|
|
1273
1357
|
);
|
|
1274
1358
|
}
|
|
1275
1359
|
if (deltaProps !== undefined) {
|
|
@@ -1298,51 +1382,14 @@ export class IntervalCollection
|
|
|
1298
1382
|
return this.client?.getCollabWindow().collaborating ?? false;
|
|
1299
1383
|
}
|
|
1300
1384
|
|
|
1301
|
-
private
|
|
1302
|
-
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
assert(
|
|
1306
|
-
(serializedInterval.start === undefined) === (serializedInterval.end === undefined),
|
|
1307
|
-
0xbb0 /* both start and end must be set or unset */,
|
|
1308
|
-
);
|
|
1309
|
-
if (serializedInterval.start !== undefined || serializedInterval.end !== undefined) {
|
|
1310
|
-
const entries = this.pendingChanges.get(id) ?? [];
|
|
1311
|
-
this.pendingChanges.set(id, entries);
|
|
1312
|
-
entries.push(serializedInterval as any);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
private removePendingChange(serializedInterval: SerializedIntervalDelta) {
|
|
1317
|
-
// Change ops always have an ID.
|
|
1318
|
-
const { id } = getSerializedProperties(serializedInterval);
|
|
1319
|
-
if (serializedInterval.start !== undefined) {
|
|
1320
|
-
const entries = this.pendingChanges.get(id);
|
|
1321
|
-
if (entries) {
|
|
1322
|
-
const pendingChange = entries.shift();
|
|
1323
|
-
if (entries.length === 0) {
|
|
1324
|
-
this.pendingChanges.delete(id);
|
|
1325
|
-
}
|
|
1326
|
-
if (
|
|
1327
|
-
pendingChange?.start !== serializedInterval.start ||
|
|
1328
|
-
pendingChange?.end !== serializedInterval.end
|
|
1329
|
-
) {
|
|
1330
|
-
throw new LoggingError("Mismatch in pending changes");
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
private hasPendingChanges(id: string) {
|
|
1337
|
-
const entries = this.pendingChanges.get(id);
|
|
1338
|
-
return entries && entries.length !== 0;
|
|
1385
|
+
private hasPendingEndpointChanges(id: string) {
|
|
1386
|
+
return this.pending[id]?.endpointChanges?.empty === false;
|
|
1339
1387
|
}
|
|
1340
1388
|
|
|
1341
1389
|
public ackChange(
|
|
1342
1390
|
serializedInterval: SerializedIntervalDelta,
|
|
1343
1391
|
local: boolean,
|
|
1344
1392
|
op: ISequencedDocumentMessage,
|
|
1345
|
-
localOpMetadata: IMapMessageLocalMetadata | undefined,
|
|
1346
1393
|
) {
|
|
1347
1394
|
if (!this.localCollection) {
|
|
1348
1395
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
@@ -1355,16 +1402,6 @@ export class IntervalCollection
|
|
|
1355
1402
|
assert(id !== undefined, 0x3fe /* id must exist on the interval */);
|
|
1356
1403
|
const interval: SequenceIntervalClass | undefined = this.getIntervalById(id);
|
|
1357
1404
|
|
|
1358
|
-
if (local) {
|
|
1359
|
-
assert(
|
|
1360
|
-
localOpMetadata !== undefined,
|
|
1361
|
-
0x552 /* op metadata should be defined for local op */,
|
|
1362
|
-
);
|
|
1363
|
-
// This is an ack from the server. Remove the pending change.
|
|
1364
|
-
this.localSeqToSerializedInterval.delete(localOpMetadata?.localSeq);
|
|
1365
|
-
this.removePendingChange(serializedInterval);
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
1405
|
if (!interval) {
|
|
1369
1406
|
// The interval has been removed locally; no-op.
|
|
1370
1407
|
return;
|
|
@@ -1380,7 +1417,7 @@ export class IntervalCollection
|
|
|
1380
1417
|
let start: number | "start" | "end" | undefined;
|
|
1381
1418
|
let end: number | "start" | "end" | undefined;
|
|
1382
1419
|
// Track pending start/end independently of one another.
|
|
1383
|
-
if (!this.
|
|
1420
|
+
if (!this.hasPendingEndpointChanges(id)) {
|
|
1384
1421
|
start = serializedInterval.start;
|
|
1385
1422
|
end = serializedInterval.end;
|
|
1386
1423
|
}
|
|
@@ -1440,23 +1477,22 @@ export class IntervalCollection
|
|
|
1440
1477
|
*
|
|
1441
1478
|
*/
|
|
1442
1479
|
public rebaseLocalInterval(
|
|
1443
|
-
|
|
1444
|
-
serializedInterval: SerializedIntervalDelta,
|
|
1445
|
-
localOpMetadata: IMapMessageLocalMetadata,
|
|
1480
|
+
localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
|
|
1446
1481
|
): SerializedIntervalDelta | undefined {
|
|
1482
|
+
const original = localOpMetadata.original;
|
|
1447
1483
|
if (!this.client) {
|
|
1448
1484
|
// If there's no associated mergeTree client, the originally submitted op is still correct.
|
|
1449
|
-
return
|
|
1485
|
+
return original;
|
|
1450
1486
|
}
|
|
1451
1487
|
if (!this.attached) {
|
|
1452
1488
|
throw new LoggingError("attachSequence must be called");
|
|
1453
1489
|
}
|
|
1454
1490
|
|
|
1455
1491
|
const { localSeq } = localOpMetadata;
|
|
1456
|
-
const { intervalType, properties, stickiness, startSide, endSide } =
|
|
1457
|
-
const { id } = getSerializedProperties(
|
|
1458
|
-
const { start: startRebased, end: endRebased } =
|
|
1459
|
-
this.
|
|
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));
|
|
1460
1496
|
|
|
1461
1497
|
const localInterval = this.localCollection?.idIntervalIndex.getIntervalById(id);
|
|
1462
1498
|
|
|
@@ -1471,15 +1507,6 @@ export class IntervalCollection
|
|
|
1471
1507
|
endSide,
|
|
1472
1508
|
};
|
|
1473
1509
|
|
|
1474
|
-
if (
|
|
1475
|
-
opName === "change" &&
|
|
1476
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when .hasPendingChangeStart returns false.
|
|
1477
|
-
this.hasPendingChanges(id)
|
|
1478
|
-
) {
|
|
1479
|
-
this.removePendingChange(serializedInterval);
|
|
1480
|
-
this.addPendingChange(id, rebased);
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
1510
|
// if the interval slid off the string, rebase the op to be a noop and delete the interval.
|
|
1484
1511
|
if (
|
|
1485
1512
|
!this.options.mergeTreeReferencesCanSlideToEndpoint &&
|
|
@@ -1509,28 +1536,27 @@ export class IntervalCollection
|
|
|
1509
1536
|
private getSlideToSegment(
|
|
1510
1537
|
lref: LocalReferencePosition,
|
|
1511
1538
|
slidingPreference: SlidingPreference,
|
|
1512
|
-
): { segment: ISegment
|
|
1539
|
+
): { segment: ISegment; offset: number } | undefined {
|
|
1513
1540
|
if (!this.client) {
|
|
1514
1541
|
throw new LoggingError("client does not exist");
|
|
1515
1542
|
}
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1543
|
+
const segment: ISegmentInternal | undefined = lref.getSegment();
|
|
1544
|
+
if (segment === undefined) {
|
|
1545
|
+
return undefined;
|
|
1546
|
+
}
|
|
1547
|
+
const segoff = {
|
|
1548
|
+
segment,
|
|
1518
1549
|
offset: lref.getOffset(),
|
|
1519
1550
|
};
|
|
1520
|
-
if (segoff.segment
|
|
1551
|
+
if (segoff.segment.localRefs?.has(lref) !== true) {
|
|
1521
1552
|
return undefined;
|
|
1522
1553
|
}
|
|
1523
|
-
|
|
1554
|
+
return getSlideToSegoff(
|
|
1524
1555
|
segoff,
|
|
1525
1556
|
slidingPreference,
|
|
1526
1557
|
undefined,
|
|
1527
1558
|
this.options.mergeTreeReferencesCanSlideToEndpoint,
|
|
1528
1559
|
);
|
|
1529
|
-
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
1530
|
-
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
1531
|
-
? undefined
|
|
1532
|
-
: newSegoff;
|
|
1533
|
-
return value;
|
|
1534
1560
|
}
|
|
1535
1561
|
|
|
1536
1562
|
private ackInterval(interval: SequenceIntervalClass, op: ISequencedDocumentMessage): void {
|
|
@@ -1551,15 +1577,16 @@ export class IntervalCollection
|
|
|
1551
1577
|
);
|
|
1552
1578
|
|
|
1553
1579
|
const id = interval.getIntervalId();
|
|
1554
|
-
const hasPendingChange = this.
|
|
1580
|
+
const hasPendingChange = this.hasPendingEndpointChanges(id);
|
|
1555
1581
|
|
|
1556
1582
|
if (!hasPendingChange) {
|
|
1557
1583
|
setSlideOnRemove(interval.start);
|
|
1558
1584
|
setSlideOnRemove(interval.end);
|
|
1559
1585
|
}
|
|
1560
1586
|
|
|
1561
|
-
const needsStartUpdate =
|
|
1562
|
-
|
|
1587
|
+
const needsStartUpdate =
|
|
1588
|
+
newStart?.segment !== interval.start.getSegment() && !hasPendingChange;
|
|
1589
|
+
const needsEndUpdate = newEnd?.segment !== interval.end.getSegment() && !hasPendingChange;
|
|
1563
1590
|
|
|
1564
1591
|
if (needsStartUpdate || needsEndUpdate) {
|
|
1565
1592
|
if (!this.localCollection) {
|
|
@@ -1628,7 +1655,7 @@ export class IntervalCollection
|
|
|
1628
1655
|
serializedInterval: ISerializedInterval,
|
|
1629
1656
|
local: boolean,
|
|
1630
1657
|
op: ISequencedDocumentMessage,
|
|
1631
|
-
localOpMetadata:
|
|
1658
|
+
localOpMetadata: IntervalAddLocalMetadata | undefined,
|
|
1632
1659
|
) {
|
|
1633
1660
|
const { id, properties } = getSerializedProperties(serializedInterval);
|
|
1634
1661
|
|
|
@@ -1637,7 +1664,6 @@ export class IntervalCollection
|
|
|
1637
1664
|
localOpMetadata !== undefined,
|
|
1638
1665
|
0x553 /* op metadata should be defined for local op */,
|
|
1639
1666
|
);
|
|
1640
|
-
this.localSeqToSerializedInterval.delete(localOpMetadata.localSeq);
|
|
1641
1667
|
const localInterval = this.getIntervalById(id);
|
|
1642
1668
|
if (localInterval) {
|
|
1643
1669
|
this.ackInterval(localInterval, op);
|