@fluidframework/merge-tree 2.1.0 → 2.2.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 +22 -0
- package/README.md +2 -2
- package/api-report/merge-tree.legacy.alpha.api.md +47 -11
- package/dist/attributionCollection.d.ts.map +1 -1
- package/dist/attributionCollection.js +9 -2
- package/dist/attributionCollection.js.map +1 -1
- package/dist/attributionPolicy.d.ts.map +1 -1
- package/dist/attributionPolicy.js +5 -9
- package/dist/attributionPolicy.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -19
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +4 -0
- package/dist/localReference.d.ts +6 -0
- package/dist/localReference.d.ts.map +1 -1
- package/dist/localReference.js.map +1 -1
- package/dist/mergeTree.d.ts +11 -1
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +51 -14
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +35 -1
- package/dist/mergeTreeNodes.d.ts.map +1 -1
- package/dist/mergeTreeNodes.js +35 -12
- package/dist/mergeTreeNodes.js.map +1 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +7 -21
- package/dist/properties.js.map +1 -1
- package/dist/referencePositions.d.ts +6 -0
- package/dist/referencePositions.d.ts.map +1 -1
- package/dist/referencePositions.js.map +1 -1
- package/dist/segmentGroupCollection.d.ts +3 -0
- package/dist/segmentGroupCollection.d.ts.map +1 -1
- package/dist/segmentGroupCollection.js +3 -0
- package/dist/segmentGroupCollection.js.map +1 -1
- package/dist/segmentPropertiesManager.d.ts +8 -0
- package/dist/segmentPropertiesManager.d.ts.map +1 -1
- package/dist/segmentPropertiesManager.js +11 -8
- package/dist/segmentPropertiesManager.js.map +1 -1
- package/dist/sequencePlace.d.ts +66 -0
- package/dist/sequencePlace.d.ts.map +1 -0
- package/dist/sequencePlace.js +41 -0
- package/dist/sequencePlace.js.map +1 -0
- package/dist/snapshotlegacy.js +1 -1
- package/dist/snapshotlegacy.js.map +1 -1
- package/dist/test/obliterate.rangeExpansion.spec.d.ts +6 -0
- package/dist/test/obliterate.rangeExpansion.spec.d.ts.map +1 -0
- package/dist/test/obliterate.rangeExpansion.spec.js +288 -0
- package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -0
- package/dist/test/obliterate.reconnect.spec.js +5 -5
- package/dist/test/obliterate.reconnect.spec.js.map +1 -1
- package/dist/test/reconnectHelper.d.ts +3 -3
- package/dist/test/reconnectHelper.d.ts.map +1 -1
- package/dist/test/reconnectHelper.js +14 -3
- package/dist/test/reconnectHelper.js.map +1 -1
- package/dist/test/resetPendingSegmentsToOp.spec.js +12 -0
- package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
- package/dist/test/testClient.d.ts +3 -3
- package/dist/test/testClient.d.ts.map +1 -1
- package/dist/test/testClient.js +4 -16
- package/dist/test/testClient.js.map +1 -1
- package/dist/test/text.js +1 -1
- package/dist/test/text.js.map +1 -1
- package/dist/textSegment.d.ts +2 -2
- package/dist/textSegment.d.ts.map +1 -1
- package/dist/textSegment.js +3 -7
- package/dist/textSegment.js.map +1 -1
- package/lib/attributionCollection.d.ts.map +1 -1
- package/lib/attributionCollection.js +9 -2
- package/lib/attributionCollection.js.map +1 -1
- package/lib/attributionPolicy.d.ts.map +1 -1
- package/lib/attributionPolicy.js +5 -9
- package/lib/attributionPolicy.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +21 -22
- package/lib/client.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/legacy.d.ts +4 -0
- package/lib/localReference.d.ts +6 -0
- package/lib/localReference.d.ts.map +1 -1
- package/lib/localReference.js.map +1 -1
- package/lib/mergeTree.d.ts +11 -1
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +49 -13
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +35 -1
- package/lib/mergeTreeNodes.d.ts.map +1 -1
- package/lib/mergeTreeNodes.js +35 -12
- package/lib/mergeTreeNodes.js.map +1 -1
- package/lib/properties.d.ts.map +1 -1
- package/lib/properties.js +7 -21
- package/lib/properties.js.map +1 -1
- package/lib/referencePositions.d.ts +6 -0
- package/lib/referencePositions.d.ts.map +1 -1
- package/lib/referencePositions.js.map +1 -1
- package/lib/segmentGroupCollection.d.ts +3 -0
- package/lib/segmentGroupCollection.d.ts.map +1 -1
- package/lib/segmentGroupCollection.js +3 -0
- package/lib/segmentGroupCollection.js.map +1 -1
- package/lib/segmentPropertiesManager.d.ts +8 -0
- package/lib/segmentPropertiesManager.d.ts.map +1 -1
- package/lib/segmentPropertiesManager.js +12 -9
- package/lib/segmentPropertiesManager.js.map +1 -1
- package/lib/sequencePlace.d.ts +66 -0
- package/lib/sequencePlace.d.ts.map +1 -0
- package/lib/sequencePlace.js +37 -0
- package/lib/sequencePlace.js.map +1 -0
- package/lib/snapshotlegacy.js +1 -1
- package/lib/snapshotlegacy.js.map +1 -1
- package/lib/test/obliterate.rangeExpansion.spec.d.ts +6 -0
- package/lib/test/obliterate.rangeExpansion.spec.d.ts.map +1 -0
- package/lib/test/obliterate.rangeExpansion.spec.js +286 -0
- package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -0
- package/lib/test/obliterate.reconnect.spec.js +5 -5
- package/lib/test/obliterate.reconnect.spec.js.map +1 -1
- package/lib/test/reconnectHelper.d.ts +3 -3
- package/lib/test/reconnectHelper.d.ts.map +1 -1
- package/lib/test/reconnectHelper.js +14 -3
- package/lib/test/reconnectHelper.js.map +1 -1
- package/lib/test/resetPendingSegmentsToOp.spec.js +12 -0
- package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
- package/lib/test/testClient.d.ts +3 -3
- package/lib/test/testClient.d.ts.map +1 -1
- package/lib/test/testClient.js +4 -16
- package/lib/test/testClient.js.map +1 -1
- package/lib/test/text.js +1 -1
- package/lib/test/text.js.map +1 -1
- package/lib/textSegment.d.ts +2 -2
- package/lib/textSegment.d.ts.map +1 -1
- package/lib/textSegment.js +3 -7
- package/lib/textSegment.js.map +1 -1
- package/package.json +19 -19
- package/src/attributionCollection.ts +12 -2
- package/src/attributionPolicy.ts +6 -7
- package/src/client.ts +28 -23
- package/src/index.ts +6 -0
- package/src/localReference.ts +8 -1
- package/src/mergeTree.ts +74 -17
- package/src/mergeTreeNodes.ts +66 -14
- package/src/properties.ts +7 -22
- package/src/referencePositions.ts +6 -0
- package/src/segmentGroupCollection.ts +3 -0
- package/src/segmentPropertiesManager.ts +13 -9
- package/src/sequencePlace.ts +89 -0
- package/src/snapshotlegacy.ts +1 -1
- package/src/textSegment.ts +8 -9
package/src/attributionPolicy.ts
CHANGED
|
@@ -136,25 +136,22 @@ function createPropertyTrackingMergeTreeCallbacks(
|
|
|
136
136
|
): AttributionCallbacks {
|
|
137
137
|
const toTrack = propNames.map((entry) => ({ propName: entry, channelName: entry }));
|
|
138
138
|
const attributeAnnotateOnSegments = (
|
|
139
|
+
isLocal: boolean,
|
|
139
140
|
deltaSegments: IMergeTreeSegmentDelta[],
|
|
140
|
-
{ op
|
|
141
|
+
{ op }: IMergeTreeDeltaOpArgs,
|
|
141
142
|
key: AttributionKey,
|
|
142
143
|
): void => {
|
|
143
|
-
for (const { segment } of deltaSegments) {
|
|
144
|
+
for (const { segment, propertyDeltas } of deltaSegments) {
|
|
144
145
|
for (const { propName, channelName } of toTrack) {
|
|
145
146
|
const shouldAttributeInsert =
|
|
146
147
|
op.type === MergeTreeDeltaType.INSERT &&
|
|
147
148
|
segment.properties?.[propName] !== undefined;
|
|
148
149
|
|
|
149
|
-
const isLocal = sequencedMessage === undefined;
|
|
150
150
|
const shouldAttributeAnnotate =
|
|
151
151
|
op.type === MergeTreeDeltaType.ANNOTATE &&
|
|
152
152
|
// Only attribute annotations which change the tracked property
|
|
153
153
|
op.props[propName] !== undefined &&
|
|
154
|
-
|
|
155
|
-
(isLocal ||
|
|
156
|
-
// Acked changes only take effect if there isn't a pending local change
|
|
157
|
-
(!isLocal && !segment.propertyManager?.hasPendingProperty(propName)));
|
|
154
|
+
(isLocal || (propertyDeltas !== undefined && propName in propertyDeltas));
|
|
158
155
|
|
|
159
156
|
if (shouldAttributeInsert || shouldAttributeAnnotate) {
|
|
160
157
|
segment.attribution?.update(
|
|
@@ -170,6 +167,7 @@ function createPropertyTrackingMergeTreeCallbacks(
|
|
|
170
167
|
const { op, sequencedMessage } = opArgs;
|
|
171
168
|
if (op.type === MergeTreeDeltaType.ANNOTATE || op.type === MergeTreeDeltaType.INSERT) {
|
|
172
169
|
attributeAnnotateOnSegments(
|
|
170
|
+
sequencedMessage === undefined,
|
|
173
171
|
deltaSegments,
|
|
174
172
|
opArgs,
|
|
175
173
|
getAttributionKey(client, sequencedMessage),
|
|
@@ -179,6 +177,7 @@ function createPropertyTrackingMergeTreeCallbacks(
|
|
|
179
177
|
maintenance: ({ deltaSegments, operation }, opArgs, client): void => {
|
|
180
178
|
if (operation === MergeTreeMaintenanceType.ACKNOWLEDGED && opArgs !== undefined) {
|
|
181
179
|
attributeAnnotateOnSegments(
|
|
180
|
+
true,
|
|
182
181
|
deltaSegments,
|
|
183
182
|
opArgs,
|
|
184
183
|
getAttributionKey(client, opArgs.sequencedMessage),
|
package/src/client.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
9
9
|
import { type IEventThisPlaceHolder, IFluidHandle } from "@fluidframework/core-interfaces";
|
|
10
|
-
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
|
|
10
|
+
import { assert, unreachableCase, isObject } from "@fluidframework/core-utils/internal";
|
|
11
11
|
import {
|
|
12
12
|
IFluidDataStoreRuntime,
|
|
13
13
|
IChannelStorageService,
|
|
@@ -29,7 +29,7 @@ import { MergeTreeTextHelper } from "./MergeTreeTextHelper.js";
|
|
|
29
29
|
import { DoublyLinkedList, RedBlackTree } from "./collections/index.js";
|
|
30
30
|
import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js";
|
|
31
31
|
import { LocalReferencePosition, SlidingPreference } from "./localReference.js";
|
|
32
|
-
import { IMergeTreeOptions, MergeTree } from "./mergeTree.js";
|
|
32
|
+
import { IMergeTreeOptions, MergeTree, errorIfOptionNotTrue } from "./mergeTree.js";
|
|
33
33
|
import type {
|
|
34
34
|
IMergeTreeClientSequenceArgs,
|
|
35
35
|
IMergeTreeDeltaCallbackArgs,
|
|
@@ -40,7 +40,6 @@ import { walkAllChildSegments } from "./mergeTreeNodeWalk.js";
|
|
|
40
40
|
import {
|
|
41
41
|
// eslint-disable-next-line import/no-deprecated
|
|
42
42
|
CollaborationWindow,
|
|
43
|
-
IMoveInfo,
|
|
44
43
|
ISegment,
|
|
45
44
|
ISegmentAction,
|
|
46
45
|
ISegmentLeaf,
|
|
@@ -48,6 +47,7 @@ import {
|
|
|
48
47
|
// eslint-disable-next-line import/no-deprecated
|
|
49
48
|
SegmentGroup,
|
|
50
49
|
compareStrings,
|
|
50
|
+
toMoveInfo,
|
|
51
51
|
} from "./mergeTreeNodes.js";
|
|
52
52
|
import {
|
|
53
53
|
createAnnotateMarkerOp,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
MergeTreeDeltaType,
|
|
74
74
|
ReferenceType,
|
|
75
75
|
} from "./ops.js";
|
|
76
|
-
import { PropertySet
|
|
76
|
+
import { PropertySet } from "./properties.js";
|
|
77
77
|
import { DetachedReferencePosition, ReferencePosition } from "./referencePositions.js";
|
|
78
78
|
import { SnapshotLoader } from "./snapshotLoader.js";
|
|
79
79
|
import { SnapshotV1 } from "./snapshotV1.js";
|
|
@@ -84,14 +84,6 @@ import { IMergeTreeTextHelper } from "./textSegment.js";
|
|
|
84
84
|
type IMergeTreeDeltaRemoteOpArgs = Omit<IMergeTreeDeltaOpArgs, "sequencedMessage"> &
|
|
85
85
|
Required<Pick<IMergeTreeDeltaOpArgs, "sequencedMessage">>;
|
|
86
86
|
|
|
87
|
-
function removeMoveInfo(segment: Partial<IMoveInfo>): void {
|
|
88
|
-
delete segment.movedSeq;
|
|
89
|
-
delete segment.movedSeqs;
|
|
90
|
-
delete segment.localMovedSeq;
|
|
91
|
-
delete segment.movedClientIds;
|
|
92
|
-
delete segment.wasMovedOnInsert;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
87
|
/**
|
|
96
88
|
* A range [start, end)
|
|
97
89
|
* @internal
|
|
@@ -825,18 +817,30 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
825
817
|
segment.seq === UnassignedSequenceNumber,
|
|
826
818
|
0x037 /* "Segment already has assigned sequence number" */,
|
|
827
819
|
);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
820
|
+
const moveInfo = toMoveInfo(segment);
|
|
821
|
+
|
|
822
|
+
if (moveInfo !== undefined) {
|
|
823
|
+
errorIfOptionNotTrue(
|
|
824
|
+
this._mergeTree.options,
|
|
825
|
+
"mergeTreeEnableObliterateReconnect",
|
|
826
|
+
);
|
|
827
|
+
if (moveInfo.movedSeq !== UnassignedSequenceNumber) {
|
|
828
|
+
// the segment was remotely obliterated, so is considered removed
|
|
829
|
+
// we set the seq to the universal seq and remove the local seq,
|
|
830
|
+
// so its length is not considered for subsequent local changes
|
|
831
|
+
// this allows us to not send the op as even the local client will ignore the segment
|
|
832
|
+
segment.seq = UniversalSequenceNumber;
|
|
833
|
+
segment.localSeq = undefined;
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
839
836
|
}
|
|
837
|
+
|
|
838
|
+
const segInsertOp: ISegment = segment.clone();
|
|
839
|
+
const opProps =
|
|
840
|
+
isObject(resetOp.seg) && "props" in resetOp.seg && isObject(resetOp.seg.props)
|
|
841
|
+
? { ...resetOp.seg.props }
|
|
842
|
+
: undefined;
|
|
843
|
+
segInsertOp.properties = opProps;
|
|
840
844
|
newOp = createInsertSegmentOp(segmentPosition, segInsertOp);
|
|
841
845
|
break;
|
|
842
846
|
}
|
|
@@ -857,6 +861,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
857
861
|
break;
|
|
858
862
|
}
|
|
859
863
|
case MergeTreeDeltaType.OBLITERATE: {
|
|
864
|
+
errorIfOptionNotTrue(this._mergeTree.options, "mergeTreeEnableObliterateReconnect");
|
|
860
865
|
if (
|
|
861
866
|
segment.localMovedSeq !== undefined &&
|
|
862
867
|
segment.movedSeq === UnassignedSequenceNumber &&
|
package/src/index.ts
CHANGED
|
@@ -122,6 +122,12 @@ export {
|
|
|
122
122
|
} from "./referencePositions.js";
|
|
123
123
|
export { SegmentGroupCollection } from "./segmentGroupCollection.js";
|
|
124
124
|
export { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
125
|
+
export {
|
|
126
|
+
InteriorSequencePlace,
|
|
127
|
+
Side,
|
|
128
|
+
SequencePlace,
|
|
129
|
+
endpointPosAndSide,
|
|
130
|
+
} from "./sequencePlace.js";
|
|
125
131
|
export { SortedSet } from "./sortedSet.js";
|
|
126
132
|
export { SortedSegmentSet, SortedSegmentSetItem } from "./sortedSegmentSet.js";
|
|
127
133
|
export { IJSONTextSegment, IMergeTreeTextHelper, TextSegment } from "./textSegment.js";
|
package/src/localReference.ts
CHANGED
|
@@ -70,6 +70,13 @@ export interface LocalReferencePosition extends ReferencePosition {
|
|
|
70
70
|
* special segments representing the position before or after the tree
|
|
71
71
|
*/
|
|
72
72
|
readonly canSlideToEndpoint?: boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param newProps - Properties to add to this reference.
|
|
76
|
+
* @remarks Note that merge-tree does not broadcast changes to other clients. It is up to the consumer
|
|
77
|
+
* to ensure broadcast happens if that is desired.
|
|
78
|
+
*/
|
|
79
|
+
addProperties(newProps: PropertySet): void;
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
/**
|
|
@@ -125,7 +132,7 @@ class LocalReference implements LocalReferencePosition {
|
|
|
125
132
|
this.offset = offset;
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
public isLeaf():
|
|
135
|
+
public isLeaf(): this is ISegment {
|
|
129
136
|
return false;
|
|
130
137
|
}
|
|
131
138
|
|
package/src/mergeTree.ts
CHANGED
|
@@ -85,7 +85,9 @@ import {
|
|
|
85
85
|
refHasTileLabel,
|
|
86
86
|
refTypeIncludesFlag,
|
|
87
87
|
} from "./referencePositions.js";
|
|
88
|
+
// eslint-disable-next-line import/no-deprecated
|
|
88
89
|
import { PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
90
|
+
import { endpointPosAndSide, type SequencePlace } from "./sequencePlace.js";
|
|
89
91
|
import { zamboniSegments } from "./zamboni.js";
|
|
90
92
|
|
|
91
93
|
function wasRemovedAfter(seg: ISegment, seq: number): boolean {
|
|
@@ -190,6 +192,23 @@ export interface IMergeTreeOptions {
|
|
|
190
192
|
* Default value: false
|
|
191
193
|
*/
|
|
192
194
|
mergeTreeEnableObliterate?: boolean;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Enables support for reconnecting when obliterate operations are present
|
|
198
|
+
*
|
|
199
|
+
* Obliterate is currently experimental and may not work in all scenarios.
|
|
200
|
+
*
|
|
201
|
+
* @defaultValue `false`
|
|
202
|
+
*/
|
|
203
|
+
mergeTreeEnableObliterateReconnect?: boolean;
|
|
204
|
+
}
|
|
205
|
+
export function errorIfOptionNotTrue(
|
|
206
|
+
options: IMergeTreeOptions | undefined,
|
|
207
|
+
option: keyof IMergeTreeOptions,
|
|
208
|
+
): void {
|
|
209
|
+
if (options?.[option] !== true) {
|
|
210
|
+
throw new Error(`${option} is not enabled.`);
|
|
211
|
+
}
|
|
193
212
|
}
|
|
194
213
|
|
|
195
214
|
/**
|
|
@@ -1636,7 +1655,11 @@ export class MergeTree {
|
|
|
1636
1655
|
return { next };
|
|
1637
1656
|
};
|
|
1638
1657
|
|
|
1639
|
-
private ensureIntervalBoundary(
|
|
1658
|
+
private ensureIntervalBoundary(
|
|
1659
|
+
pos: number | "start" | "end",
|
|
1660
|
+
refSeq: number,
|
|
1661
|
+
clientId: number,
|
|
1662
|
+
): void {
|
|
1640
1663
|
const splitNode = this.insertingWalk(
|
|
1641
1664
|
this.root,
|
|
1642
1665
|
pos,
|
|
@@ -1684,14 +1707,22 @@ export class MergeTree {
|
|
|
1684
1707
|
|
|
1685
1708
|
private insertingWalk(
|
|
1686
1709
|
block: MergeBlock,
|
|
1687
|
-
pos: number,
|
|
1710
|
+
pos: number | "start" | "end",
|
|
1688
1711
|
refSeq: number,
|
|
1689
1712
|
clientId: number,
|
|
1690
1713
|
seq: number,
|
|
1691
1714
|
context: InsertContext,
|
|
1692
1715
|
isLastChildBlock: boolean = true,
|
|
1693
1716
|
): MergeBlock | undefined {
|
|
1694
|
-
let _pos
|
|
1717
|
+
let _pos: number;
|
|
1718
|
+
if (pos === "start") {
|
|
1719
|
+
_pos = 0;
|
|
1720
|
+
} else if (pos === "end") {
|
|
1721
|
+
_pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
|
|
1722
|
+
} else {
|
|
1723
|
+
_pos = pos;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1695
1726
|
const children = block.children;
|
|
1696
1727
|
let childIndex: number;
|
|
1697
1728
|
let child: IMergeNode;
|
|
@@ -1849,6 +1880,7 @@ export class MergeTree {
|
|
|
1849
1880
|
clientId: number,
|
|
1850
1881
|
seq: number,
|
|
1851
1882
|
opArgs: IMergeTreeDeltaOpArgs,
|
|
1883
|
+
// eslint-disable-next-line import/no-deprecated
|
|
1852
1884
|
rollback: PropertiesRollback = PropertiesRollback.None,
|
|
1853
1885
|
): void {
|
|
1854
1886
|
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
@@ -1910,20 +1942,30 @@ export class MergeTree {
|
|
|
1910
1942
|
}
|
|
1911
1943
|
|
|
1912
1944
|
public obliterateRange(
|
|
1913
|
-
start:
|
|
1914
|
-
end:
|
|
1945
|
+
start: SequencePlace,
|
|
1946
|
+
end: SequencePlace,
|
|
1915
1947
|
refSeq: number,
|
|
1916
1948
|
clientId: number,
|
|
1917
1949
|
seq: number,
|
|
1918
1950
|
overwrite: boolean = false,
|
|
1919
1951
|
opArgs: IMergeTreeDeltaOpArgs,
|
|
1920
1952
|
): void {
|
|
1921
|
-
|
|
1922
|
-
throw new UsageError("Attempted to send obliterate op without enabling feature flag.");
|
|
1923
|
-
}
|
|
1953
|
+
errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
|
|
1924
1954
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1955
|
+
const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
|
|
1956
|
+
|
|
1957
|
+
assert(
|
|
1958
|
+
startPos !== undefined &&
|
|
1959
|
+
endPos !== undefined &&
|
|
1960
|
+
startSide !== undefined &&
|
|
1961
|
+
endSide !== undefined &&
|
|
1962
|
+
startPos !== "end" &&
|
|
1963
|
+
endPos !== "start",
|
|
1964
|
+
0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */,
|
|
1965
|
+
);
|
|
1966
|
+
|
|
1967
|
+
this.ensureIntervalBoundary(startPos, refSeq, clientId);
|
|
1968
|
+
this.ensureIntervalBoundary(endPos, refSeq, clientId);
|
|
1927
1969
|
|
|
1928
1970
|
let _overwrite = overwrite;
|
|
1929
1971
|
const localOverlapWithRefs: ISegment[] = [];
|
|
@@ -1944,6 +1986,8 @@ export class MergeTree {
|
|
|
1944
1986
|
_end: number,
|
|
1945
1987
|
): boolean => {
|
|
1946
1988
|
const existingMoveInfo = toMoveInfo(segment);
|
|
1989
|
+
if (startSide) segment.startSide = startSide;
|
|
1990
|
+
if (endSide) segment.endSide = endSide;
|
|
1947
1991
|
|
|
1948
1992
|
if (
|
|
1949
1993
|
clientId !== segment.clientId &&
|
|
@@ -2259,6 +2303,7 @@ export class MergeTree {
|
|
|
2259
2303
|
this.collabWindow.clientId,
|
|
2260
2304
|
UniversalSequenceNumber,
|
|
2261
2305
|
{ op: annotateOp },
|
|
2306
|
+
// eslint-disable-next-line import/no-deprecated
|
|
2262
2307
|
PropertiesRollback.Rollback,
|
|
2263
2308
|
);
|
|
2264
2309
|
i++;
|
|
@@ -2662,18 +2707,28 @@ export class MergeTree {
|
|
|
2662
2707
|
leaf: ISegmentAction<TClientData>,
|
|
2663
2708
|
accum: TClientData,
|
|
2664
2709
|
post?: BlockAction<TClientData>,
|
|
2665
|
-
start:
|
|
2666
|
-
end?:
|
|
2710
|
+
start: SequencePlace = 0,
|
|
2711
|
+
end?: SequencePlace,
|
|
2667
2712
|
localSeq?: number,
|
|
2668
2713
|
visibilitySeq: number = refSeq,
|
|
2669
2714
|
): void {
|
|
2670
|
-
const
|
|
2671
|
-
if (
|
|
2715
|
+
const maybeEndPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
|
|
2716
|
+
if (maybeEndPos === start) {
|
|
2672
2717
|
return;
|
|
2673
2718
|
}
|
|
2674
2719
|
|
|
2675
2720
|
let pos = 0;
|
|
2721
|
+
let { startPos, endPos } = endpointPosAndSide(start, end);
|
|
2676
2722
|
|
|
2723
|
+
startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
|
|
2724
|
+
endPos =
|
|
2725
|
+
endPos === "end" || endPos === undefined
|
|
2726
|
+
? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
|
|
2727
|
+
: endPos;
|
|
2728
|
+
assert(
|
|
2729
|
+
startPos !== "end" && endPos !== "start",
|
|
2730
|
+
0x9e3 /* start cannot be 'end' and end cannot be 'start' */,
|
|
2731
|
+
);
|
|
2677
2732
|
depthFirstNodeWalk(
|
|
2678
2733
|
this.root,
|
|
2679
2734
|
this.root.children[0],
|
|
@@ -2701,13 +2756,15 @@ export class MergeTree {
|
|
|
2701
2756
|
|
|
2702
2757
|
const nextPos = pos + lenAtRefSeq;
|
|
2703
2758
|
// start is beyond the current node, so we can skip it
|
|
2704
|
-
if (
|
|
2759
|
+
if (typeof startPos === "number" && startPos >= nextPos) {
|
|
2705
2760
|
pos = nextPos;
|
|
2706
2761
|
return NodeAction.Skip;
|
|
2707
2762
|
}
|
|
2708
2763
|
|
|
2709
2764
|
if (node.isLeaf()) {
|
|
2710
|
-
if (
|
|
2765
|
+
if (
|
|
2766
|
+
leaf(node, pos, refSeq, clientId, startPos - pos, endPos - pos, accum) === false
|
|
2767
|
+
) {
|
|
2711
2768
|
return NodeAction.Exit;
|
|
2712
2769
|
}
|
|
2713
2770
|
pos = nextPos;
|
|
@@ -2717,7 +2774,7 @@ export class MergeTree {
|
|
|
2717
2774
|
post === undefined
|
|
2718
2775
|
? undefined
|
|
2719
2776
|
: (block): boolean =>
|
|
2720
|
-
post(block, pos, refSeq, clientId,
|
|
2777
|
+
post(block, pos, refSeq, clientId, startPos - pos, endPos - pos, accum),
|
|
2721
2778
|
);
|
|
2722
2779
|
}
|
|
2723
2780
|
}
|
package/src/mergeTreeNodes.ts
CHANGED
|
@@ -26,8 +26,11 @@ import {
|
|
|
26
26
|
refGetTileLabels,
|
|
27
27
|
refTypeIncludesFlag,
|
|
28
28
|
} from "./referencePositions.js";
|
|
29
|
+
// eslint-disable-next-line import/no-deprecated
|
|
29
30
|
import { SegmentGroupCollection } from "./segmentGroupCollection.js";
|
|
31
|
+
// eslint-disable-next-line import/no-deprecated
|
|
30
32
|
import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
33
|
+
import { Side } from "./sequencePlace.js";
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* Common properties for a node in a merge tree.
|
|
@@ -181,6 +184,10 @@ export function toMoveInfo(maybe: Partial<IMoveInfo> | undefined): IMoveInfo | u
|
|
|
181
184
|
*/
|
|
182
185
|
export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Partial<IMoveInfo> {
|
|
183
186
|
readonly type: string;
|
|
187
|
+
/**
|
|
188
|
+
* @deprecated - This property should not be used externally and will be removed in a subsequent release.
|
|
189
|
+
*/
|
|
190
|
+
// eslint-disable-next-line import/no-deprecated
|
|
184
191
|
readonly segmentGroups: SegmentGroupCollection;
|
|
185
192
|
readonly trackingCollection: TrackingGroupCollection;
|
|
186
193
|
/**
|
|
@@ -217,7 +224,10 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
|
|
|
217
224
|
|
|
218
225
|
/**
|
|
219
226
|
* Manages pending local state for properties on this segment.
|
|
227
|
+
*
|
|
228
|
+
* @deprecated - This property should not be used externally and will be removed in a subsequent release.
|
|
220
229
|
*/
|
|
230
|
+
// eslint-disable-next-line import/no-deprecated
|
|
221
231
|
propertyManager?: PropertiesManager;
|
|
222
232
|
/**
|
|
223
233
|
* Local seq at which this segment was inserted.
|
|
@@ -253,10 +263,20 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
|
|
|
253
263
|
* Properties that have been added to this segment via annotation.
|
|
254
264
|
*/
|
|
255
265
|
properties?: PropertySet;
|
|
266
|
+
/**
|
|
267
|
+
* Stores side information passed to obliterate for the start of a range.
|
|
268
|
+
*/
|
|
269
|
+
startSide?: Side.Before | Side.After;
|
|
270
|
+
/**
|
|
271
|
+
* Stores side information passed to obliterate for the end of a range.
|
|
272
|
+
*/
|
|
273
|
+
endSide?: Side.Before | Side.After;
|
|
256
274
|
|
|
257
275
|
/**
|
|
258
276
|
* Add properties to this segment via annotation.
|
|
259
277
|
*
|
|
278
|
+
* @deprecated - This function should not be used externally and will be removed in a subsequent release.
|
|
279
|
+
*
|
|
260
280
|
* @remarks This function should not be called directly. Properties should
|
|
261
281
|
* be added through the `annotateRange` functions.
|
|
262
282
|
*/
|
|
@@ -264,6 +284,7 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
|
|
|
264
284
|
newProps: PropertySet,
|
|
265
285
|
seq?: number,
|
|
266
286
|
collaborating?: boolean,
|
|
287
|
+
// eslint-disable-next-line import/no-deprecated
|
|
267
288
|
rollback?: PropertiesRollback,
|
|
268
289
|
): PropertySet;
|
|
269
290
|
clone(): ISegment;
|
|
@@ -274,6 +295,7 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
|
|
|
274
295
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
275
296
|
toJSONObject(): any;
|
|
276
297
|
/**
|
|
298
|
+
* @deprecated - This function should not be used externally and will be removed in a subsequent release.
|
|
277
299
|
* Acks the current segment against the segment group, op, and merge tree.
|
|
278
300
|
*
|
|
279
301
|
* @param segmentGroup - Pending segment group associated with this op.
|
|
@@ -499,12 +521,22 @@ export abstract class BaseSegment implements ISegment {
|
|
|
499
521
|
public ordinal: string = "";
|
|
500
522
|
public cachedLength: number = 0;
|
|
501
523
|
|
|
524
|
+
/**
|
|
525
|
+
* {@inheritdoc ISegment.segmentGroups}
|
|
526
|
+
* @deprecated - This property should not be used externally and will be removed in a subsequent release.
|
|
527
|
+
*/
|
|
528
|
+
// eslint-disable-next-line import/no-deprecated
|
|
502
529
|
public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
|
|
503
530
|
public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(
|
|
504
531
|
this,
|
|
505
532
|
);
|
|
506
533
|
/***/
|
|
507
534
|
public attribution?: IAttributionCollection<AttributionKey>;
|
|
535
|
+
/**
|
|
536
|
+
* {@inheritdoc ISegment.propertyManager}
|
|
537
|
+
* @deprecated - This property should not be used externally and will be removed in a subsequent release.
|
|
538
|
+
*/
|
|
539
|
+
// eslint-disable-next-line import/no-deprecated
|
|
508
540
|
public propertyManager?: PropertiesManager;
|
|
509
541
|
public properties?: PropertySet;
|
|
510
542
|
public localRefs?: LocalReferenceCollection;
|
|
@@ -513,12 +545,24 @@ export abstract class BaseSegment implements ISegment {
|
|
|
513
545
|
public localRemovedSeq?: number;
|
|
514
546
|
public localMovedSeq?: number;
|
|
515
547
|
|
|
548
|
+
public constructor(properties?: PropertySet) {
|
|
549
|
+
if (properties !== undefined) {
|
|
550
|
+
this.properties = clone(properties);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* {@inheritdoc ISegment.addProperties}
|
|
556
|
+
* @deprecated - This function should not be used externally and will be removed in a subsequent release.
|
|
557
|
+
*/
|
|
516
558
|
public addProperties(
|
|
517
559
|
newProps: PropertySet,
|
|
518
560
|
seq?: number,
|
|
519
561
|
collaborating?: boolean,
|
|
562
|
+
// eslint-disable-next-line import/no-deprecated
|
|
520
563
|
rollback: PropertiesRollback = PropertiesRollback.None,
|
|
521
564
|
): PropertySet {
|
|
565
|
+
// eslint-disable-next-line import/no-deprecated
|
|
522
566
|
this.propertyManager ??= new PropertiesManager();
|
|
523
567
|
// A property set must be able to hold properties of any type, so the any is needed.
|
|
524
568
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -569,6 +613,10 @@ export abstract class BaseSegment implements ISegment {
|
|
|
569
613
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
570
614
|
public abstract toJSONObject(): any;
|
|
571
615
|
|
|
616
|
+
/**
|
|
617
|
+
* {@inheritdoc ISegment.ack}
|
|
618
|
+
* @deprecated - This function should not be used externally and will be removed in a subsequent release.
|
|
619
|
+
*/
|
|
572
620
|
public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean {
|
|
573
621
|
const currentSegmentGroup = this.segmentGroups.dequeue();
|
|
574
622
|
assert(
|
|
@@ -676,13 +724,18 @@ export abstract class BaseSegment implements ISegment {
|
|
|
676
724
|
}
|
|
677
725
|
|
|
678
726
|
private copyPropertiesTo(other: ISegment): void {
|
|
679
|
-
if (this.
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
other.properties
|
|
684
|
-
|
|
685
|
-
|
|
727
|
+
if (this.properties !== undefined) {
|
|
728
|
+
if (this.propertyManager) {
|
|
729
|
+
// eslint-disable-next-line import/no-deprecated
|
|
730
|
+
other.propertyManager = new PropertiesManager();
|
|
731
|
+
other.properties = this.propertyManager.copyTo(
|
|
732
|
+
this.properties,
|
|
733
|
+
other.properties,
|
|
734
|
+
other.propertyManager,
|
|
735
|
+
);
|
|
736
|
+
} else {
|
|
737
|
+
other.properties = clone(this.properties);
|
|
738
|
+
}
|
|
686
739
|
}
|
|
687
740
|
}
|
|
688
741
|
|
|
@@ -755,15 +808,14 @@ export class Marker extends BaseSegment implements ReferencePosition, ISegment {
|
|
|
755
808
|
public readonly type = Marker.type;
|
|
756
809
|
|
|
757
810
|
public static make(refType: ReferenceType, props?: PropertySet): Marker {
|
|
758
|
-
|
|
759
|
-
if (props) {
|
|
760
|
-
marker.addProperties(props);
|
|
761
|
-
}
|
|
762
|
-
return marker;
|
|
811
|
+
return new Marker(refType, props);
|
|
763
812
|
}
|
|
764
813
|
|
|
765
|
-
constructor(
|
|
766
|
-
|
|
814
|
+
constructor(
|
|
815
|
+
public refType: ReferenceType,
|
|
816
|
+
props?: PropertySet,
|
|
817
|
+
) {
|
|
818
|
+
super(props);
|
|
767
819
|
this.cachedLength = 1;
|
|
768
820
|
}
|
|
769
821
|
|
package/src/properties.ts
CHANGED
|
@@ -69,17 +69,14 @@ export function matchProperties(
|
|
|
69
69
|
*/
|
|
70
70
|
export function extend<T>(base: MapLike<T>, extension: MapLike<T> | undefined): MapLike<T> {
|
|
71
71
|
if (extension !== undefined) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (v === null) {
|
|
72
|
+
for (const [key, v] of Object.entries(extension)) {
|
|
73
|
+
if (v === undefined) {
|
|
74
|
+
continue;
|
|
75
|
+
} else if (v === null) {
|
|
77
76
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
78
77
|
delete base[key];
|
|
79
78
|
} else {
|
|
80
|
-
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
82
|
-
base[key] = v!;
|
|
79
|
+
base[key] = v;
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
}
|
|
@@ -96,16 +93,7 @@ export function clone<T>(extension: MapLike<T> | undefined): MapLike<T> | undefi
|
|
|
96
93
|
return undefined;
|
|
97
94
|
}
|
|
98
95
|
const cloneMap = createMap<T>();
|
|
99
|
-
|
|
100
|
-
for (const key in extension) {
|
|
101
|
-
const v = extension[key];
|
|
102
|
-
if (v !== null) {
|
|
103
|
-
// If `v` is undefined, undefined must have been assignable to `T`.
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
105
|
-
cloneMap[key] = v!;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return cloneMap;
|
|
96
|
+
return extend(cloneMap, extension);
|
|
109
97
|
}
|
|
110
98
|
|
|
111
99
|
/**
|
|
@@ -118,10 +106,7 @@ export function addProperties(
|
|
|
118
106
|
oldProps: PropertySet | undefined,
|
|
119
107
|
newProps: PropertySet,
|
|
120
108
|
): PropertySet {
|
|
121
|
-
|
|
122
|
-
const _oldProps = oldProps ?? createMap<any>();
|
|
123
|
-
extend(_oldProps, newProps);
|
|
124
|
-
return { ..._oldProps };
|
|
109
|
+
return extend(oldProps ?? createMap<unknown>(), newProps);
|
|
125
110
|
}
|
|
126
111
|
|
|
127
112
|
/**
|
|
@@ -101,6 +101,12 @@ export interface ReferencePosition {
|
|
|
101
101
|
* @param newProps - Properties to add to this reference.
|
|
102
102
|
* @remarks Note that merge-tree does not broadcast changes to other clients. It is up to the consumer
|
|
103
103
|
* to ensure broadcast happens if that is desired.
|
|
104
|
+
*
|
|
105
|
+
* @deprecated - This function should not be used externally and will be removed in a subsequent release.
|
|
106
|
+
*
|
|
107
|
+
* @privateRemarks This interface is used by both marker segments and local reference positions. We will remove
|
|
108
|
+
* this function from segments, but keep it on local reference positions for now. So it has been added to local reference
|
|
109
|
+
* positions, and must be removed here to not apply to marker segments.
|
|
104
110
|
*/
|
|
105
111
|
addProperties(newProps: PropertySet): void;
|
|
106
112
|
isLeaf(): this is ISegment;
|
|
@@ -8,8 +8,11 @@ import { DoublyLinkedList, walkList } from "./collections/index.js";
|
|
|
8
8
|
import { ISegment, SegmentGroup } from "./mergeTreeNodes.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
+
* @deprecated - This class should not be used externally and will be removed in a subsequent release.
|
|
11
12
|
* @legacy
|
|
12
13
|
* @alpha
|
|
14
|
+
*
|
|
15
|
+
* @privateRemarks After the deprecation period this class should be remove from this package's exports, and only be used internally
|
|
13
16
|
*/
|
|
14
17
|
export class SegmentGroupCollection {
|
|
15
18
|
// eslint-disable-next-line import/no-deprecated
|
|
@@ -9,11 +9,15 @@ import { assert } from "@fluidframework/core-utils/internal";
|
|
|
9
9
|
|
|
10
10
|
import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants.js";
|
|
11
11
|
import { IMergeTreeAnnotateMsg } from "./ops.js";
|
|
12
|
-
import { MapLike, PropertySet, createMap } from "./properties.js";
|
|
12
|
+
import { MapLike, PropertySet, clone, createMap, extend } from "./properties.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @legacy
|
|
16
16
|
* @alpha
|
|
17
|
+
*
|
|
18
|
+
* @deprecated - This enum should not be used externally and will be removed in a subsequent release.
|
|
19
|
+
*
|
|
20
|
+
* @privateRemarks This enum should be made internal after the deprecation period
|
|
17
21
|
*/
|
|
18
22
|
export enum PropertiesRollback {
|
|
19
23
|
/**
|
|
@@ -30,6 +34,10 @@ export enum PropertiesRollback {
|
|
|
30
34
|
/**
|
|
31
35
|
* @legacy
|
|
32
36
|
* @alpha
|
|
37
|
+
*
|
|
38
|
+
* @deprecated - This class should not be used externally and will be removed in a subsequent release.
|
|
39
|
+
*
|
|
40
|
+
* @privateRemarks This class should be made internal after the deprecation period
|
|
33
41
|
*/
|
|
34
42
|
export class PropertiesManager {
|
|
35
43
|
private pendingKeyUpdateCount: MapLike<number> | undefined;
|
|
@@ -125,14 +133,10 @@ export class PropertiesManager {
|
|
|
125
133
|
if (!newManager) {
|
|
126
134
|
throw new Error("Must provide new PropertyManager");
|
|
127
135
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
newManager.pendingKeyUpdateCount = createMap<number>();
|
|
133
|
-
for (const key of Object.keys(this.pendingKeyUpdateCount!)) {
|
|
134
|
-
// TODO Non null asserting, why is this not null?
|
|
135
|
-
newManager.pendingKeyUpdateCount[key] = this.pendingKeyUpdateCount![key]!;
|
|
136
|
+
extend(newProps, oldProps);
|
|
137
|
+
|
|
138
|
+
if (this.pendingKeyUpdateCount) {
|
|
139
|
+
newManager.pendingKeyUpdateCount = clone(this.pendingKeyUpdateCount);
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
return newProps;
|