@fluidframework/sequence 1.3.0 → 2.0.0-dev.1.4.5.105745
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/.mocharc.js +12 -0
- package/README.md +19 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/intervalCollection.d.ts +51 -18
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +117 -73
- 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 +13 -22
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +11 -32
- package/dist/sequence.js.map +1 -1
- package/dist/sequenceDeltaEvent.d.ts +0 -6
- package/dist/sequenceDeltaEvent.d.ts.map +1 -1
- package/dist/sequenceDeltaEvent.js +0 -1
- package/dist/sequenceDeltaEvent.js.map +1 -1
- package/dist/sharedIntervalCollection.d.ts +5 -5
- package/dist/sharedIntervalCollection.js +5 -5
- package/dist/sharedIntervalCollection.js.map +1 -1
- package/dist/sharedString.d.ts +30 -1
- package/dist/sharedString.d.ts.map +1 -1
- package/dist/sharedString.js +40 -5
- package/dist/sharedString.js.map +1 -1
- package/dist/sparsematrix.d.ts +28 -15
- package/dist/sparsematrix.d.ts.map +1 -1
- package/dist/sparsematrix.js +24 -13
- package/dist/sparsematrix.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/intervalCollection.d.ts +51 -18
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +116 -73
- 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 +13 -22
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +12 -33
- package/lib/sequence.js.map +1 -1
- package/lib/sequenceDeltaEvent.d.ts +0 -6
- package/lib/sequenceDeltaEvent.d.ts.map +1 -1
- package/lib/sequenceDeltaEvent.js +0 -1
- package/lib/sequenceDeltaEvent.js.map +1 -1
- package/lib/sharedIntervalCollection.d.ts +5 -5
- package/lib/sharedIntervalCollection.js +5 -5
- package/lib/sharedIntervalCollection.js.map +1 -1
- package/lib/sharedString.d.ts +30 -1
- package/lib/sharedString.d.ts.map +1 -1
- package/lib/sharedString.js +38 -4
- package/lib/sharedString.js.map +1 -1
- package/lib/sparsematrix.d.ts +28 -15
- package/lib/sparsematrix.d.ts.map +1 -1
- package/lib/sparsematrix.js +24 -13
- package/lib/sparsematrix.js.map +1 -1
- package/package.json +70 -25
- package/src/index.ts +3 -1
- package/src/intervalCollection.ts +209 -97
- package/src/packageVersion.ts +1 -1
- package/src/sequence.ts +12 -41
- package/src/sequenceDeltaEvent.ts +0 -7
- package/src/sharedIntervalCollection.ts +5 -5
- package/src/sharedString.ts +44 -6
- package/src/sparsematrix.ts +48 -35
|
@@ -11,6 +11,7 @@ import { UsageError } from "@fluidframework/container-utils";
|
|
|
11
11
|
import {
|
|
12
12
|
addProperties,
|
|
13
13
|
Client,
|
|
14
|
+
compareReferencePositions,
|
|
14
15
|
ConflictAction,
|
|
15
16
|
createMap,
|
|
16
17
|
ICombiningOp,
|
|
@@ -19,15 +20,19 @@ import {
|
|
|
19
20
|
IntervalNode,
|
|
20
21
|
IntervalTree,
|
|
21
22
|
ISegment,
|
|
22
|
-
LocalReference,
|
|
23
23
|
MergeTreeDeltaType,
|
|
24
|
+
minReferencePosition,
|
|
24
25
|
PropertiesManager,
|
|
25
26
|
PropertySet,
|
|
26
27
|
RedBlackTree,
|
|
28
|
+
LocalReferencePosition,
|
|
27
29
|
ReferenceType,
|
|
28
30
|
refTypeIncludesFlag,
|
|
29
31
|
reservedRangeLabelsKey,
|
|
30
32
|
UnassignedSequenceNumber,
|
|
33
|
+
maxReferencePosition,
|
|
34
|
+
createDetachedLocalReferencePosition,
|
|
35
|
+
DetachedReferencePosition,
|
|
31
36
|
} from "@fluidframework/merge-tree";
|
|
32
37
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
33
38
|
import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
@@ -46,6 +51,7 @@ const reservedIntervalIdKey = "intervalId";
|
|
|
46
51
|
export enum IntervalType {
|
|
47
52
|
Simple = 0x0,
|
|
48
53
|
Nest = 0x1,
|
|
54
|
+
|
|
49
55
|
/**
|
|
50
56
|
* SlideOnRemove indicates that the ends of the interval will slide if the segment
|
|
51
57
|
* they reference is removed and acked.
|
|
@@ -53,9 +59,10 @@ export enum IntervalType {
|
|
|
53
59
|
* SlideOnRemove is the default interval behavior and does not need to be specified.
|
|
54
60
|
*/
|
|
55
61
|
SlideOnRemove = 0x2, // SlideOnRemove is default behavior - all intervals are SlideOnRemove
|
|
62
|
+
|
|
56
63
|
/**
|
|
57
|
-
* @internal
|
|
58
64
|
* A temporary interval, used internally
|
|
65
|
+
* @internal
|
|
59
66
|
*/
|
|
60
67
|
Transient = 0x4,
|
|
61
68
|
}
|
|
@@ -95,7 +102,7 @@ function decompressInterval(interval: CompressedSerializedInterval, label?: stri
|
|
|
95
102
|
end: interval[1],
|
|
96
103
|
sequenceNumber: interval[2],
|
|
97
104
|
intervalType: interval[3],
|
|
98
|
-
properties: { ...interval[4], [reservedRangeLabelsKey]: label },
|
|
105
|
+
properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
|
|
99
106
|
};
|
|
100
107
|
}
|
|
101
108
|
|
|
@@ -128,8 +135,26 @@ export interface ISerializableInterval extends IInterval {
|
|
|
128
135
|
|
|
129
136
|
export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
|
|
130
137
|
compareEnds(a: TInterval, b: TInterval): number;
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
* @param label - label of the interval collection this interval is being added to. This parameter is
|
|
141
|
+
* irrelevant for transient intervals.
|
|
142
|
+
* @param start - numerical start position of the interval
|
|
143
|
+
* @param end - numberical end position of the interval
|
|
144
|
+
* @param client - client creating the interval
|
|
145
|
+
* @param intervalType - Type of interval to create. Default is SlideOnRemove
|
|
146
|
+
* @param op - If this create came from a remote client, op that created it. Default is undefined (i.e. local)
|
|
147
|
+
* @param fromSnapshot - If this create came from loading a snapshot. Default is false.
|
|
148
|
+
*/
|
|
149
|
+
create(
|
|
150
|
+
label: string,
|
|
151
|
+
start: number,
|
|
152
|
+
end: number,
|
|
153
|
+
client: Client,
|
|
154
|
+
intervalType?: IntervalType,
|
|
155
|
+
op?: ISequencedDocumentMessage,
|
|
156
|
+
fromSnapshot?: boolean,
|
|
157
|
+
): TInterval;
|
|
133
158
|
}
|
|
134
159
|
|
|
135
160
|
export class Interval implements ISerializableInterval {
|
|
@@ -169,11 +194,7 @@ export class Interval implements ISerializableInterval {
|
|
|
169
194
|
}
|
|
170
195
|
|
|
171
196
|
public serialize(client: Client): ISerializedInterval {
|
|
172
|
-
|
|
173
|
-
if (client) {
|
|
174
|
-
seq = client.getCurrentSeq();
|
|
175
|
-
}
|
|
176
|
-
|
|
197
|
+
const seq = client?.getCurrentSeq() ?? 0;
|
|
177
198
|
const serializedInterval: ISerializedInterval = {
|
|
178
199
|
end: this.end,
|
|
179
200
|
intervalType: 0,
|
|
@@ -277,8 +298,9 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
277
298
|
public propertyManager: PropertiesManager;
|
|
278
299
|
|
|
279
300
|
constructor(
|
|
280
|
-
|
|
281
|
-
public
|
|
301
|
+
private readonly client: Client,
|
|
302
|
+
public start: LocalReferencePosition,
|
|
303
|
+
public end: LocalReferencePosition,
|
|
282
304
|
public intervalType: IntervalType,
|
|
283
305
|
props?: PropertySet,
|
|
284
306
|
) {
|
|
@@ -293,8 +315,8 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
293
315
|
private callbacks?: Record<"beforePositionChange" | "afterPositionChange", () => void>;
|
|
294
316
|
|
|
295
317
|
/**
|
|
296
|
-
* @internal
|
|
297
318
|
* Subscribes to position change events on this interval if there are no current listeners.
|
|
319
|
+
* @internal
|
|
298
320
|
*/
|
|
299
321
|
public addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void {
|
|
300
322
|
if (this.callbacks === undefined) {
|
|
@@ -311,8 +333,8 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
311
333
|
}
|
|
312
334
|
|
|
313
335
|
/**
|
|
314
|
-
* @internal
|
|
315
336
|
* Removes the currently subscribed position change listeners.
|
|
337
|
+
* @internal
|
|
316
338
|
*/
|
|
317
339
|
public removePositionChangeListeners(): void {
|
|
318
340
|
if (this.callbacks) {
|
|
@@ -323,8 +345,8 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
323
345
|
}
|
|
324
346
|
|
|
325
347
|
public serialize(client: Client): ISerializedInterval {
|
|
326
|
-
const startPosition = this.start
|
|
327
|
-
const endPosition = this.end
|
|
348
|
+
const startPosition = client.localReferencePositionToPosition(this.start);
|
|
349
|
+
const endPosition = client.localReferencePositionToPosition(this.end);
|
|
328
350
|
const serializedInterval: ISerializedInterval = {
|
|
329
351
|
end: endPosition,
|
|
330
352
|
intervalType: this.intervalType,
|
|
@@ -340,7 +362,7 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
340
362
|
}
|
|
341
363
|
|
|
342
364
|
public clone() {
|
|
343
|
-
return new SequenceInterval(this.start, this.end, this.intervalType, this.properties);
|
|
365
|
+
return new SequenceInterval(this.client, this.start, this.end, this.intervalType, this.properties);
|
|
344
366
|
}
|
|
345
367
|
|
|
346
368
|
public compare(b: SequenceInterval) {
|
|
@@ -366,16 +388,16 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
366
388
|
}
|
|
367
389
|
|
|
368
390
|
public compareStart(b: SequenceInterval) {
|
|
369
|
-
return this.start
|
|
391
|
+
return compareReferencePositions(this.start, b.start);
|
|
370
392
|
}
|
|
371
393
|
|
|
372
394
|
public compareEnd(b: SequenceInterval) {
|
|
373
|
-
return this.end
|
|
395
|
+
return compareReferencePositions(this.end, b.end);
|
|
374
396
|
}
|
|
375
397
|
|
|
376
398
|
public overlaps(b: SequenceInterval) {
|
|
377
|
-
const result = (this.start
|
|
378
|
-
(this.end
|
|
399
|
+
const result = (compareReferencePositions(this.start, b.end) <= 0) &&
|
|
400
|
+
(compareReferencePositions(this.end, b.start) >= 0);
|
|
379
401
|
return result;
|
|
380
402
|
}
|
|
381
403
|
|
|
@@ -388,8 +410,8 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
388
410
|
}
|
|
389
411
|
|
|
390
412
|
public union(b: SequenceInterval) {
|
|
391
|
-
return new SequenceInterval(this.start
|
|
392
|
-
this.end
|
|
413
|
+
return new SequenceInterval(this.client, minReferencePosition(this.start, b.start),
|
|
414
|
+
maxReferencePosition(this.end, b.end), this.intervalType);
|
|
393
415
|
}
|
|
394
416
|
|
|
395
417
|
public addProperties(
|
|
@@ -403,12 +425,12 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
403
425
|
}
|
|
404
426
|
|
|
405
427
|
public overlapsPos(bstart: number, bend: number) {
|
|
406
|
-
const startPos = this.
|
|
407
|
-
const endPos = this.
|
|
428
|
+
const startPos = this.client.localReferencePositionToPosition(this.start);
|
|
429
|
+
const endPos = this.client.localReferencePositionToPosition(this.end);
|
|
408
430
|
return (endPos > bstart) && (startPos < bend);
|
|
409
431
|
}
|
|
410
432
|
|
|
411
|
-
public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
|
|
433
|
+
public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage, localSeq?: number) {
|
|
412
434
|
const getRefType = (baseType: ReferenceType): ReferenceType => {
|
|
413
435
|
let refType = baseType;
|
|
414
436
|
if (op === undefined) {
|
|
@@ -420,20 +442,21 @@ export class SequenceInterval implements ISerializableInterval {
|
|
|
420
442
|
|
|
421
443
|
let startRef = this.start;
|
|
422
444
|
if (start !== undefined) {
|
|
423
|
-
startRef = createPositionReference(
|
|
445
|
+
startRef = createPositionReference(
|
|
446
|
+
this.client, start, getRefType(this.start.refType), op, undefined, localSeq,
|
|
447
|
+
);
|
|
424
448
|
startRef.addProperties(this.start.properties);
|
|
425
449
|
}
|
|
426
450
|
|
|
427
451
|
let endRef = this.end;
|
|
428
452
|
if (end !== undefined) {
|
|
429
|
-
endRef = createPositionReference(
|
|
453
|
+
endRef = createPositionReference(
|
|
454
|
+
this.client, end, getRefType(this.end.refType), op, undefined, localSeq,
|
|
455
|
+
);
|
|
430
456
|
endRef.addProperties(this.end.properties);
|
|
431
457
|
}
|
|
432
458
|
|
|
433
|
-
|
|
434
|
-
endRef.pairedRef = startRef;
|
|
435
|
-
|
|
436
|
-
const newInterval = new SequenceInterval(startRef, endRef, this.intervalType);
|
|
459
|
+
const newInterval = new SequenceInterval(this.client, startRef, endRef, this.intervalType);
|
|
437
460
|
if (this.properties) {
|
|
438
461
|
newInterval.initializeProperties();
|
|
439
462
|
this.propertyManager.copyTo(this.properties, newInterval.properties, newInterval.propertyManager);
|
|
@@ -455,31 +478,37 @@ function createPositionReferenceFromSegoff(
|
|
|
455
478
|
client: Client,
|
|
456
479
|
segoff: { segment: ISegment | undefined; offset: number | undefined; },
|
|
457
480
|
refType: ReferenceType,
|
|
458
|
-
op?: ISequencedDocumentMessage):
|
|
481
|
+
op?: ISequencedDocumentMessage): LocalReferencePosition {
|
|
459
482
|
if (segoff.segment) {
|
|
460
483
|
const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
|
|
461
|
-
return ref
|
|
462
|
-
} else {
|
|
463
|
-
if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
|
|
464
|
-
throw new UsageError("Non-transient references need segment");
|
|
465
|
-
}
|
|
466
|
-
return new LocalReference(client, undefined, 0, refType);
|
|
484
|
+
return ref;
|
|
467
485
|
}
|
|
486
|
+
|
|
487
|
+
if (!op && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
|
|
488
|
+
// reference to segment that dne locally
|
|
489
|
+
throw new UsageError("Non-transient references need segment");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return createDetachedLocalReferencePosition(refType);
|
|
468
493
|
}
|
|
469
494
|
|
|
470
495
|
function createPositionReference(
|
|
471
496
|
client: Client,
|
|
472
497
|
pos: number,
|
|
473
498
|
refType: ReferenceType,
|
|
474
|
-
op?: ISequencedDocumentMessage
|
|
499
|
+
op?: ISequencedDocumentMessage,
|
|
500
|
+
fromSnapshot?: boolean,
|
|
501
|
+
localSeq?: number,
|
|
502
|
+
): LocalReferencePosition {
|
|
475
503
|
let segoff;
|
|
476
504
|
if (op) {
|
|
477
505
|
assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
|
|
478
506
|
segoff = client.getContainingSegment(pos, op);
|
|
479
507
|
segoff = client.getSlideToSegment(segoff);
|
|
480
508
|
} else {
|
|
481
|
-
assert((refType & ReferenceType.SlideOnRemove) === 0
|
|
482
|
-
|
|
509
|
+
assert((refType & ReferenceType.SlideOnRemove) === 0 || fromSnapshot,
|
|
510
|
+
0x2f6 /* SlideOnRemove references must be op created */);
|
|
511
|
+
segoff = client.getContainingSegment(pos, undefined, localSeq);
|
|
483
512
|
}
|
|
484
513
|
return createPositionReferenceFromSegoff(client, segoff, refType, op);
|
|
485
514
|
}
|
|
@@ -490,7 +519,8 @@ function createSequenceInterval(
|
|
|
490
519
|
end: number,
|
|
491
520
|
client: Client,
|
|
492
521
|
intervalType?: IntervalType,
|
|
493
|
-
op?: ISequencedDocumentMessage
|
|
522
|
+
op?: ISequencedDocumentMessage,
|
|
523
|
+
fromSnapshot?: boolean): SequenceInterval {
|
|
494
524
|
let beginRefType = ReferenceType.RangeBegin;
|
|
495
525
|
let endRefType = ReferenceType.RangeEnd;
|
|
496
526
|
if (intervalType === IntervalType.Transient) {
|
|
@@ -504,7 +534,7 @@ function createSequenceInterval(
|
|
|
504
534
|
// All non-transient interval references must eventually be SlideOnRemove
|
|
505
535
|
// To ensure eventual consistency, they must start as StayOnRemove when
|
|
506
536
|
// pending (created locally and creation op is not acked)
|
|
507
|
-
if (op) {
|
|
537
|
+
if (op || fromSnapshot) {
|
|
508
538
|
beginRefType |= ReferenceType.SlideOnRemove;
|
|
509
539
|
endRefType |= ReferenceType.SlideOnRemove;
|
|
510
540
|
} else {
|
|
@@ -513,17 +543,15 @@ function createSequenceInterval(
|
|
|
513
543
|
}
|
|
514
544
|
}
|
|
515
545
|
|
|
516
|
-
const startLref = createPositionReference(client, start, beginRefType, op);
|
|
517
|
-
const endLref = createPositionReference(client, end, endRefType, op);
|
|
518
|
-
startLref.pairedRef = endLref;
|
|
519
|
-
endLref.pairedRef = startLref;
|
|
546
|
+
const startLref = createPositionReference(client, start, beginRefType, op, fromSnapshot);
|
|
547
|
+
const endLref = createPositionReference(client, end, endRefType, op, fromSnapshot);
|
|
520
548
|
const rangeProp = {
|
|
521
549
|
[reservedRangeLabelsKey]: [label],
|
|
522
550
|
};
|
|
523
551
|
startLref.addProperties(rangeProp);
|
|
524
552
|
endLref.addProperties(rangeProp);
|
|
525
553
|
|
|
526
|
-
const ival = new SequenceInterval(startLref, endLref, intervalType, rangeProp);
|
|
554
|
+
const ival = new SequenceInterval(client, startLref, endLref, intervalType, rangeProp);
|
|
527
555
|
return ival;
|
|
528
556
|
}
|
|
529
557
|
|
|
@@ -772,18 +800,23 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
772
800
|
if (!interval.properties) {
|
|
773
801
|
interval.properties = createMap<any>();
|
|
774
802
|
}
|
|
803
|
+
|
|
775
804
|
if (props) {
|
|
776
805
|
interval.addProperties(props);
|
|
777
806
|
}
|
|
778
|
-
|
|
779
|
-
// Create a new ID.
|
|
780
|
-
interval.properties[reservedIntervalIdKey] = uuid();
|
|
781
|
-
}
|
|
807
|
+
interval.properties[reservedIntervalIdKey] ??= uuid();
|
|
782
808
|
this.add(interval);
|
|
783
809
|
}
|
|
784
810
|
return interval;
|
|
785
811
|
}
|
|
786
812
|
|
|
813
|
+
private linkEndpointsToInterval(interval: TInterval): void {
|
|
814
|
+
if (interval instanceof SequenceInterval) {
|
|
815
|
+
interval.start.addProperties({ interval });
|
|
816
|
+
interval.end.addProperties({ interval });
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
787
820
|
private addIntervalToIndex(interval: TInterval) {
|
|
788
821
|
const id = interval.getIntervalId();
|
|
789
822
|
assert(id !== undefined, 0x2c0 /* "ID must be created before adding interval to collection" */);
|
|
@@ -799,6 +832,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
799
832
|
}
|
|
800
833
|
|
|
801
834
|
public add(interval: TInterval) {
|
|
835
|
+
this.linkEndpointsToInterval(interval);
|
|
802
836
|
this.addIntervalToIndex(interval);
|
|
803
837
|
this.addIntervalListeners(interval);
|
|
804
838
|
}
|
|
@@ -807,8 +841,14 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
807
841
|
return this.intervalIdMap.get(id);
|
|
808
842
|
}
|
|
809
843
|
|
|
810
|
-
public changeInterval(
|
|
811
|
-
|
|
844
|
+
public changeInterval(
|
|
845
|
+
interval: TInterval,
|
|
846
|
+
start: number,
|
|
847
|
+
end: number,
|
|
848
|
+
op?: ISequencedDocumentMessage,
|
|
849
|
+
localSeq?: number,
|
|
850
|
+
) {
|
|
851
|
+
const newInterval = interval.modify(this.label, start, end, op, localSeq) as TInterval | undefined;
|
|
812
852
|
if (newInterval) {
|
|
813
853
|
this.removeExistingInterval(interval);
|
|
814
854
|
this.add(newInterval);
|
|
@@ -846,7 +886,8 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
846
886
|
}
|
|
847
887
|
}
|
|
848
888
|
|
|
849
|
-
const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
|
|
889
|
+
const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
|
|
890
|
+
compareReferencePositions(a.end, b.end);
|
|
850
891
|
|
|
851
892
|
class SequenceIntervalCollectionFactory
|
|
852
893
|
implements IValueFactory<IntervalCollection<SequenceInterval>> {
|
|
@@ -958,6 +999,11 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
|
|
|
958
999
|
"add",
|
|
959
1000
|
{
|
|
960
1001
|
process: (collection, params, local, op) => {
|
|
1002
|
+
// if params is undefined, the interval was deleted during
|
|
1003
|
+
// rebasing
|
|
1004
|
+
if (!params) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
961
1007
|
collection.ackAdd(params, local, op);
|
|
962
1008
|
},
|
|
963
1009
|
rebase,
|
|
@@ -979,6 +1025,11 @@ function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValueOperat
|
|
|
979
1025
|
"change",
|
|
980
1026
|
{
|
|
981
1027
|
process: (collection, params, local, op) => {
|
|
1028
|
+
// if params is undefined, the interval was deleted during
|
|
1029
|
+
// rebasing
|
|
1030
|
+
if (!params) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
982
1033
|
collection.ackChange(params, local, op);
|
|
983
1034
|
},
|
|
984
1035
|
rebase,
|
|
@@ -1041,7 +1092,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1041
1092
|
private savedSerializedIntervals?: ISerializedInterval[];
|
|
1042
1093
|
private localCollection: LocalIntervalCollection<TInterval>;
|
|
1043
1094
|
private onDeserialize: DeserializeCallback | undefined;
|
|
1044
|
-
private client: Client;
|
|
1095
|
+
private client: Client | undefined;
|
|
1045
1096
|
private readonly pendingChangesStart: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
|
|
1046
1097
|
private readonly pendingChangesEnd: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
|
|
1047
1098
|
|
|
@@ -1058,12 +1109,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1058
1109
|
) {
|
|
1059
1110
|
super();
|
|
1060
1111
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
this.savedSerializedIntervals =
|
|
1065
|
-
serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
|
|
1066
|
-
}
|
|
1112
|
+
this.savedSerializedIntervals = Array.isArray(serializedIntervals)
|
|
1113
|
+
? serializedIntervals
|
|
1114
|
+
: serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
|
|
1067
1115
|
}
|
|
1068
1116
|
|
|
1069
1117
|
public attachGraph(client: Client, label: string) {
|
|
@@ -1086,11 +1134,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1086
1134
|
if (this.savedSerializedIntervals) {
|
|
1087
1135
|
for (const serializedInterval of this.savedSerializedIntervals) {
|
|
1088
1136
|
this.localCollection.ensureSerializedId(serializedInterval);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1137
|
+
const { start, end, intervalType, properties } = serializedInterval;
|
|
1138
|
+
const interval = this.helpers.create(
|
|
1139
|
+
label,
|
|
1140
|
+
start,
|
|
1141
|
+
end,
|
|
1142
|
+
client,
|
|
1143
|
+
intervalType,
|
|
1144
|
+
undefined,
|
|
1145
|
+
true,
|
|
1146
|
+
);
|
|
1147
|
+
interval.addProperties(properties);
|
|
1148
|
+
this.localCollection.add(interval);
|
|
1094
1149
|
}
|
|
1095
1150
|
}
|
|
1096
1151
|
this.savedSerializedIntervals = undefined;
|
|
@@ -1100,7 +1155,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1100
1155
|
* Gets the next local sequence number, modifying this client's collab window in doing so.
|
|
1101
1156
|
*/
|
|
1102
1157
|
private getNextLocalSeq(): number {
|
|
1103
|
-
|
|
1158
|
+
if (this.client) {
|
|
1159
|
+
return ++this.client.getCollabWindow().localSeq;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
return 0;
|
|
1104
1163
|
}
|
|
1105
1164
|
|
|
1106
1165
|
public getIntervalById(id: string) {
|
|
@@ -1302,11 +1361,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1302
1361
|
return entries && entries.length !== 0;
|
|
1303
1362
|
}
|
|
1304
1363
|
|
|
1305
|
-
/** @deprecated - use ackChange */
|
|
1306
|
-
public changeInterval(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
|
|
1307
|
-
return this.ackChange(serializedInterval, local, op);
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
1364
|
/** @internal */
|
|
1311
1365
|
public ackChange(serializedInterval: ISerializedInterval, local: boolean, op: ISequencedDocumentMessage) {
|
|
1312
1366
|
if (!this.attached) {
|
|
@@ -1387,12 +1441,22 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1387
1441
|
});
|
|
1388
1442
|
}
|
|
1389
1443
|
|
|
1390
|
-
/**
|
|
1444
|
+
/**
|
|
1445
|
+
* Returns new interval after rebasing. If undefined, the interval was
|
|
1446
|
+
* deleted as a result of rebasing. This can occur if the interval applies
|
|
1447
|
+
* to a range that no longer exists, and the interval was unable to slide.
|
|
1448
|
+
*
|
|
1449
|
+
* @internal
|
|
1450
|
+
*/
|
|
1391
1451
|
public rebaseLocalInterval(
|
|
1392
1452
|
opName: string,
|
|
1393
1453
|
serializedInterval: ISerializedInterval,
|
|
1394
1454
|
localSeq: number,
|
|
1395
|
-
) {
|
|
1455
|
+
): ISerializedInterval | undefined {
|
|
1456
|
+
if (!this.client) {
|
|
1457
|
+
// If there's no associated mergeTree client, the originally submitted op is still correct.
|
|
1458
|
+
return serializedInterval;
|
|
1459
|
+
}
|
|
1396
1460
|
if (!this.attached) {
|
|
1397
1461
|
throw new LoggingError("attachSequence must be called");
|
|
1398
1462
|
}
|
|
@@ -1404,6 +1468,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1404
1468
|
this.client.rebasePosition(end, sequenceNumber, localSeq);
|
|
1405
1469
|
|
|
1406
1470
|
const intervalId = properties?.[reservedIntervalIdKey];
|
|
1471
|
+
const localInterval = this.localCollection.getIntervalById(intervalId);
|
|
1472
|
+
|
|
1407
1473
|
const rebased: ISerializedInterval = {
|
|
1408
1474
|
start: startRebased,
|
|
1409
1475
|
end: endRebased,
|
|
@@ -1411,22 +1477,56 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1411
1477
|
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1412
1478
|
properties,
|
|
1413
1479
|
};
|
|
1480
|
+
|
|
1414
1481
|
if (opName === "change" && (this.hasPendingChangeStart(intervalId) || this.hasPendingChangeEnd(intervalId))) {
|
|
1415
1482
|
this.removePendingChange(serializedInterval);
|
|
1416
1483
|
this.addPendingChange(intervalId, rebased);
|
|
1417
1484
|
}
|
|
1485
|
+
|
|
1486
|
+
// if the interval slid off the string, rebase the op to be a noop and
|
|
1487
|
+
// delete the interval
|
|
1488
|
+
if (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition) {
|
|
1489
|
+
if (localInterval) {
|
|
1490
|
+
this.localCollection.removeExistingInterval(localInterval);
|
|
1491
|
+
}
|
|
1492
|
+
return undefined;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (!localInterval) {
|
|
1496
|
+
return rebased;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// we know we must be using `SequenceInterval` because `this.client` exists
|
|
1500
|
+
assert(
|
|
1501
|
+
localInterval instanceof SequenceInterval,
|
|
1502
|
+
0x3a0 /* localInterval must be `SequenceInterval` when used with client */,
|
|
1503
|
+
);
|
|
1504
|
+
|
|
1505
|
+
const startSegment = this.getSlideToSegment(localInterval.start);
|
|
1506
|
+
const endSegment = this.getSlideToSegment(localInterval.end);
|
|
1507
|
+
|
|
1508
|
+
// we need to slide because the reference has been removed
|
|
1509
|
+
if (startSegment || endSegment) {
|
|
1510
|
+
const newStart =
|
|
1511
|
+
startSegment && this.client.getPosition(startSegment.segment, localSeq) + startSegment.offset;
|
|
1512
|
+
const newEnd =
|
|
1513
|
+
endSegment && this.client.getPosition(endSegment.segment, localSeq) + endSegment.offset;
|
|
1514
|
+
|
|
1515
|
+
this.localCollection.changeInterval(localInterval, newStart, newEnd, undefined, localSeq);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1418
1518
|
return rebased;
|
|
1419
1519
|
}
|
|
1420
1520
|
|
|
1421
|
-
private getSlideToSegment(lref:
|
|
1422
|
-
const segoff = { segment: lref.
|
|
1521
|
+
private getSlideToSegment(lref: LocalReferencePosition) {
|
|
1522
|
+
const segoff = { segment: lref.getSegment(), offset: lref.getOffset() };
|
|
1423
1523
|
const newSegoff = this.client.getSlideToSegment(segoff);
|
|
1424
1524
|
const value: { segment: ISegment | undefined; offset: number | undefined; } | undefined
|
|
1425
1525
|
= (segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset) ? undefined : newSegoff;
|
|
1426
1526
|
return value;
|
|
1427
1527
|
}
|
|
1428
1528
|
|
|
1429
|
-
private setSlideOnRemove(lref:
|
|
1529
|
+
private setSlideOnRemove(lref: LocalReferencePosition) {
|
|
1430
1530
|
let refType = lref.refType;
|
|
1431
1531
|
refType = refType & ~ReferenceType.StayOnRemove;
|
|
1432
1532
|
refType = refType | ReferenceType.SlideOnRemove;
|
|
@@ -1434,7 +1534,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1434
1534
|
}
|
|
1435
1535
|
|
|
1436
1536
|
private ackInterval(interval: TInterval, op: ISequencedDocumentMessage) {
|
|
1437
|
-
//
|
|
1537
|
+
// Only SequenceIntervals need potential sliding
|
|
1438
1538
|
if (!(interval instanceof SequenceInterval)) {
|
|
1439
1539
|
return;
|
|
1440
1540
|
}
|
|
@@ -1465,7 +1565,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1465
1565
|
if (needsStartUpdate || needsEndUpdate) {
|
|
1466
1566
|
// In this case, where we change the start or end of an interval,
|
|
1467
1567
|
// it is necessary to remove and re-add the interval listeners.
|
|
1468
|
-
// This ensures that the correct listeners are added to the
|
|
1568
|
+
// This ensures that the correct listeners are added to the LocalReferencePosition.
|
|
1469
1569
|
this.localCollection.removeExistingInterval(interval);
|
|
1470
1570
|
|
|
1471
1571
|
if (needsStartUpdate) {
|
|
@@ -1488,14 +1588,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1488
1588
|
}
|
|
1489
1589
|
}
|
|
1490
1590
|
|
|
1491
|
-
/** @deprecated - use ackAdd */
|
|
1492
|
-
public addInternal(
|
|
1493
|
-
serializedInterval: ISerializedInterval,
|
|
1494
|
-
local: boolean,
|
|
1495
|
-
op: ISequencedDocumentMessage) {
|
|
1496
|
-
return this.ackAdd(serializedInterval, local, op);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
1591
|
/** @internal */
|
|
1500
1592
|
public ackAdd(
|
|
1501
1593
|
serializedInterval: ISerializedInterval,
|
|
@@ -1534,14 +1626,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1534
1626
|
return interval;
|
|
1535
1627
|
}
|
|
1536
1628
|
|
|
1537
|
-
/** @deprecated - use ackDelete */
|
|
1538
|
-
public deleteInterval(
|
|
1539
|
-
serializedInterval: ISerializedInterval,
|
|
1540
|
-
local: boolean,
|
|
1541
|
-
op: ISequencedDocumentMessage): void {
|
|
1542
|
-
return this.ackDelete(serializedInterval, local, op);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
1629
|
/** @internal */
|
|
1546
1630
|
public ackDelete(
|
|
1547
1631
|
serializedInterval: ISerializedInterval,
|
|
@@ -1645,3 +1729,31 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1645
1729
|
return this.localCollection.nextInterval(pos);
|
|
1646
1730
|
}
|
|
1647
1731
|
}
|
|
1732
|
+
|
|
1733
|
+
/**
|
|
1734
|
+
* Information that identifies an interval within a `Sequence`.
|
|
1735
|
+
*/
|
|
1736
|
+
export interface IntervalLocator {
|
|
1737
|
+
/**
|
|
1738
|
+
* Label for the collection the interval is a part of
|
|
1739
|
+
*/
|
|
1740
|
+
label: string;
|
|
1741
|
+
/**
|
|
1742
|
+
* Interval within that collection
|
|
1743
|
+
*/
|
|
1744
|
+
interval: SequenceInterval;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* Returns an object that can be used to find the interval a given LocalReferencePosition belongs to.
|
|
1749
|
+
* @returns undefined if the reference position is not the endpoint of any interval (e.g. it was created
|
|
1750
|
+
* on the merge tree directly by app code), otherwise an {@link IntervalLocator} for the interval this
|
|
1751
|
+
* endpoint is a part of.
|
|
1752
|
+
*/
|
|
1753
|
+
export function intervalLocatorFromEndpoint(potentialEndpoint: LocalReferencePosition): IntervalLocator | undefined {
|
|
1754
|
+
const {
|
|
1755
|
+
interval,
|
|
1756
|
+
[reservedRangeLabelsKey]: collectionNameArray,
|
|
1757
|
+
} = potentialEndpoint.properties ?? {};
|
|
1758
|
+
return (interval && collectionNameArray?.length === 1) ? { label: collectionNameArray[0], interval } : undefined;
|
|
1759
|
+
}
|
package/src/packageVersion.ts
CHANGED