@fluidframework/sequence 0.59.4000-71130 → 1.0.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/.eslintrc.js +1 -1
- package/dist/intervalCollection.d.ts +21 -0
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +123 -37
- package/dist/intervalCollection.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/sequence.d.ts +2 -2
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +6 -6
- package/dist/sequence.js.map +1 -1
- package/dist/sharedIntervalCollection.js +1 -1
- package/dist/sharedIntervalCollection.js.map +1 -1
- package/lib/intervalCollection.d.ts +21 -0
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +124 -38
- package/lib/intervalCollection.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/sequence.d.ts +2 -2
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +7 -7
- package/lib/sequence.js.map +1 -1
- package/lib/sharedIntervalCollection.js +1 -1
- package/lib/sharedIntervalCollection.js.map +1 -1
- package/package.json +42 -43
- package/src/intervalCollection.ts +143 -45
- package/src/packageVersion.ts +1 -1
- package/src/sequence.ts +19 -17
- package/src/sharedIntervalCollection.ts +1 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
9
9
|
import { IEvent } from "@fluidframework/common-definitions";
|
|
10
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
10
11
|
import {
|
|
11
12
|
addProperties,
|
|
12
13
|
Client,
|
|
@@ -17,12 +18,14 @@ import {
|
|
|
17
18
|
IntervalConflictResolver,
|
|
18
19
|
IntervalNode,
|
|
19
20
|
IntervalTree,
|
|
21
|
+
ISegment,
|
|
20
22
|
LocalReference,
|
|
21
23
|
MergeTreeDeltaType,
|
|
22
24
|
PropertiesManager,
|
|
23
25
|
PropertySet,
|
|
24
26
|
RedBlackTree,
|
|
25
27
|
ReferenceType,
|
|
28
|
+
refTypeIncludesFlag,
|
|
26
29
|
reservedRangeLabelsKey,
|
|
27
30
|
UnassignedSequenceNumber,
|
|
28
31
|
} from "@fluidframework/merge-tree";
|
|
@@ -35,7 +38,17 @@ const reservedIntervalIdKey = "intervalId";
|
|
|
35
38
|
export enum IntervalType {
|
|
36
39
|
Simple = 0x0,
|
|
37
40
|
Nest = 0x1,
|
|
38
|
-
|
|
41
|
+
/**
|
|
42
|
+
* SlideOnRemove indicates that the ends of the interval will slide if the segment
|
|
43
|
+
* they reference is removed and acked.
|
|
44
|
+
* See `packages\dds\merge-tree\REFERENCEPOSITIONS.md` for details
|
|
45
|
+
* SlideOnRemove is the default interval behavior and does not need to be specified.
|
|
46
|
+
*/
|
|
47
|
+
SlideOnRemove = 0x2, // SlideOnRemove is default behavior - all intervals are SlideOnRemove
|
|
48
|
+
/**
|
|
49
|
+
* @internal
|
|
50
|
+
* A temporary interval, used internally
|
|
51
|
+
*/
|
|
39
52
|
Transient = 0x4,
|
|
40
53
|
}
|
|
41
54
|
|
|
@@ -310,20 +323,37 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
310
323
|
}
|
|
311
324
|
}
|
|
312
325
|
|
|
326
|
+
function createPositionReferenceFromSegoff(
|
|
327
|
+
client: Client,
|
|
328
|
+
segoff: { segment: ISegment | undefined; offset: number | undefined; },
|
|
329
|
+
refType: ReferenceType,
|
|
330
|
+
op?: ISequencedDocumentMessage): LocalReference {
|
|
331
|
+
if (segoff.segment) {
|
|
332
|
+
const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
|
|
333
|
+
return ref as LocalReference;
|
|
334
|
+
} else {
|
|
335
|
+
if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
|
|
336
|
+
throw new UsageError("Non-transient references need segment");
|
|
337
|
+
}
|
|
338
|
+
return new LocalReference(client, undefined, 0, refType);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
313
342
|
function createPositionReference(
|
|
314
343
|
client: Client,
|
|
315
344
|
pos: number,
|
|
316
345
|
refType: ReferenceType,
|
|
317
346
|
op?: ISequencedDocumentMessage): LocalReference {
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
347
|
+
let segoff;
|
|
348
|
+
if (op) {
|
|
349
|
+
assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
|
|
350
|
+
segoff = client.getContainingSegment(pos, op);
|
|
351
|
+
segoff = client.getSlideToSegment(segoff);
|
|
352
|
+
} else {
|
|
353
|
+
assert((refType & ReferenceType.SlideOnRemove) === 0, 0x2f6 /* SlideOnRemove references must be op created */);
|
|
354
|
+
segoff = client.getContainingSegment(pos);
|
|
325
355
|
}
|
|
326
|
-
return
|
|
356
|
+
return createPositionReferenceFromSegoff(client, segoff, refType, op);
|
|
327
357
|
}
|
|
328
358
|
|
|
329
359
|
function createSequenceInterval(
|
|
@@ -331,38 +361,42 @@ function createSequenceInterval(
|
|
|
331
361
|
start: number,
|
|
332
362
|
end: number,
|
|
333
363
|
client: Client,
|
|
334
|
-
intervalType
|
|
364
|
+
intervalType?: IntervalType,
|
|
335
365
|
op?: ISequencedDocumentMessage): SequenceInterval {
|
|
336
366
|
let beginRefType = ReferenceType.RangeBegin;
|
|
337
367
|
let endRefType = ReferenceType.RangeEnd;
|
|
338
|
-
if (intervalType === IntervalType.
|
|
339
|
-
beginRefType = ReferenceType.NestBegin;
|
|
340
|
-
endRefType = ReferenceType.NestEnd;
|
|
341
|
-
} else if (intervalType === IntervalType.Transient) {
|
|
368
|
+
if (intervalType === IntervalType.Transient) {
|
|
342
369
|
beginRefType = ReferenceType.Transient;
|
|
343
370
|
endRefType = ReferenceType.Transient;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
371
|
+
} else {
|
|
372
|
+
if (intervalType === IntervalType.Nest) {
|
|
373
|
+
beginRefType = ReferenceType.NestBegin;
|
|
374
|
+
endRefType = ReferenceType.NestEnd;
|
|
375
|
+
}
|
|
376
|
+
// All non-transient interval references must eventually be SlideOnRemove
|
|
377
|
+
// To ensure eventual consistency, they must start as StayOnRemove when
|
|
378
|
+
// pending (created locally and creation op is not acked)
|
|
379
|
+
if (op) {
|
|
380
|
+
beginRefType |= ReferenceType.SlideOnRemove;
|
|
381
|
+
endRefType |= ReferenceType.SlideOnRemove;
|
|
382
|
+
} else {
|
|
383
|
+
beginRefType |= ReferenceType.StayOnRemove;
|
|
384
|
+
endRefType |= ReferenceType.StayOnRemove;
|
|
385
|
+
}
|
|
350
386
|
}
|
|
351
387
|
|
|
352
388
|
const startLref = createPositionReference(client, start, beginRefType, op);
|
|
353
389
|
const endLref = createPositionReference(client, end, endRefType, op);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
endLref.addProperties(rangeProp);
|
|
390
|
+
startLref.pairedRef = endLref;
|
|
391
|
+
endLref.pairedRef = startLref;
|
|
392
|
+
const rangeProp = {
|
|
393
|
+
[reservedRangeLabelsKey]: [label],
|
|
394
|
+
};
|
|
395
|
+
startLref.addProperties(rangeProp);
|
|
396
|
+
endLref.addProperties(rangeProp);
|
|
362
397
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
398
|
+
const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
|
|
399
|
+
return ival;
|
|
366
400
|
}
|
|
367
401
|
|
|
368
402
|
export function defaultIntervalConflictResolver(a: Interval, b: Interval) {
|
|
@@ -520,20 +554,19 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
520
554
|
}
|
|
521
555
|
|
|
522
556
|
public findOverlappingIntervals(startPosition: number, endPosition: number) {
|
|
523
|
-
if (
|
|
524
|
-
const transientInterval =
|
|
525
|
-
this.helpers.create(
|
|
526
|
-
"transient",
|
|
527
|
-
startPosition,
|
|
528
|
-
endPosition,
|
|
529
|
-
this.client,
|
|
530
|
-
IntervalType.Transient);
|
|
531
|
-
|
|
532
|
-
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
533
|
-
return overlappingIntervalNodes.map((node) => node.key);
|
|
534
|
-
} else {
|
|
557
|
+
if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
|
|
535
558
|
return [];
|
|
536
559
|
}
|
|
560
|
+
const transientInterval =
|
|
561
|
+
this.helpers.create(
|
|
562
|
+
"transient",
|
|
563
|
+
startPosition,
|
|
564
|
+
endPosition,
|
|
565
|
+
this.client,
|
|
566
|
+
IntervalType.Transient);
|
|
567
|
+
|
|
568
|
+
const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
|
|
569
|
+
return overlappingIntervalNodes.map((node) => node.key);
|
|
537
570
|
}
|
|
538
571
|
|
|
539
572
|
public previousInterval(pos: number) {
|
|
@@ -872,6 +905,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
872
905
|
return this.localCollection.getIntervalById(id);
|
|
873
906
|
}
|
|
874
907
|
|
|
908
|
+
/**
|
|
909
|
+
* Create a new interval and add it to the collection
|
|
910
|
+
* @param start - interval start position
|
|
911
|
+
* @param end - interval end position
|
|
912
|
+
* @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
|
|
913
|
+
* @param props - properties of the interval
|
|
914
|
+
* @returns - the created interval
|
|
915
|
+
*/
|
|
875
916
|
public add(
|
|
876
917
|
start: number,
|
|
877
918
|
end: number,
|
|
@@ -881,6 +922,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
881
922
|
if (!this.attached) {
|
|
882
923
|
throw new Error("attach must be called prior to adding intervals");
|
|
883
924
|
}
|
|
925
|
+
if (intervalType & IntervalType.Transient) {
|
|
926
|
+
throw new Error("Can not add transient intervals");
|
|
927
|
+
}
|
|
884
928
|
|
|
885
929
|
const interval: TInterval = this.localCollection.addInterval(start, end, intervalType, props);
|
|
886
930
|
|
|
@@ -1064,6 +1108,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1064
1108
|
// This is an ack from the server. Remove the pending change.
|
|
1065
1109
|
this.removePendingChange(serializedInterval);
|
|
1066
1110
|
const id: string = serializedInterval.properties[reservedIntervalIdKey];
|
|
1111
|
+
// Could store the interval in the localOpMetadata to avoid the getIntervalById call
|
|
1067
1112
|
interval = this.getIntervalById(id);
|
|
1068
1113
|
if (interval) {
|
|
1069
1114
|
// Let the propertyManager prune its pending change-properties set.
|
|
@@ -1072,6 +1117,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1072
1117
|
type: MergeTreeDeltaType.ANNOTATE,
|
|
1073
1118
|
props: serializedInterval.properties,
|
|
1074
1119
|
});
|
|
1120
|
+
|
|
1121
|
+
this.ackInterval(interval, op);
|
|
1075
1122
|
}
|
|
1076
1123
|
} else {
|
|
1077
1124
|
// If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
|
|
@@ -1130,6 +1177,53 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1130
1177
|
});
|
|
1131
1178
|
}
|
|
1132
1179
|
|
|
1180
|
+
private getSlideToSegment(lref: LocalReference) {
|
|
1181
|
+
const segoff = { segment: lref.segment, offset: lref.offset };
|
|
1182
|
+
const newSegoff = this.client.getSlideToSegment(segoff);
|
|
1183
|
+
const value: { segment: ISegment | undefined; offset: number | undefined; } | undefined
|
|
1184
|
+
= (segoff === newSegoff) ? undefined : newSegoff;
|
|
1185
|
+
return value;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
private setSlideOnRemove(lref: LocalReference) {
|
|
1189
|
+
let refType = lref.refType;
|
|
1190
|
+
refType = refType & ~ReferenceType.StayOnRemove;
|
|
1191
|
+
refType = refType | ReferenceType.SlideOnRemove;
|
|
1192
|
+
lref.refType = refType;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
|
|
1196
|
+
// in current usage, interval is always a SequenceInterval
|
|
1197
|
+
if (!(interval instanceof SequenceInterval)) {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (!refTypeIncludesFlag(interval.start, ReferenceType.StayOnRemove)) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
assert(refTypeIncludesFlag(interval.end, ReferenceType.StayOnRemove),
|
|
1205
|
+
0x2f7 /* start and end must both be StayOnRemove */);
|
|
1206
|
+
const newStart = this.getSlideToSegment(interval.start);
|
|
1207
|
+
const newEnd = this.getSlideToSegment(interval.end);
|
|
1208
|
+
this.setSlideOnRemove(interval.start);
|
|
1209
|
+
this.setSlideOnRemove(interval.end);
|
|
1210
|
+
|
|
1211
|
+
if (newStart || newEnd) {
|
|
1212
|
+
this.localCollection.removeExistingInterval(interval);
|
|
1213
|
+
if (newStart) {
|
|
1214
|
+
const props = interval.start.properties;
|
|
1215
|
+
interval.start = createPositionReferenceFromSegoff(this.client, newStart, interval.start.refType, op);
|
|
1216
|
+
interval.start.addProperties(props);
|
|
1217
|
+
}
|
|
1218
|
+
if (newEnd) {
|
|
1219
|
+
const props = interval.end.properties;
|
|
1220
|
+
interval.end = createPositionReferenceFromSegoff(this.client, newEnd, interval.end.refType, op);
|
|
1221
|
+
interval.end.addProperties(props);
|
|
1222
|
+
}
|
|
1223
|
+
this.localCollection.add(interval);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1133
1227
|
/** @deprecated - use ackAdd */
|
|
1134
1228
|
public addInternal(
|
|
1135
1229
|
serializedInterval: ISerializedInterval,
|
|
@@ -1144,8 +1238,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1144
1238
|
local: boolean,
|
|
1145
1239
|
op: ISequencedDocumentMessage) {
|
|
1146
1240
|
if (local) {
|
|
1147
|
-
|
|
1148
|
-
//
|
|
1241
|
+
const id: string = serializedInterval.properties[reservedIntervalIdKey];
|
|
1242
|
+
// Could store the interval in the localOpMetadata to avoid the getIntervalById call
|
|
1243
|
+
const localInterval = this.getIntervalById(id);
|
|
1244
|
+
if (localInterval) {
|
|
1245
|
+
this.ackInterval(localInterval, op);
|
|
1246
|
+
}
|
|
1149
1247
|
return;
|
|
1150
1248
|
}
|
|
1151
1249
|
|
package/src/packageVersion.ts
CHANGED
package/src/sequence.ts
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
SummarySerializer,
|
|
49
49
|
} from "@fluidframework/shared-object-base";
|
|
50
50
|
import { IEventThisPlaceHolder } from "@fluidframework/common-definitions";
|
|
51
|
-
import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
|
|
51
|
+
import { ISummaryTreeWithStats, ITelemetryContext } from "@fluidframework/runtime-definitions";
|
|
52
52
|
|
|
53
53
|
import {
|
|
54
54
|
IntervalCollection,
|
|
@@ -173,7 +173,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
|
|
|
173
173
|
attributes: IChannelAttributes,
|
|
174
174
|
public readonly segmentFromSpec: (spec: IJSONSegment) => ISegment,
|
|
175
175
|
) {
|
|
176
|
-
super(id, dataStoreRuntime, attributes);
|
|
176
|
+
super(id, dataStoreRuntime, attributes, "fluid_sequence_");
|
|
177
177
|
|
|
178
178
|
this.loadedDeferred.promise.catch((error) => {
|
|
179
179
|
this.logger.sendErrorEvent({ eventName: "SequenceLoadFailed" }, error);
|
|
@@ -463,7 +463,10 @@ export abstract class SharedSegmentSequence<T extends ISegment>
|
|
|
463
463
|
return this.intervalCollections.keys();
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
protected summarizeCore(
|
|
466
|
+
protected summarizeCore(
|
|
467
|
+
serializer: IFluidSerializer,
|
|
468
|
+
telemetryContext?: ITelemetryContext,
|
|
469
|
+
): ISummaryTreeWithStats {
|
|
467
470
|
const builder = new SummaryTreeBuilder();
|
|
468
471
|
|
|
469
472
|
// conditionally write the interval collection blob
|
|
@@ -519,7 +522,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
|
|
|
519
522
|
this.client.startOrUpdateCollaboration(this.runtime.clientId);
|
|
520
523
|
}
|
|
521
524
|
|
|
522
|
-
protected onDisconnect() {}
|
|
525
|
+
protected onDisconnect() { }
|
|
523
526
|
|
|
524
527
|
protected reSubmitCore(content: any, localOpMetadata: unknown) {
|
|
525
528
|
if (!this.intervalCollections.trySubmitMessage(content, localOpMetadata as IMapMessageLocalMetadata)) {
|
|
@@ -559,18 +562,17 @@ export abstract class SharedSegmentSequence<T extends ISegment>
|
|
|
559
562
|
|| m.referenceSequenceNumber < collabWindow.minSeq
|
|
560
563
|
|| m.sequenceNumber <= collabWindow.minSeq
|
|
561
564
|
|| m.sequenceNumber <= collabWindow.currentSeq) {
|
|
562
|
-
throw new Error(`Invalid catchup operations in snapshot: ${
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
})}`);
|
|
565
|
+
throw new Error(`Invalid catchup operations in snapshot: ${JSON.stringify({
|
|
566
|
+
op: {
|
|
567
|
+
seq: m.sequenceNumber,
|
|
568
|
+
minSeq: m.minimumSequenceNumber,
|
|
569
|
+
refSeq: m.referenceSequenceNumber,
|
|
570
|
+
},
|
|
571
|
+
collabWindow: {
|
|
572
|
+
seq: collabWindow.currentSeq,
|
|
573
|
+
minSeq: collabWindow.minSeq,
|
|
574
|
+
},
|
|
575
|
+
})}`);
|
|
574
576
|
}
|
|
575
577
|
this.processMergeTreeMsg(m);
|
|
576
578
|
});
|
|
@@ -666,7 +668,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
|
|
|
666
668
|
// shallow clone the message as we only overwrite top level properties,
|
|
667
669
|
// like referenceSequenceNumber and content only
|
|
668
670
|
stashMessage = {
|
|
669
|
-
...
|
|
671
|
+
...message,
|
|
670
672
|
referenceSequenceNumber: stashMessage.sequenceNumber - 1,
|
|
671
673
|
contents: ops.length !== 1 ? createGroupOp(...ops) : ops[0],
|
|
672
674
|
};
|
|
@@ -117,7 +117,7 @@ export class SharedIntervalCollection
|
|
|
117
117
|
runtime: IFluidDataStoreRuntime,
|
|
118
118
|
attributes: IChannelAttributes,
|
|
119
119
|
) {
|
|
120
|
-
super(id, runtime, attributes);
|
|
120
|
+
super(id, runtime, attributes, "fluid_sharedIntervalCollection_");
|
|
121
121
|
this.intervalCollections = new DefaultMap(
|
|
122
122
|
this.serializer,
|
|
123
123
|
this.handle,
|