@fluidframework/merge-tree 2.41.0 → 2.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/client.d.ts +6 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +71 -25
- package/dist/client.js.map +1 -1
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +6 -1
- package/dist/constants.js.map +1 -1
- package/dist/mergeTree.js +1 -1
- package/dist/mergeTree.js.map +1 -1
- package/dist/perspective.d.ts +15 -0
- package/dist/perspective.d.ts.map +1 -1
- package/dist/perspective.js +25 -1
- package/dist/perspective.js.map +1 -1
- package/dist/stamps.d.ts +1 -0
- package/dist/stamps.d.ts.map +1 -1
- package/dist/stamps.js +5 -1
- package/dist/stamps.js.map +1 -1
- package/dist/test/client.applyMsg.spec.js +4 -4
- package/dist/test/client.applyMsg.spec.js.map +1 -1
- package/dist/test/client.applyStashedOpFarm.spec.d.ts.map +1 -1
- package/dist/test/client.applyStashedOpFarm.spec.js +3 -3
- package/dist/test/client.applyStashedOpFarm.spec.js.map +1 -1
- package/dist/test/client.reconnectFarm.spec.js +1 -1
- package/dist/test/client.reconnectFarm.spec.js.map +1 -1
- package/dist/test/client.searchForMarker.spec.js +2 -2
- package/dist/test/client.searchForMarker.spec.js.map +1 -1
- package/dist/test/clientTestHelper.js +1 -1
- package/dist/test/clientTestHelper.js.map +1 -1
- package/dist/test/resetPendingSegmentsToOp.spec.js +10 -10
- package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
- package/lib/client.d.ts +6 -3
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +73 -27
- package/lib/client.js.map +1 -1
- package/lib/constants.d.ts +5 -0
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +5 -0
- package/lib/constants.js.map +1 -1
- package/lib/mergeTree.js +1 -1
- package/lib/mergeTree.js.map +1 -1
- package/lib/perspective.d.ts +15 -0
- package/lib/perspective.d.ts.map +1 -1
- package/lib/perspective.js +23 -0
- package/lib/perspective.js.map +1 -1
- package/lib/stamps.d.ts +1 -0
- package/lib/stamps.d.ts.map +1 -1
- package/lib/stamps.js +4 -1
- package/lib/stamps.js.map +1 -1
- package/lib/test/client.applyMsg.spec.js +4 -4
- package/lib/test/client.applyMsg.spec.js.map +1 -1
- package/lib/test/client.applyStashedOpFarm.spec.d.ts.map +1 -1
- package/lib/test/client.applyStashedOpFarm.spec.js +3 -3
- package/lib/test/client.applyStashedOpFarm.spec.js.map +1 -1
- package/lib/test/client.reconnectFarm.spec.js +1 -1
- package/lib/test/client.reconnectFarm.spec.js.map +1 -1
- package/lib/test/client.searchForMarker.spec.js +2 -2
- package/lib/test/client.searchForMarker.spec.js.map +1 -1
- package/lib/test/clientTestHelper.js +1 -1
- package/lib/test/clientTestHelper.js.map +1 -1
- package/lib/test/resetPendingSegmentsToOp.spec.js +10 -10
- package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
- package/package.json +16 -16
- package/src/client.ts +95 -30
- package/src/constants.ts +6 -0
- package/src/mergeTree.ts +1 -1
- package/src/perspective.ts +26 -0
- package/src/stamps.ts +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/merge-tree",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.42.0",
|
|
4
4
|
"description": "Merge tree",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -81,30 +81,30 @@
|
|
|
81
81
|
"temp-directory": "nyc/.nyc_output"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@fluid-internal/client-utils": "~2.
|
|
85
|
-
"@fluidframework/container-definitions": "~2.
|
|
86
|
-
"@fluidframework/core-interfaces": "~2.
|
|
87
|
-
"@fluidframework/core-utils": "~2.
|
|
88
|
-
"@fluidframework/datastore-definitions": "~2.
|
|
89
|
-
"@fluidframework/driver-definitions": "~2.
|
|
90
|
-
"@fluidframework/runtime-definitions": "~2.
|
|
91
|
-
"@fluidframework/runtime-utils": "~2.
|
|
92
|
-
"@fluidframework/shared-object-base": "~2.
|
|
93
|
-
"@fluidframework/telemetry-utils": "~2.
|
|
84
|
+
"@fluid-internal/client-utils": "~2.42.0",
|
|
85
|
+
"@fluidframework/container-definitions": "~2.42.0",
|
|
86
|
+
"@fluidframework/core-interfaces": "~2.42.0",
|
|
87
|
+
"@fluidframework/core-utils": "~2.42.0",
|
|
88
|
+
"@fluidframework/datastore-definitions": "~2.42.0",
|
|
89
|
+
"@fluidframework/driver-definitions": "~2.42.0",
|
|
90
|
+
"@fluidframework/runtime-definitions": "~2.42.0",
|
|
91
|
+
"@fluidframework/runtime-utils": "~2.42.0",
|
|
92
|
+
"@fluidframework/shared-object-base": "~2.42.0",
|
|
93
|
+
"@fluidframework/telemetry-utils": "~2.42.0"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
96
|
"@arethetypeswrong/cli": "^0.17.1",
|
|
97
97
|
"@biomejs/biome": "~1.9.3",
|
|
98
|
-
"@fluid-internal/mocha-test-setup": "~2.
|
|
99
|
-
"@fluid-private/stochastic-test-utils": "~2.
|
|
100
|
-
"@fluid-private/test-pairwise-generator": "~2.
|
|
98
|
+
"@fluid-internal/mocha-test-setup": "~2.42.0",
|
|
99
|
+
"@fluid-private/stochastic-test-utils": "~2.42.0",
|
|
100
|
+
"@fluid-private/test-pairwise-generator": "~2.42.0",
|
|
101
101
|
"@fluid-tools/benchmark": "^0.51.0",
|
|
102
102
|
"@fluid-tools/build-cli": "^0.55.0",
|
|
103
103
|
"@fluidframework/build-common": "^2.0.3",
|
|
104
104
|
"@fluidframework/build-tools": "^0.55.0",
|
|
105
105
|
"@fluidframework/eslint-config-fluid": "^5.7.4",
|
|
106
|
-
"@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@2.
|
|
107
|
-
"@fluidframework/test-runtime-utils": "~2.
|
|
106
|
+
"@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@2.41.0",
|
|
107
|
+
"@fluidframework/test-runtime-utils": "~2.42.0",
|
|
108
108
|
"@microsoft/api-extractor": "7.52.8",
|
|
109
109
|
"@types/diff": "^3.5.1",
|
|
110
110
|
"@types/mocha": "^10.0.10",
|
package/src/client.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
|
|
28
28
|
import { MergeTreeTextHelper, type IMergeTreeTextHelper } from "./MergeTreeTextHelper.js";
|
|
29
29
|
import { DoublyLinkedList, RedBlackTree } from "./collections/index.js";
|
|
30
|
-
import { NonCollabClient, UniversalSequenceNumber } from "./constants.js";
|
|
30
|
+
import { NonCollabClient, SquashClient, UniversalSequenceNumber } from "./constants.js";
|
|
31
31
|
import { LocalReferencePosition, SlidingPreference } from "./localReference.js";
|
|
32
32
|
import {
|
|
33
33
|
MergeTree,
|
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
} from "./ops.js";
|
|
85
85
|
import {
|
|
86
86
|
LocalReconnectingPerspective,
|
|
87
|
+
LocalSquashPerspective,
|
|
87
88
|
PriorPerspective,
|
|
88
89
|
type Perspective,
|
|
89
90
|
} from "./perspective.js";
|
|
@@ -95,6 +96,7 @@ import {
|
|
|
95
96
|
overwriteInfo,
|
|
96
97
|
toRemovalInfo,
|
|
97
98
|
type IHasInsertionInfo,
|
|
99
|
+
type IHasRemovalInfo,
|
|
98
100
|
} from "./segmentInfos.js";
|
|
99
101
|
import { Side, type InteriorSequencePlace } from "./sequencePlace.js";
|
|
100
102
|
import { SnapshotLoader } from "./snapshotLoader.js";
|
|
@@ -363,6 +365,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
363
365
|
end: number | undefined,
|
|
364
366
|
accum: TClientData,
|
|
365
367
|
splitRange?: boolean,
|
|
368
|
+
perspective?: Pick<ISequencedDocumentMessage, "clientId" | "referenceSequenceNumber">,
|
|
366
369
|
): void;
|
|
367
370
|
public walkSegments(
|
|
368
371
|
handler: ISegmentAction<undefined>,
|
|
@@ -370,6 +373,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
370
373
|
end?: number,
|
|
371
374
|
accum?: undefined,
|
|
372
375
|
splitRange?: boolean,
|
|
376
|
+
perspective?: Pick<ISequencedDocumentMessage, "clientId" | "referenceSequenceNumber">,
|
|
373
377
|
): void;
|
|
374
378
|
public walkSegments<TClientData>(
|
|
375
379
|
handler: ISegmentAction<TClientData>,
|
|
@@ -377,10 +381,13 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
377
381
|
end: number | undefined,
|
|
378
382
|
accum: TClientData,
|
|
379
383
|
splitRange: boolean = false,
|
|
384
|
+
perspective?: Pick<ISequencedDocumentMessage, "clientId" | "referenceSequenceNumber">,
|
|
380
385
|
): void {
|
|
381
386
|
this._mergeTree.mapRange(
|
|
382
387
|
handler,
|
|
383
|
-
|
|
388
|
+
perspective === undefined
|
|
389
|
+
? this.getCollabWindow().localPerspective
|
|
390
|
+
: this.getOperationPerspective(perspective),
|
|
384
391
|
accum,
|
|
385
392
|
start,
|
|
386
393
|
end,
|
|
@@ -556,7 +563,9 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
556
563
|
}
|
|
557
564
|
|
|
558
565
|
private getOperationPerspective(
|
|
559
|
-
sequencedMessage:
|
|
566
|
+
sequencedMessage:
|
|
567
|
+
| Pick<ISequencedDocumentMessage, "clientId" | "referenceSequenceNumber">
|
|
568
|
+
| undefined,
|
|
560
569
|
): Perspective {
|
|
561
570
|
if (!sequencedMessage) {
|
|
562
571
|
return this._mergeTree.localPerspective;
|
|
@@ -898,16 +907,17 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
898
907
|
return { segment: newSegment, offset: newOffset, side: newSide };
|
|
899
908
|
}
|
|
900
909
|
|
|
901
|
-
private computeNewObliterateEndpoints(
|
|
910
|
+
private computeNewObliterateEndpoints(
|
|
911
|
+
obliterateInfo: ObliterateInfo,
|
|
912
|
+
squash: boolean,
|
|
913
|
+
): {
|
|
902
914
|
start: RebasedObliterateEndpoint;
|
|
903
915
|
end: RebasedObliterateEndpoint;
|
|
904
916
|
} {
|
|
905
917
|
const { currentSeq, clientId } = this.getCollabWindow();
|
|
906
|
-
const reconnectingPerspective = new
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
obliterateInfo.stamp.localSeq! - 1,
|
|
910
|
-
);
|
|
918
|
+
const reconnectingPerspective = new (
|
|
919
|
+
squash ? LocalSquashPerspective : LocalReconnectingPerspective
|
|
920
|
+
)(currentSeq, clientId, obliterateInfo.stamp.localSeq! - 1);
|
|
911
921
|
|
|
912
922
|
const newStart = this.rebaseSidedLocalReference(
|
|
913
923
|
obliterateInfo.start,
|
|
@@ -932,6 +942,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
932
942
|
resetOp: IMergeTreeDeltaOp,
|
|
933
943
|
|
|
934
944
|
segmentGroup: SegmentGroup,
|
|
945
|
+
squash: boolean,
|
|
935
946
|
): IMergeTreeDeltaOp[] {
|
|
936
947
|
assert(!!segmentGroup, 0x033 /* "Segment group undefined" */);
|
|
937
948
|
const NACKedSegmentGroup = this.pendingRebase?.shift()?.data;
|
|
@@ -991,13 +1002,20 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
991
1002
|
);
|
|
992
1003
|
const lastRemove = segment.removes[segment.removes.length - 1];
|
|
993
1004
|
assert(
|
|
994
|
-
lastRemove.type === "sliceRemove" &&
|
|
995
|
-
|
|
1005
|
+
(lastRemove.type === "sliceRemove" &&
|
|
1006
|
+
lastRemove.localSeq === segmentGroup.localSeq) ||
|
|
1007
|
+
opstampUtils.isSquashedOp(lastRemove),
|
|
1008
|
+
0xbad /* Last remove should be the obliterate that is being resubmitted. */,
|
|
996
1009
|
);
|
|
997
|
-
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
|
|
1010
|
+
|
|
1011
|
+
// The original obliterate affected this segment, but it has since been removed.
|
|
1012
|
+
// This can happen when a concurrent obliterate also removed the segment, as well as when the segment was
|
|
1013
|
+
// only locally inserted and its insertion was squashed upon reconnecting.
|
|
1014
|
+
// In the concurrent removal case (where we didn't avoid sending the segment's insertion in the first place due
|
|
1015
|
+
// to squashing), we adjust the metadata on that segment to reflect the fact that this obliterate no longer removes it.
|
|
1016
|
+
if (!opstampUtils.isSquashedOp(lastRemove)) {
|
|
1017
|
+
segment.removes.pop();
|
|
1018
|
+
}
|
|
1001
1019
|
}
|
|
1002
1020
|
|
|
1003
1021
|
this._mergeTree.rebaseObliterateTo(obliterateInfo, undefined);
|
|
@@ -1047,10 +1065,11 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1047
1065
|
0x035 /* "Segment group not in segment pending queue" */,
|
|
1048
1066
|
);
|
|
1049
1067
|
if (
|
|
1050
|
-
(segment
|
|
1068
|
+
!isRemovedAndAcked(segment) &&
|
|
1069
|
+
((segment.ordinal > newStartSegment.ordinal &&
|
|
1051
1070
|
segment.ordinal < newEndSegment.ordinal) ||
|
|
1052
|
-
|
|
1053
|
-
|
|
1071
|
+
(segment === newStartSegment && newStartSide === Side.Before) ||
|
|
1072
|
+
(segment === newEndSegment && newEndSide === Side.After))
|
|
1054
1073
|
) {
|
|
1055
1074
|
segment.segmentGroups.enqueue(newObliterate.segmentGroup);
|
|
1056
1075
|
} else {
|
|
@@ -1060,12 +1079,17 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1060
1079
|
);
|
|
1061
1080
|
const lastRemove = segment.removes[segment.removes.length - 1];
|
|
1062
1081
|
assert(
|
|
1063
|
-
lastRemove.type === "sliceRemove" &&
|
|
1064
|
-
|
|
1082
|
+
(lastRemove.type === "sliceRemove" &&
|
|
1083
|
+
lastRemove.localSeq === segmentGroup.localSeq) ||
|
|
1084
|
+
opstampUtils.isSquashedOp(lastRemove),
|
|
1085
|
+
0xbae /* Last remove should be the obliterate that is being resubmitted. */,
|
|
1065
1086
|
);
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1087
|
+
|
|
1088
|
+
if (!opstampUtils.isSquashedOp(lastRemove)) {
|
|
1089
|
+
// The original obliterate affected this segment, but it has since been removed and it's impossible to apply the
|
|
1090
|
+
// local obliterate so that is so. We adjust the metadata on that segment now.
|
|
1091
|
+
segment.removes.pop();
|
|
1092
|
+
}
|
|
1069
1093
|
}
|
|
1070
1094
|
}
|
|
1071
1095
|
|
|
@@ -1154,13 +1178,25 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1154
1178
|
}
|
|
1155
1179
|
|
|
1156
1180
|
case MergeTreeDeltaType.INSERT: {
|
|
1181
|
+
if (isInserted(segment) && opstampUtils.isSquashedOp(segment.insert)) {
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1157
1184
|
assert(
|
|
1158
1185
|
isInserted(segment) && opstampUtils.isLocal(segment.insert),
|
|
1159
1186
|
0x037 /* "Segment already has assigned sequence number" */,
|
|
1160
1187
|
);
|
|
1161
1188
|
const removeInfo = toRemovalInfo(segment);
|
|
1162
1189
|
|
|
1163
|
-
|
|
1190
|
+
const unusedStamp: OperationStamp = { seq: 0, clientId: 0 };
|
|
1191
|
+
if (removeInfo !== undefined && squash) {
|
|
1192
|
+
assert(
|
|
1193
|
+
removeInfo.removes.length === 1 ||
|
|
1194
|
+
opstampUtils.isAcked(removeInfo.removes[removeInfo.removes.length - 2]),
|
|
1195
|
+
0xbaf /* Expected only one local remove */,
|
|
1196
|
+
);
|
|
1197
|
+
this.squashInsertion(segment);
|
|
1198
|
+
break;
|
|
1199
|
+
} else if (removeInfo !== undefined && opstampUtils.isAcked(removeInfo.removes[0])) {
|
|
1164
1200
|
assert(
|
|
1165
1201
|
removeInfo.removes[0].type === "sliceRemove",
|
|
1166
1202
|
0xb5c /* Remove on insertion must be caused by obliterate. */,
|
|
@@ -1181,6 +1217,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1181
1217
|
clientId: NonCollabClient,
|
|
1182
1218
|
},
|
|
1183
1219
|
});
|
|
1220
|
+
this._mergeTree.blockUpdatePathLengths(segment.parent, unusedStamp, true);
|
|
1184
1221
|
break;
|
|
1185
1222
|
}
|
|
1186
1223
|
|
|
@@ -1360,13 +1397,40 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1360
1397
|
{ start: RebasedObliterateEndpoint; end: RebasedObliterateEndpoint }
|
|
1361
1398
|
> = new Map();
|
|
1362
1399
|
|
|
1400
|
+
private squashInsertion(segment: ISegmentLeaf): void {
|
|
1401
|
+
overwriteInfo<IHasInsertionInfo & IHasRemovalInfo>(segment, {
|
|
1402
|
+
insert: {
|
|
1403
|
+
type: "insert",
|
|
1404
|
+
seq: UniversalSequenceNumber,
|
|
1405
|
+
localSeq: undefined,
|
|
1406
|
+
clientId: SquashClient,
|
|
1407
|
+
},
|
|
1408
|
+
removes: [
|
|
1409
|
+
{
|
|
1410
|
+
type: "setRemove",
|
|
1411
|
+
seq: UniversalSequenceNumber,
|
|
1412
|
+
localSeq: undefined,
|
|
1413
|
+
clientId: SquashClient,
|
|
1414
|
+
},
|
|
1415
|
+
],
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
this._mergeTree.blockUpdatePathLengths(segment.parent, { seq: 0, clientId: 0 }, true);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1363
1421
|
/**
|
|
1364
1422
|
* Given a pending operation and segment group, regenerate the op, so it
|
|
1365
1423
|
* can be resubmitted
|
|
1366
1424
|
* @param resetOp - The op to reset
|
|
1367
1425
|
* @param segmentGroup - The segment group associated with the op
|
|
1426
|
+
* @param squash - whether intermediate states should be squashed. See `IDeltaHandler.reSubmit`'s squash parameter
|
|
1427
|
+
* documentation for more details.
|
|
1368
1428
|
*/
|
|
1369
|
-
public regeneratePendingOp(
|
|
1429
|
+
public regeneratePendingOp(
|
|
1430
|
+
resetOp: IMergeTreeOp,
|
|
1431
|
+
localOpMetadata: unknown,
|
|
1432
|
+
squash: boolean,
|
|
1433
|
+
): IMergeTreeOp {
|
|
1370
1434
|
const segmentGroup = localOpMetadata as SegmentGroup | SegmentGroup[];
|
|
1371
1435
|
if (this.pendingRebase === undefined || this.pendingRebase.empty) {
|
|
1372
1436
|
let firstGroup: SegmentGroup;
|
|
@@ -1396,13 +1460,14 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1396
1460
|
collabWindow.currentSeq !== this.lastNormalization.refSeq ||
|
|
1397
1461
|
collabWindow.localSeq !== this.lastNormalization.localRefSeq
|
|
1398
1462
|
) {
|
|
1463
|
+
const allPendingSegments = [...this._mergeTree.pendingSegments, ...this.pendingRebase];
|
|
1399
1464
|
// Compute obliterate endpoint destinations before segments are normalized.
|
|
1400
1465
|
// Segment normalization can affect what should be the semantically correct segments for the endpoints to be placed on.
|
|
1401
1466
|
this.cachedObliterateRebases.clear();
|
|
1402
|
-
for (const group of
|
|
1467
|
+
for (const group of allPendingSegments) {
|
|
1403
1468
|
const { obliterateInfo } = group.data;
|
|
1404
1469
|
if (obliterateInfo !== undefined) {
|
|
1405
|
-
const { start, end } = this.computeNewObliterateEndpoints(obliterateInfo);
|
|
1470
|
+
const { start, end } = this.computeNewObliterateEndpoints(obliterateInfo, squash);
|
|
1406
1471
|
const { localSeq } = obliterateInfo.stamp;
|
|
1407
1472
|
assert(localSeq !== undefined, 0xb6d /* Local seq must be defined */);
|
|
1408
1473
|
this.cachedObliterateRebases.set(localSeq, { start, end });
|
|
@@ -1426,7 +1491,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1426
1491
|
);
|
|
1427
1492
|
|
|
1428
1493
|
for (let i = 0; i < resetOp.ops.length; i++) {
|
|
1429
|
-
opList.push(...this.resetPendingDeltaToOps(resetOp.ops[i], segmentGroup[i]));
|
|
1494
|
+
opList.push(...this.resetPendingDeltaToOps(resetOp.ops[i], segmentGroup[i], squash));
|
|
1430
1495
|
}
|
|
1431
1496
|
} else {
|
|
1432
1497
|
// A group op containing a single op will pass a direct reference to 'segmentGroup'
|
|
@@ -1435,7 +1500,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1435
1500
|
resetOp.ops.length === 1,
|
|
1436
1501
|
0x03b /* "Number of ops in 'resetOp' must match the number of segment groups provided." */,
|
|
1437
1502
|
);
|
|
1438
|
-
opList.push(...this.resetPendingDeltaToOps(resetOp.ops[0], segmentGroup));
|
|
1503
|
+
opList.push(...this.resetPendingDeltaToOps(resetOp.ops[0], segmentGroup, squash));
|
|
1439
1504
|
}
|
|
1440
1505
|
} else {
|
|
1441
1506
|
assert(
|
|
@@ -1446,7 +1511,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1446
1511
|
!Array.isArray(segmentGroup),
|
|
1447
1512
|
0x03d /* "segmentGroup is array rather than singleton!" */,
|
|
1448
1513
|
);
|
|
1449
|
-
opList.push(...this.resetPendingDeltaToOps(resetOp, segmentGroup));
|
|
1514
|
+
opList.push(...this.resetPendingDeltaToOps(resetOp, segmentGroup, squash));
|
|
1450
1515
|
}
|
|
1451
1516
|
|
|
1452
1517
|
return opList.length === 1 ? opList[0] : createGroupOp(...opList);
|
package/src/constants.ts
CHANGED
|
@@ -34,3 +34,9 @@ export const LocalClientId = -1;
|
|
|
34
34
|
* @internal
|
|
35
35
|
*/
|
|
36
36
|
export const NonCollabClient = -2;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Used as the client id for operations that were squashed upon resubmission and should therefore
|
|
40
|
+
* never be seen by other clients.
|
|
41
|
+
*/
|
|
42
|
+
export const SquashClient = -3;
|
package/src/mergeTree.ts
CHANGED
|
@@ -1270,7 +1270,7 @@ export class MergeTree {
|
|
|
1270
1270
|
segment,
|
|
1271
1271
|
(node) => {
|
|
1272
1272
|
if (node.isLeaf()) {
|
|
1273
|
-
if (Marker.is(node) && refHasTileLabel(node, markerLabel)) {
|
|
1273
|
+
if (!isRemoved(node) && Marker.is(node) && refHasTileLabel(node, markerLabel)) {
|
|
1274
1274
|
foundMarker = node;
|
|
1275
1275
|
}
|
|
1276
1276
|
} else {
|
package/src/perspective.ts
CHANGED
|
@@ -117,6 +117,32 @@ export class LocalReconnectingPerspective extends PerspectiveBase implements Per
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* This perspective is used when rebasing obliterate endpoints to find the segment to slide to when squash is enabled.
|
|
122
|
+
*
|
|
123
|
+
* TODO:AB#39357: This class would not be necessary if obliterate rebasing occurred as resubmit was called rather than
|
|
124
|
+
* precomputed before segment normalization. It also adds more dependencies on all ops being resubmitted (the squash
|
|
125
|
+
* parameter coming from rebasing an obliterate does not necessarily align with an inserted segment), which is not
|
|
126
|
+
* fully correct.
|
|
127
|
+
*/
|
|
128
|
+
export class LocalSquashPerspective extends LocalReconnectingPerspective {
|
|
129
|
+
public constructor(
|
|
130
|
+
readonly refSeq: number,
|
|
131
|
+
readonly clientId: number,
|
|
132
|
+
readonly localSeq: number,
|
|
133
|
+
) {
|
|
134
|
+
super(refSeq, clientId, localSeq);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public override isSegmentPresent(seg: ISegment): boolean {
|
|
138
|
+
// Avoid sliding to segments whose insertion will be squashed.
|
|
139
|
+
if (isInserted(seg) && opstampUtils.isLocal(seg.insert) && isRemoved(seg)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return super.isSegmentPresent(seg);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
120
146
|
/**
|
|
121
147
|
* A perspective which includes edits which were either:
|
|
122
148
|
* - acked and at or before some reference sequence number
|
package/src/stamps.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
SquashClient,
|
|
8
|
+
UnassignedSequenceNumber,
|
|
9
|
+
UniversalSequenceNumber,
|
|
10
|
+
} from "./constants.js";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* A stamp that identifies provenance of an operation performed on the MergeTree.
|
|
@@ -122,6 +126,10 @@ export function isLocal(a: OperationStamp): boolean {
|
|
|
122
126
|
return a.seq === UnassignedSequenceNumber;
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
export function isSquashedOp(a: OperationStamp): boolean {
|
|
130
|
+
return a.clientId === SquashClient && a.seq === UniversalSequenceNumber;
|
|
131
|
+
}
|
|
132
|
+
|
|
125
133
|
export function isAcked(a: OperationStamp): boolean {
|
|
126
134
|
return a.seq !== UnassignedSequenceNumber;
|
|
127
135
|
}
|