@fluidframework/sequence 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.5.3.2.178189
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 +28 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/intervalCollection.d.ts +9 -19
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +26 -105
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalIndex/index.d.ts +8 -0
- package/dist/intervalIndex/index.d.ts.map +1 -0
- package/dist/intervalIndex/index.js +12 -0
- package/dist/intervalIndex/index.js.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.js +103 -0
- package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +33 -0
- package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.js +7 -0
- package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
- package/dist/packageVersion.d.ts +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 +47 -7
- package/dist/revertibles.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/intervalCollection.d.ts +9 -19
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +26 -106
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalIndex/index.d.ts +8 -0
- package/lib/intervalIndex/index.d.ts.map +1 -0
- package/lib/intervalIndex/index.js +7 -0
- package/lib/intervalIndex/index.js.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.js +98 -0
- package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +29 -0
- package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.js +6 -0
- package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
- package/lib/packageVersion.d.ts +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 +48 -8
- package/lib/revertibles.js.map +1 -1
- package/package.json +18 -18
- package/src/index.ts +6 -0
- package/src/intervalCollection.ts +41 -143
- package/src/intervalIndex/index.ts +11 -0
- package/src/intervalIndex/overlappingIntervalsIndex.ts +166 -0
- package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +71 -0
- package/src/intervalIndex/sequenceIntervalIndexes.ts +32 -0
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +61 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/sequence",
|
|
3
|
-
"version": "2.0.0-dev.5.2.
|
|
3
|
+
"version": "2.0.0-dev.5.3.2.178189",
|
|
4
4
|
"description": "Distributed sequence",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -37,30 +37,30 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@fluidframework/common-definitions": "^0.20.1",
|
|
39
39
|
"@fluidframework/common-utils": "^1.1.1",
|
|
40
|
-
"@fluidframework/container-utils": "2.0.0-dev.5.2.
|
|
41
|
-
"@fluidframework/core-interfaces": "2.0.0-dev.5.2.
|
|
42
|
-
"@fluidframework/datastore-definitions": "2.0.0-dev.5.2.
|
|
43
|
-
"@fluidframework/merge-tree": "2.0.0-dev.5.2.
|
|
40
|
+
"@fluidframework/container-utils": "2.0.0-dev.5.3.2.178189",
|
|
41
|
+
"@fluidframework/core-interfaces": "2.0.0-dev.5.3.2.178189",
|
|
42
|
+
"@fluidframework/datastore-definitions": "2.0.0-dev.5.3.2.178189",
|
|
43
|
+
"@fluidframework/merge-tree": "2.0.0-dev.5.3.2.178189",
|
|
44
44
|
"@fluidframework/protocol-definitions": "^1.1.0",
|
|
45
|
-
"@fluidframework/runtime-definitions": "2.0.0-dev.5.2.
|
|
46
|
-
"@fluidframework/runtime-utils": "2.0.0-dev.5.2.
|
|
47
|
-
"@fluidframework/shared-object-base": "2.0.0-dev.5.2.
|
|
48
|
-
"@fluidframework/telemetry-utils": "2.0.0-dev.5.2.
|
|
45
|
+
"@fluidframework/runtime-definitions": "2.0.0-dev.5.3.2.178189",
|
|
46
|
+
"@fluidframework/runtime-utils": "2.0.0-dev.5.3.2.178189",
|
|
47
|
+
"@fluidframework/shared-object-base": "2.0.0-dev.5.3.2.178189",
|
|
48
|
+
"@fluidframework/telemetry-utils": "2.0.0-dev.5.3.2.178189",
|
|
49
49
|
"uuid": "^8.3.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@fluid-internal/stochastic-test-utils": "2.0.0-dev.5.2.
|
|
53
|
-
"@fluid-internal/test-dds-utils": "2.0.0-dev.5.2.
|
|
54
|
-
"@fluid-tools/benchmark": "^0.
|
|
55
|
-
"@fluid-tools/build-cli": "^0.
|
|
52
|
+
"@fluid-internal/stochastic-test-utils": "2.0.0-dev.5.3.2.178189",
|
|
53
|
+
"@fluid-internal/test-dds-utils": "2.0.0-dev.5.3.2.178189",
|
|
54
|
+
"@fluid-tools/benchmark": "^0.48.0",
|
|
55
|
+
"@fluid-tools/build-cli": "^0.21.0",
|
|
56
56
|
"@fluidframework/build-common": "^1.2.0",
|
|
57
|
-
"@fluidframework/build-tools": "^0.
|
|
57
|
+
"@fluidframework/build-tools": "^0.21.0",
|
|
58
58
|
"@fluidframework/eslint-config-fluid": "^2.0.0",
|
|
59
59
|
"@fluidframework/gitresources": "^0.1039.1000",
|
|
60
|
-
"@fluidframework/mocha-test-setup": "2.0.0-dev.5.2.
|
|
61
|
-
"@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.0.0-internal.5.
|
|
60
|
+
"@fluidframework/mocha-test-setup": "2.0.0-dev.5.3.2.178189",
|
|
61
|
+
"@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.0.0-internal.5.2.0",
|
|
62
62
|
"@fluidframework/server-services-client": "^0.1039.1000",
|
|
63
|
-
"@fluidframework/test-runtime-utils": "2.0.0-dev.5.2.
|
|
63
|
+
"@fluidframework/test-runtime-utils": "2.0.0-dev.5.3.2.178189",
|
|
64
64
|
"@microsoft/api-extractor": "^7.34.4",
|
|
65
65
|
"@types/diff": "^3.5.1",
|
|
66
66
|
"@types/mocha": "^9.1.1",
|
|
@@ -124,6 +124,6 @@
|
|
|
124
124
|
"testfarm": "node dist/test/testFarm.js",
|
|
125
125
|
"tsc": "tsc",
|
|
126
126
|
"typetests:gen": "fluid-type-test-generator",
|
|
127
|
-
"typetests:prepare": "flub
|
|
127
|
+
"typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
|
|
128
128
|
}
|
|
129
129
|
}
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,12 @@ export {
|
|
|
39
39
|
createStartpointInRangeIndex,
|
|
40
40
|
} from "./intervalCollection";
|
|
41
41
|
export { IInterval, IntervalConflictResolver } from "./intervalTree";
|
|
42
|
+
export {
|
|
43
|
+
SequenceIntervalIndexes,
|
|
44
|
+
IOverlappingIntervalsIndex,
|
|
45
|
+
createOverlappingIntervalsIndex,
|
|
46
|
+
createOverlappingSequenceIntervalsIndex,
|
|
47
|
+
} from "./intervalIndex";
|
|
42
48
|
export {
|
|
43
49
|
appendAddIntervalToRevertibles,
|
|
44
50
|
appendChangeIntervalToRevertibles,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Client,
|
|
14
14
|
compareReferencePositions,
|
|
15
15
|
createMap,
|
|
16
|
+
getSlideToSegoff,
|
|
16
17
|
ICombiningOp,
|
|
17
18
|
ISegment,
|
|
18
19
|
MergeTreeDeltaType,
|
|
@@ -43,7 +44,8 @@ import {
|
|
|
43
44
|
IValueTypeOperationValue,
|
|
44
45
|
SequenceOptions,
|
|
45
46
|
} from "./defaultMapInterfaces";
|
|
46
|
-
import { IInterval, IntervalConflictResolver
|
|
47
|
+
import { IInterval, IntervalConflictResolver } from "./intervalTree";
|
|
48
|
+
import { IOverlappingIntervalsIndex, createOverlappingIntervalsIndex } from "./intervalIndex";
|
|
47
49
|
|
|
48
50
|
const reservedIntervalIdKey = "intervalId";
|
|
49
51
|
|
|
@@ -772,7 +774,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
772
774
|
}
|
|
773
775
|
}
|
|
774
776
|
|
|
775
|
-
function createPositionReferenceFromSegoff(
|
|
777
|
+
export function createPositionReferenceFromSegoff(
|
|
776
778
|
client: Client,
|
|
777
779
|
segoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
778
780
|
refType: ReferenceType,
|
|
@@ -828,7 +830,7 @@ function createPositionReference(
|
|
|
828
830
|
referenceSequenceNumber: op.referenceSequenceNumber,
|
|
829
831
|
clientId: op.clientId,
|
|
830
832
|
});
|
|
831
|
-
segoff =
|
|
833
|
+
segoff = getSlideToSegoff(segoff);
|
|
832
834
|
} else {
|
|
833
835
|
assert(
|
|
834
836
|
(refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
|
|
@@ -889,6 +891,7 @@ export function createSequenceInterval(
|
|
|
889
891
|
undefined,
|
|
890
892
|
startReferenceSlidingPreference(stickiness),
|
|
891
893
|
);
|
|
894
|
+
|
|
892
895
|
const endLref = createPositionReference(
|
|
893
896
|
client,
|
|
894
897
|
end,
|
|
@@ -898,6 +901,7 @@ export function createSequenceInterval(
|
|
|
898
901
|
undefined,
|
|
899
902
|
endReferenceSlidingPreference(stickiness),
|
|
900
903
|
);
|
|
904
|
+
|
|
901
905
|
const rangeProp = {
|
|
902
906
|
[reservedRangeLabelsKey]: [label],
|
|
903
907
|
};
|
|
@@ -949,135 +953,6 @@ export interface IntervalIndex<TInterval extends ISerializableInterval> {
|
|
|
949
953
|
remove(interval: TInterval): void;
|
|
950
954
|
}
|
|
951
955
|
|
|
952
|
-
class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
|
|
953
|
-
implements IntervalIndex<TInterval>
|
|
954
|
-
{
|
|
955
|
-
private readonly intervalTree = new IntervalTree<TInterval>();
|
|
956
|
-
|
|
957
|
-
constructor(
|
|
958
|
-
private readonly client: Client,
|
|
959
|
-
private readonly helpers: IIntervalHelpers<TInterval>,
|
|
960
|
-
) {}
|
|
961
|
-
|
|
962
|
-
public map(fn: (interval: TInterval) => void) {
|
|
963
|
-
this.intervalTree.map(fn);
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
public mapUntil(fn: (interval: TInterval) => boolean) {
|
|
967
|
-
this.intervalTree.mapUntil(fn);
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
public gatherIterationResults(
|
|
971
|
-
results: TInterval[],
|
|
972
|
-
iteratesForward: boolean,
|
|
973
|
-
start?: number,
|
|
974
|
-
end?: number,
|
|
975
|
-
) {
|
|
976
|
-
if (this.intervalTree.intervals.isEmpty()) {
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (start === undefined && end === undefined) {
|
|
981
|
-
// No start/end provided. Gather the whole tree in the specified order.
|
|
982
|
-
if (iteratesForward) {
|
|
983
|
-
this.intervalTree.map((interval: TInterval) => {
|
|
984
|
-
results.push(interval);
|
|
985
|
-
});
|
|
986
|
-
} else {
|
|
987
|
-
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
988
|
-
results.push(interval);
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
const transientInterval: TInterval = this.helpers.create(
|
|
993
|
-
"transient",
|
|
994
|
-
start,
|
|
995
|
-
end,
|
|
996
|
-
this.client,
|
|
997
|
-
IntervalType.Transient,
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
if (start === undefined) {
|
|
1001
|
-
// Only end position provided. Since the tree is not sorted by end position,
|
|
1002
|
-
// walk the whole tree in the specified order, gathering intervals that match the end.
|
|
1003
|
-
if (iteratesForward) {
|
|
1004
|
-
this.intervalTree.map((interval: TInterval) => {
|
|
1005
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
1006
|
-
results.push(interval);
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
} else {
|
|
1010
|
-
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
1011
|
-
if (transientInterval.compareEnd(interval) === 0) {
|
|
1012
|
-
results.push(interval);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
} else {
|
|
1017
|
-
// Start and (possibly) end provided. Walk the subtrees that may contain
|
|
1018
|
-
// this start position.
|
|
1019
|
-
const compareFn =
|
|
1020
|
-
end === undefined
|
|
1021
|
-
? (node: IntervalNode<TInterval>) => {
|
|
1022
|
-
return transientInterval.compareStart(node.key);
|
|
1023
|
-
}
|
|
1024
|
-
: (node: IntervalNode<TInterval>) => {
|
|
1025
|
-
return transientInterval.compare(node.key);
|
|
1026
|
-
};
|
|
1027
|
-
const continueLeftFn = (cmpResult: number) => cmpResult <= 0;
|
|
1028
|
-
const continueRightFn = (cmpResult: number) => cmpResult >= 0;
|
|
1029
|
-
const actionFn = (node: IntervalNode<TInterval>) => {
|
|
1030
|
-
results.push(node.key);
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
if (iteratesForward) {
|
|
1034
|
-
this.intervalTree.intervals.walkExactMatchesForward(
|
|
1035
|
-
compareFn,
|
|
1036
|
-
actionFn,
|
|
1037
|
-
continueLeftFn,
|
|
1038
|
-
continueRightFn,
|
|
1039
|
-
);
|
|
1040
|
-
} else {
|
|
1041
|
-
this.intervalTree.intervals.walkExactMatchesBackward(
|
|
1042
|
-
compareFn,
|
|
1043
|
-
actionFn,
|
|
1044
|
-
continueLeftFn,
|
|
1045
|
-
continueRightFn,
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
/**
|
|
1053
|
-
* @returns an array of all intervals contained in this collection that overlap the range
|
|
1054
|
-
* `[startPosition, endPosition)`.
|
|
1055
|
-
*/
|
|
1056
|
-
public findOverlappingIntervals(startPosition: number, endPosition: number) {
|
|
1057
|
-
if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
|
|
1058
|
-
return [];
|
|
1059
|
-
}
|
|
1060
|
-
const transientInterval = this.helpers.create(
|
|
1061
|
-
"transient",
|
|
1062
|
-
startPosition,
|
|
1063
|
-
endPosition,
|
|
1064
|
-
this.client,
|
|
1065
|
-
IntervalType.Transient,
|
|
1066
|
-
);
|
|
1067
|
-
|
|
1068
|
-
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
1069
|
-
return overlappingIntervalNodes.map((node) => node.key);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
public remove(interval: TInterval) {
|
|
1073
|
-
this.intervalTree.removeExisting(interval);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
public add(interval: TInterval) {
|
|
1077
|
-
this.intervalTree.put(interval);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
956
|
class IdIntervalIndex<TInterval extends ISerializableInterval>
|
|
1082
957
|
implements IntervalIndex<TInterval>, Iterable<TInterval>
|
|
1083
958
|
{
|
|
@@ -1380,7 +1255,7 @@ export function createStartpointInRangeIndex<TInterval extends ISerializableInte
|
|
|
1380
1255
|
|
|
1381
1256
|
export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
1382
1257
|
private static readonly legacyIdPrefix = "legacy";
|
|
1383
|
-
public readonly overlappingIntervalsIndex:
|
|
1258
|
+
public readonly overlappingIntervalsIndex: IOverlappingIntervalsIndex<TInterval>;
|
|
1384
1259
|
public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
|
|
1385
1260
|
public readonly endIntervalIndex: EndpointIndex<TInterval>;
|
|
1386
1261
|
private readonly indexes: Set<IntervalIndex<TInterval>>;
|
|
@@ -1395,7 +1270,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1395
1270
|
previousInterval: TInterval,
|
|
1396
1271
|
) => void,
|
|
1397
1272
|
) {
|
|
1398
|
-
this.overlappingIntervalsIndex =
|
|
1273
|
+
this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
|
|
1399
1274
|
this.idIntervalIndex = new IdIntervalIndex();
|
|
1400
1275
|
this.endIntervalIndex = new EndpointIndex(client, helpers);
|
|
1401
1276
|
this.indexes = new Set([
|
|
@@ -1493,6 +1368,17 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1493
1368
|
}
|
|
1494
1369
|
|
|
1495
1370
|
if (props) {
|
|
1371
|
+
// This check is intended to prevent scenarios where a random interval is created and then
|
|
1372
|
+
// inserted into a collection. The aim is to ensure that the collection is created first
|
|
1373
|
+
// then the user can create/add intervals based on the collection
|
|
1374
|
+
if (
|
|
1375
|
+
props[reservedRangeLabelsKey] !== undefined &&
|
|
1376
|
+
props[reservedRangeLabelsKey][0] !== this.label
|
|
1377
|
+
) {
|
|
1378
|
+
throw new LoggingError(
|
|
1379
|
+
"Adding an interval that belongs to another interval collection is not permitted",
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1496
1382
|
interval.addProperties(props);
|
|
1497
1383
|
}
|
|
1498
1384
|
interval.properties[reservedIntervalIdKey] ??= uuid();
|
|
@@ -1839,6 +1725,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1839
1725
|
* endpoints. These references should be used for position information only.
|
|
1840
1726
|
* `local` reflects whether the change originated locally.
|
|
1841
1727
|
* `op` is defined if and only if the server has acked this change.
|
|
1728
|
+
* `slide` is true if the change is due to sliding on removal of position
|
|
1842
1729
|
*/
|
|
1843
1730
|
(
|
|
1844
1731
|
event: "changeInterval",
|
|
@@ -1847,6 +1734,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
|
|
|
1847
1734
|
previousInterval: TInterval,
|
|
1848
1735
|
local: boolean,
|
|
1849
1736
|
op: ISequencedDocumentMessage | undefined,
|
|
1737
|
+
slide: boolean,
|
|
1850
1738
|
) => void,
|
|
1851
1739
|
);
|
|
1852
1740
|
/**
|
|
@@ -2112,7 +2000,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2112
2000
|
// if segment is undefined, it slid off the string
|
|
2113
2001
|
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
2114
2002
|
|
|
2115
|
-
const segoff =
|
|
2003
|
+
const segoff = getSlideToSegoff({ segment, offset }) ?? segment;
|
|
2116
2004
|
|
|
2117
2005
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
2118
2006
|
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
@@ -2176,7 +2064,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2176
2064
|
client,
|
|
2177
2065
|
label,
|
|
2178
2066
|
this.helpers,
|
|
2179
|
-
(interval, previousInterval) => this.emitChange(interval, previousInterval, true),
|
|
2067
|
+
(interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
|
|
2180
2068
|
);
|
|
2181
2069
|
if (this.savedSerializedIntervals) {
|
|
2182
2070
|
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
@@ -2216,6 +2104,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2216
2104
|
interval: TInterval,
|
|
2217
2105
|
previousInterval: TInterval,
|
|
2218
2106
|
local: boolean,
|
|
2107
|
+
slide: boolean,
|
|
2219
2108
|
op?: ISequencedDocumentMessage,
|
|
2220
2109
|
): void {
|
|
2221
2110
|
// Temporarily make references transient so that positional queries work (non-transient refs
|
|
@@ -2228,11 +2117,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2228
2117
|
endRefType = previousInterval.end.refType;
|
|
2229
2118
|
previousInterval.start.refType = ReferenceType.Transient;
|
|
2230
2119
|
previousInterval.end.refType = ReferenceType.Transient;
|
|
2231
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
2120
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
2232
2121
|
previousInterval.start.refType = startRefType;
|
|
2233
2122
|
previousInterval.end.refType = endRefType;
|
|
2234
2123
|
} else {
|
|
2235
|
-
this.emit("changeInterval", interval, previousInterval, local, op);
|
|
2124
|
+
this.emit("changeInterval", interval, previousInterval, local, op, slide);
|
|
2236
2125
|
}
|
|
2237
2126
|
}
|
|
2238
2127
|
|
|
@@ -2351,6 +2240,13 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2351
2240
|
if (!props) {
|
|
2352
2241
|
throw new LoggingError("changeProperties should be called with a property set");
|
|
2353
2242
|
}
|
|
2243
|
+
// prevent the overwriting of an interval label, it should remain unchanged
|
|
2244
|
+
// once it has been inserted into the collection.
|
|
2245
|
+
if (props[reservedRangeLabelsKey] !== undefined) {
|
|
2246
|
+
throw new LoggingError(
|
|
2247
|
+
"The label property should not be modified once inserted to the collection",
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2354
2250
|
|
|
2355
2251
|
const interval = this.getIntervalById(id);
|
|
2356
2252
|
if (interval) {
|
|
@@ -2402,7 +2298,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2402
2298
|
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
2403
2299
|
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
2404
2300
|
this.addPendingChange(id, serializedInterval);
|
|
2405
|
-
this.emitChange(newInterval, interval, true);
|
|
2301
|
+
this.emitChange(newInterval, interval, true, false);
|
|
2406
2302
|
return newInterval;
|
|
2407
2303
|
}
|
|
2408
2304
|
// No interval to change
|
|
@@ -2538,7 +2434,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2538
2434
|
}
|
|
2539
2435
|
|
|
2540
2436
|
if (newInterval !== interval) {
|
|
2541
|
-
this.emitChange(newInterval, interval, local, op);
|
|
2437
|
+
this.emitChange(newInterval, interval, local, false, op);
|
|
2542
2438
|
}
|
|
2543
2439
|
|
|
2544
2440
|
const changedProperties = Object.keys(newProps).length > 0;
|
|
@@ -2654,7 +2550,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2654
2550
|
return rebased;
|
|
2655
2551
|
}
|
|
2656
2552
|
|
|
2657
|
-
private getSlideToSegment(
|
|
2553
|
+
private getSlideToSegment(
|
|
2554
|
+
lref: LocalReferencePosition,
|
|
2555
|
+
): { segment: ISegment | undefined; offset: number | undefined } | undefined {
|
|
2658
2556
|
if (!this.client) {
|
|
2659
2557
|
throw new LoggingError("client does not exist");
|
|
2660
2558
|
}
|
|
@@ -2662,7 +2560,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2662
2560
|
if (segoff.segment?.localRefs?.has(lref) !== true) {
|
|
2663
2561
|
return undefined;
|
|
2664
2562
|
}
|
|
2665
|
-
const newSegoff =
|
|
2563
|
+
const newSegoff = getSlideToSegoff(segoff);
|
|
2666
2564
|
const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
|
|
2667
2565
|
segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
|
|
2668
2566
|
? undefined
|
|
@@ -2761,7 +2659,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
2761
2659
|
oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
|
|
2762
2660
|
}
|
|
2763
2661
|
this.localCollection.add(interval);
|
|
2764
|
-
this.emitChange(interval, oldInterval as TInterval, true, op);
|
|
2662
|
+
this.emitChange(interval, oldInterval as TInterval, true, true, op);
|
|
2765
2663
|
}
|
|
2766
2664
|
}
|
|
2767
2665
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { SequenceIntervalIndexes } from "./sequenceIntervalIndexes";
|
|
7
|
+
export {
|
|
8
|
+
IOverlappingIntervalsIndex,
|
|
9
|
+
createOverlappingIntervalsIndex,
|
|
10
|
+
} from "./overlappingIntervalsIndex";
|
|
11
|
+
export { createOverlappingSequenceIntervalsIndex } from "./overlappingSequenceIntervalsIndex";
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Client } from "@fluidframework/merge-tree";
|
|
7
|
+
import {
|
|
8
|
+
IntervalType,
|
|
9
|
+
IIntervalHelpers,
|
|
10
|
+
IntervalIndex,
|
|
11
|
+
ISerializableInterval,
|
|
12
|
+
} from "../intervalCollection";
|
|
13
|
+
import { IntervalNode, IntervalTree } from "../intervalTree";
|
|
14
|
+
|
|
15
|
+
export interface IOverlappingIntervalsIndex<TInterval extends ISerializableInterval>
|
|
16
|
+
extends IntervalIndex<TInterval> {
|
|
17
|
+
/**
|
|
18
|
+
* @returns an array of all intervals contained in this collection that overlap the range
|
|
19
|
+
* `[start end]`.
|
|
20
|
+
*/
|
|
21
|
+
findOverlappingIntervals(start: number, end: number): TInterval[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gathers the interval results based on specified parameters.
|
|
25
|
+
*/
|
|
26
|
+
gatherIterationResults(
|
|
27
|
+
results: TInterval[],
|
|
28
|
+
iteratesForward: boolean,
|
|
29
|
+
start?: number,
|
|
30
|
+
end?: number,
|
|
31
|
+
): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
|
|
35
|
+
implements IOverlappingIntervalsIndex<TInterval>
|
|
36
|
+
{
|
|
37
|
+
protected readonly intervalTree = new IntervalTree<TInterval>();
|
|
38
|
+
protected readonly client: Client;
|
|
39
|
+
protected readonly helpers: IIntervalHelpers<TInterval>;
|
|
40
|
+
|
|
41
|
+
constructor(client: Client, helpers: IIntervalHelpers<TInterval>) {
|
|
42
|
+
this.client = client;
|
|
43
|
+
this.helpers = helpers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public map(fn: (interval: TInterval) => void) {
|
|
47
|
+
this.intervalTree.map(fn);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public mapUntil(fn: (interval: TInterval) => boolean) {
|
|
51
|
+
this.intervalTree.mapUntil(fn);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public gatherIterationResults(
|
|
55
|
+
results: TInterval[],
|
|
56
|
+
iteratesForward: boolean,
|
|
57
|
+
start?: number,
|
|
58
|
+
end?: number,
|
|
59
|
+
): void {
|
|
60
|
+
if (this.intervalTree.intervals.isEmpty()) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (start === undefined && end === undefined) {
|
|
65
|
+
// No start/end provided. Gather the whole tree in the specified order.
|
|
66
|
+
if (iteratesForward) {
|
|
67
|
+
this.intervalTree.map((interval: TInterval) => {
|
|
68
|
+
results.push(interval);
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
72
|
+
results.push(interval);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
const transientInterval: TInterval = this.helpers.create(
|
|
77
|
+
"transient",
|
|
78
|
+
start,
|
|
79
|
+
end,
|
|
80
|
+
this.client,
|
|
81
|
+
IntervalType.Transient,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (start === undefined) {
|
|
85
|
+
// Only end position provided. Since the tree is not sorted by end position,
|
|
86
|
+
// walk the whole tree in the specified order, gathering intervals that match the end.
|
|
87
|
+
if (iteratesForward) {
|
|
88
|
+
this.intervalTree.map((interval: TInterval) => {
|
|
89
|
+
if (transientInterval.compareEnd(interval) === 0) {
|
|
90
|
+
results.push(interval);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
this.intervalTree.mapBackward((interval: TInterval) => {
|
|
95
|
+
if (transientInterval.compareEnd(interval) === 0) {
|
|
96
|
+
results.push(interval);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Start and (possibly) end provided. Walk the subtrees that may contain
|
|
102
|
+
// this start position.
|
|
103
|
+
const compareFn =
|
|
104
|
+
end === undefined
|
|
105
|
+
? (node: IntervalNode<TInterval>) => {
|
|
106
|
+
return transientInterval.compareStart(node.key);
|
|
107
|
+
}
|
|
108
|
+
: (node: IntervalNode<TInterval>) => {
|
|
109
|
+
return transientInterval.compare(node.key);
|
|
110
|
+
};
|
|
111
|
+
const continueLeftFn = (cmpResult: number) => cmpResult <= 0;
|
|
112
|
+
const continueRightFn = (cmpResult: number) => cmpResult >= 0;
|
|
113
|
+
const actionFn = (node: IntervalNode<TInterval>) => {
|
|
114
|
+
results.push(node.key);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (iteratesForward) {
|
|
118
|
+
this.intervalTree.intervals.walkExactMatchesForward(
|
|
119
|
+
compareFn,
|
|
120
|
+
actionFn,
|
|
121
|
+
continueLeftFn,
|
|
122
|
+
continueRightFn,
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
this.intervalTree.intervals.walkExactMatchesBackward(
|
|
126
|
+
compareFn,
|
|
127
|
+
actionFn,
|
|
128
|
+
continueLeftFn,
|
|
129
|
+
continueRightFn,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public findOverlappingIntervals(start: number, end: number): TInterval[] {
|
|
137
|
+
if (end < start || this.intervalTree.intervals.isEmpty()) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const transientInterval = this.helpers.create(
|
|
141
|
+
"transient",
|
|
142
|
+
start,
|
|
143
|
+
end,
|
|
144
|
+
this.client,
|
|
145
|
+
IntervalType.Transient,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
149
|
+
return overlappingIntervalNodes.map((node) => node.key);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public remove(interval: TInterval) {
|
|
153
|
+
this.intervalTree.removeExisting(interval);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public add(interval: TInterval) {
|
|
157
|
+
this.intervalTree.put(interval);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createOverlappingIntervalsIndex<TInterval extends ISerializableInterval>(
|
|
162
|
+
client: Client,
|
|
163
|
+
helpers: IIntervalHelpers<TInterval>,
|
|
164
|
+
): IOverlappingIntervalsIndex<TInterval> {
|
|
165
|
+
return new OverlappingIntervalsIndex<TInterval>(client, helpers);
|
|
166
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Client,
|
|
8
|
+
ISegment,
|
|
9
|
+
ReferenceType,
|
|
10
|
+
compareReferencePositions,
|
|
11
|
+
reservedRangeLabelsKey,
|
|
12
|
+
} from "@fluidframework/merge-tree";
|
|
13
|
+
import {
|
|
14
|
+
sequenceIntervalHelpers,
|
|
15
|
+
IntervalType,
|
|
16
|
+
SequenceInterval,
|
|
17
|
+
createPositionReferenceFromSegoff,
|
|
18
|
+
} from "../intervalCollection";
|
|
19
|
+
import { SequenceIntervalIndexes } from "./sequenceIntervalIndexes";
|
|
20
|
+
import { OverlappingIntervalsIndex } from "./overlappingIntervalsIndex";
|
|
21
|
+
|
|
22
|
+
class OverlappingSequenceIntervalsIndex
|
|
23
|
+
extends OverlappingIntervalsIndex<SequenceInterval>
|
|
24
|
+
implements SequenceIntervalIndexes.Overlapping
|
|
25
|
+
{
|
|
26
|
+
constructor(client: Client) {
|
|
27
|
+
super(client, sequenceIntervalHelpers);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public findOverlappingIntervalsBySegoff(
|
|
31
|
+
startSegoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
32
|
+
endSegoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
33
|
+
): Iterable<SequenceInterval> {
|
|
34
|
+
if (this.intervalTree.intervals.isEmpty()) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const startLref = createPositionReferenceFromSegoff(
|
|
39
|
+
this.client,
|
|
40
|
+
startSegoff,
|
|
41
|
+
ReferenceType.Transient,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const endLref = createPositionReferenceFromSegoff(
|
|
45
|
+
this.client,
|
|
46
|
+
endSegoff,
|
|
47
|
+
ReferenceType.Transient,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (compareReferencePositions(startLref, endLref) > 0) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const transientInterval = new SequenceInterval(
|
|
55
|
+
this.client,
|
|
56
|
+
startLref,
|
|
57
|
+
endLref,
|
|
58
|
+
IntervalType.Transient,
|
|
59
|
+
{ [reservedRangeLabelsKey]: ["transient"] },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
63
|
+
return overlappingIntervalNodes.map((node) => node.key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createOverlappingSequenceIntervalsIndex(
|
|
68
|
+
client: Client,
|
|
69
|
+
): SequenceIntervalIndexes.Overlapping {
|
|
70
|
+
return new OverlappingSequenceIntervalsIndex(client);
|
|
71
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ISegment } from "@fluidframework/merge-tree";
|
|
7
|
+
import { SequenceInterval } from "../intervalCollection";
|
|
8
|
+
import { IOverlappingIntervalsIndex } from "./overlappingIntervalsIndex";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This namespace contains specialiazations of indexes which support spatial queries
|
|
12
|
+
* specifically for `SequenceInterval`s.
|
|
13
|
+
*/
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
15
|
+
export namespace SequenceIntervalIndexes {
|
|
16
|
+
/**
|
|
17
|
+
* Collection of intervals.
|
|
18
|
+
*
|
|
19
|
+
* Provides additional APIs to support efficiently querying a collection of intervals based on segments and offset.
|
|
20
|
+
*/
|
|
21
|
+
export interface Overlapping extends IOverlappingIntervalsIndex<SequenceInterval> {
|
|
22
|
+
/**
|
|
23
|
+
* Finds overlapping intervals within the specified range.
|
|
24
|
+
*
|
|
25
|
+
* @returns an array of all intervals that overlap with the specified SegOff range (includes both ends)
|
|
26
|
+
*/
|
|
27
|
+
findOverlappingIntervalsBySegoff(
|
|
28
|
+
startSegoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
29
|
+
endSegoff: { segment: ISegment | undefined; offset: number | undefined },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|