@fluidframework/sequence 2.50.0-345060 → 2.51.0-347100
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/intervalCollection.d.ts +4 -5
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +94 -95
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalCollectionMapInterfaces.d.ts +1 -11
- package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -1
- package/dist/intervalCollectionMapInterfaces.js.map +1 -1
- package/dist/intervals/sequenceInterval.d.ts +7 -3
- package/dist/intervals/sequenceInterval.d.ts.map +1 -1
- package/dist/intervals/sequenceInterval.js +72 -30
- package/dist/intervals/sequenceInterval.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/intervalCollection.d.ts +4 -5
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +94 -95
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalCollectionMapInterfaces.d.ts +1 -11
- package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
- package/lib/intervalCollectionMapInterfaces.js.map +1 -1
- package/lib/intervals/sequenceInterval.d.ts +7 -3
- package/lib/intervals/sequenceInterval.d.ts.map +1 -1
- package/lib/intervals/sequenceInterval.js +74 -32
- package/lib/intervals/sequenceInterval.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +18 -19
- package/src/intervalCollection.ts +106 -103
- package/src/intervalCollectionMapInterfaces.ts +1 -9
- package/src/intervals/sequenceInterval.ts +88 -32
- package/src/packageVersion.ts +1 -1
|
@@ -99,7 +99,7 @@ export class LocalIntervalCollection {
|
|
|
99
99
|
this.removeIntervalFromIndexes(interval);
|
|
100
100
|
this.removeIntervalListeners(interval);
|
|
101
101
|
}
|
|
102
|
-
addInterval(id, start, end, props, op
|
|
102
|
+
addInterval(id, start, end, props, op) {
|
|
103
103
|
// This check is intended to prevent scenarios where a random interval is created and then
|
|
104
104
|
// inserted into a collection. The aim is to ensure that the collection is created first
|
|
105
105
|
// then the user can create/add intervals based on the collection
|
|
@@ -107,7 +107,7 @@ export class LocalIntervalCollection {
|
|
|
107
107
|
props[reservedRangeLabelsKey][0] !== this.label) {
|
|
108
108
|
throw new LoggingError("Adding an interval that belongs to another interval collection is not permitted");
|
|
109
109
|
}
|
|
110
|
-
const interval = createSequenceInterval(this.label, id, start, end, this.client, IntervalType.SlideOnRemove, op, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint, props,
|
|
110
|
+
const interval = createSequenceInterval(this.label, id, start, end, this.client, IntervalType.SlideOnRemove, op, undefined, this.options.mergeTreeReferencesCanSlideToEndpoint, props, false);
|
|
111
111
|
this.add(interval);
|
|
112
112
|
return interval;
|
|
113
113
|
}
|
|
@@ -207,12 +207,12 @@ function removeMetadataFromPendingChanges(localOpMetadataNode) {
|
|
|
207
207
|
}
|
|
208
208
|
function clearEmptyPendingEntry(pendingChanges, id) {
|
|
209
209
|
const pending = pendingChanges[id];
|
|
210
|
-
|
|
211
|
-
if (pending.local.empty) {
|
|
210
|
+
if (pending !== undefined && pending.local.empty) {
|
|
212
211
|
assert(pending.endpointChanges?.empty !== false, 0xbc0 /* endpointChanges must be empty if not pending changes */);
|
|
213
212
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
214
213
|
delete pendingChanges[id];
|
|
215
214
|
}
|
|
215
|
+
return pending;
|
|
216
216
|
}
|
|
217
217
|
function hasEndpointChanges(serialized) {
|
|
218
218
|
return serialized.start !== undefined && serialized.end !== undefined;
|
|
@@ -228,15 +228,15 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
228
228
|
super();
|
|
229
229
|
this.options = options;
|
|
230
230
|
this.pending = {};
|
|
231
|
-
this.submitDelta = (op, md) => {
|
|
231
|
+
this.submitDelta = (op, md, consensus) => {
|
|
232
232
|
const { id } = getSerializedProperties(op.value);
|
|
233
233
|
const pending = (this.pending[id] ??= {
|
|
234
234
|
local: new DoublyLinkedList(),
|
|
235
|
+
consensus,
|
|
235
236
|
});
|
|
236
237
|
if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
|
|
237
238
|
const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
|
|
238
239
|
md.endpointChangesNode = endpointChanges.push(md).last;
|
|
239
|
-
md.rebased = undefined;
|
|
240
240
|
}
|
|
241
241
|
submitDelta(op, pending.local.push(md).last);
|
|
242
242
|
};
|
|
@@ -276,57 +276,60 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
276
276
|
const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
|
|
277
277
|
const { value } = op;
|
|
278
278
|
const { id, properties } = getSerializedProperties(value);
|
|
279
|
-
const
|
|
279
|
+
const pending = clearEmptyPendingEntry(this.pending, id);
|
|
280
|
+
const previous = pending?.local.empty
|
|
281
|
+
? pending.consensus
|
|
282
|
+
: pending?.local.last?.data.interval;
|
|
283
|
+
const { type, interval } = localOpMetadata;
|
|
280
284
|
switch (type) {
|
|
281
285
|
case "add": {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.deleteExistingInterval({ interval, local: true, rollback: true });
|
|
285
|
-
}
|
|
286
|
+
this.deleteExistingInterval({ interval, local: true, rollback: true });
|
|
287
|
+
interval.dispose();
|
|
286
288
|
break;
|
|
287
289
|
}
|
|
288
290
|
case "change": {
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
? toOptionalSequencePlace(previous.start, previous.startSide)
|
|
293
|
-
: undefined;
|
|
294
|
-
const end = endpointsChanged
|
|
295
|
-
? toOptionalSequencePlace(previous.end, previous.endSide)
|
|
291
|
+
const changeProperties = Object.keys(properties).length > 0;
|
|
292
|
+
const deltaProps = changeProperties
|
|
293
|
+
? interval.changeProperties(properties, undefined, true)
|
|
296
294
|
: undefined;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
295
|
+
if (localOpMetadata.endpointChangesNode !== undefined) {
|
|
296
|
+
if (previous !== undefined) {
|
|
297
|
+
this.localCollection?.removeExistingInterval(interval);
|
|
298
|
+
this.localCollection?.add(previous);
|
|
299
|
+
this.emitChange(previous, interval, true, true);
|
|
300
|
+
}
|
|
301
|
+
if (previous !== interval) {
|
|
302
|
+
interval.dispose();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (changeProperties) {
|
|
306
|
+
this.emit("propertyChanged", previous, deltaProps, true, undefined);
|
|
307
|
+
}
|
|
303
308
|
break;
|
|
304
309
|
}
|
|
305
310
|
case "delete": {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
props: Object.keys(properties).length > 0 ? properties : undefined,
|
|
312
|
-
rollback: true,
|
|
313
|
-
});
|
|
311
|
+
// a remote could delete the same interval, so it may not exist to re-add
|
|
312
|
+
if (previous !== undefined) {
|
|
313
|
+
this.localCollection?.add(previous);
|
|
314
|
+
this.emit("addInterval", previous, true, undefined);
|
|
315
|
+
}
|
|
314
316
|
break;
|
|
315
317
|
}
|
|
316
318
|
default:
|
|
317
319
|
unreachableCase(type);
|
|
318
320
|
}
|
|
319
|
-
clearEmptyPendingEntry(this.pending, id);
|
|
320
321
|
}
|
|
321
322
|
process(op, local, message, maybeMetadata) {
|
|
322
323
|
const localOpMetadata = local
|
|
323
324
|
? removeMetadataFromPendingChanges(maybeMetadata)
|
|
324
325
|
: undefined;
|
|
325
326
|
const { opName, value } = op;
|
|
327
|
+
const { id } = getSerializedProperties(value);
|
|
326
328
|
assert((local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type, 0xbc1 /* must be same type */);
|
|
329
|
+
let newConsensus = localOpMetadata?.interval;
|
|
327
330
|
switch (opName) {
|
|
328
331
|
case "add": {
|
|
329
|
-
this.ackAdd(value, local, message,
|
|
332
|
+
newConsensus = this.ackAdd(value, local, message,
|
|
330
333
|
// this cast is safe because of the above assert which
|
|
331
334
|
// validates the op and metadata types match for local changes
|
|
332
335
|
localOpMetadata);
|
|
@@ -337,15 +340,17 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
337
340
|
break;
|
|
338
341
|
}
|
|
339
342
|
case "change": {
|
|
340
|
-
this.ackChange(value, local, message
|
|
343
|
+
newConsensus = this.ackChange(value, local, message, // this cast is safe because of the above assert which
|
|
344
|
+
// validates the op and metadata types match for local changes
|
|
345
|
+
localOpMetadata);
|
|
341
346
|
break;
|
|
342
347
|
}
|
|
343
348
|
default:
|
|
344
349
|
unreachableCase(opName);
|
|
345
350
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
const pending = clearEmptyPendingEntry(this.pending, id);
|
|
352
|
+
if (pending !== undefined) {
|
|
353
|
+
pending.consensus = newConsensus;
|
|
349
354
|
}
|
|
350
355
|
}
|
|
351
356
|
resubmitMessage(op, maybeMetadata, squash) {
|
|
@@ -443,7 +448,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
443
448
|
for (const pending of Object.values(this.pending)) {
|
|
444
449
|
if (pending?.endpointChanges !== undefined) {
|
|
445
450
|
for (const local of pending.endpointChanges) {
|
|
446
|
-
local.data.
|
|
451
|
+
this.rebaseLocalInterval(local.data.interval.serialize(), local.data, squash);
|
|
447
452
|
}
|
|
448
453
|
}
|
|
449
454
|
}
|
|
@@ -506,7 +511,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
506
511
|
/**
|
|
507
512
|
* {@inheritdoc IIntervalCollection.add}
|
|
508
513
|
*/
|
|
509
|
-
add({ id, start, end, props,
|
|
514
|
+
add({ id, start, end, props, }) {
|
|
510
515
|
if (!this.localCollection) {
|
|
511
516
|
throw new LoggingError("attach must be called prior to adding intervals");
|
|
512
517
|
}
|
|
@@ -517,7 +522,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
517
522
|
endSide !== undefined, 0x793 /* start and end cannot be undefined because they were not passed in as undefined */);
|
|
518
523
|
this.assertStickinessEnabled(start, end);
|
|
519
524
|
const intervalId = id ?? uuid();
|
|
520
|
-
const interval = this.localCollection.addInterval(intervalId, toSequencePlace(startPos, startSide), toSequencePlace(endPos, endSide), props, undefined
|
|
525
|
+
const interval = this.localCollection.addInterval(intervalId, toSequencePlace(startPos, startSide), toSequencePlace(endPos, endSide), props, undefined);
|
|
521
526
|
if (interval) {
|
|
522
527
|
if (!this.isCollaborating) {
|
|
523
528
|
setSlideOnRemove(interval.start);
|
|
@@ -525,7 +530,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
525
530
|
}
|
|
526
531
|
const serializedInterval = interval.serialize();
|
|
527
532
|
const localSeq = this.getNextLocalSeq();
|
|
528
|
-
if (this.isCollaborating
|
|
533
|
+
if (this.isCollaborating) {
|
|
529
534
|
this.submitDelta({
|
|
530
535
|
opName: "add",
|
|
531
536
|
value: serializedInterval,
|
|
@@ -555,8 +560,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
555
560
|
}, {
|
|
556
561
|
type: "delete",
|
|
557
562
|
localSeq: this.getNextLocalSeq(),
|
|
558
|
-
|
|
559
|
-
});
|
|
563
|
+
}, interval);
|
|
560
564
|
}
|
|
561
565
|
else {
|
|
562
566
|
if (this.onDeserialize) {
|
|
@@ -573,7 +577,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
573
577
|
if (!this.localCollection) {
|
|
574
578
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
575
579
|
}
|
|
576
|
-
const interval = this.
|
|
580
|
+
const interval = this.getIntervalById(id);
|
|
577
581
|
if (interval) {
|
|
578
582
|
this.deleteExistingInterval({ interval, local: true });
|
|
579
583
|
}
|
|
@@ -621,13 +625,12 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
621
625
|
const metadata = {
|
|
622
626
|
type: "change",
|
|
623
627
|
localSeq,
|
|
624
|
-
previous: interval.serialize(),
|
|
625
628
|
interval: newInterval ?? interval,
|
|
626
629
|
};
|
|
627
630
|
this.submitDelta({
|
|
628
631
|
opName: "change",
|
|
629
632
|
value: serializedInterval,
|
|
630
|
-
}, metadata);
|
|
633
|
+
}, metadata, interval);
|
|
631
634
|
}
|
|
632
635
|
if (deltaProps !== undefined) {
|
|
633
636
|
this.emit("propertyChanged", interval, deltaProps, true, undefined);
|
|
@@ -651,7 +654,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
651
654
|
hasPendingEndpointChanges(id) {
|
|
652
655
|
return this.pending[id]?.endpointChanges?.empty === false;
|
|
653
656
|
}
|
|
654
|
-
ackChange(serializedInterval, local, op) {
|
|
657
|
+
ackChange(serializedInterval, local, op, localOpMetadata) {
|
|
655
658
|
if (!this.localCollection) {
|
|
656
659
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
657
660
|
}
|
|
@@ -660,44 +663,39 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
660
663
|
// strip it out of the properties here.
|
|
661
664
|
const { id, properties } = getSerializedProperties(serializedInterval);
|
|
662
665
|
assert(id !== undefined, 0x3fe /* id must exist on the interval */);
|
|
663
|
-
const interval = this.getIntervalById(id);
|
|
664
|
-
if (!interval) {
|
|
665
|
-
// The interval has been removed locally; no-op.
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
666
|
if (local) {
|
|
667
|
+
assert(localOpMetadata !== undefined, 0xbd4 /* local must have metadata */);
|
|
668
|
+
const { interval } = localOpMetadata;
|
|
669
669
|
interval.ackPropertiesChange(properties, op);
|
|
670
670
|
this.ackInterval(interval, op);
|
|
671
|
+
return interval;
|
|
671
672
|
}
|
|
672
673
|
else {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
this.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
if (newInterval !== interval) {
|
|
694
|
-
this.emitChange(newInterval, interval, local, false, op);
|
|
674
|
+
const latestInterval = this.getIntervalById(id);
|
|
675
|
+
const intervalToChange = this.pending[id]?.consensus ?? latestInterval;
|
|
676
|
+
const isLatestInterval = intervalToChange === latestInterval;
|
|
677
|
+
if (!intervalToChange) {
|
|
678
|
+
return intervalToChange;
|
|
679
|
+
}
|
|
680
|
+
const deltaProps = intervalToChange.changeProperties(properties, op);
|
|
681
|
+
let newInterval = intervalToChange;
|
|
682
|
+
if (hasEndpointChanges(serializedInterval)) {
|
|
683
|
+
const { start, end, startSide, endSide } = serializedInterval;
|
|
684
|
+
newInterval = intervalToChange.modify("", toOptionalSequencePlace(start, startSide ?? Side.Before), toOptionalSequencePlace(end, endSide ?? Side.Before), op);
|
|
685
|
+
if (isLatestInterval) {
|
|
686
|
+
this.localCollection.removeExistingInterval(intervalToChange);
|
|
687
|
+
this.localCollection.add(newInterval);
|
|
688
|
+
this.emitChange(newInterval, intervalToChange, local, false, op);
|
|
689
|
+
if (this.onDeserialize) {
|
|
690
|
+
this.onDeserialize(newInterval);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
695
693
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
this.emit("
|
|
699
|
-
this.emit("changed", interval, deltaProps, undefined, local, false);
|
|
694
|
+
if (deltaProps !== undefined && Object.keys(deltaProps).length > 0) {
|
|
695
|
+
this.emit("propertyChanged", latestInterval, deltaProps, local, op);
|
|
696
|
+
this.emit("changed", latestInterval, deltaProps, undefined, local, false);
|
|
700
697
|
}
|
|
698
|
+
return newInterval;
|
|
701
699
|
}
|
|
702
700
|
}
|
|
703
701
|
/**
|
|
@@ -731,8 +729,8 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
731
729
|
}
|
|
732
730
|
const { localSeq, interval } = localOpMetadata;
|
|
733
731
|
const { id } = getSerializedProperties(original);
|
|
734
|
-
const rebasedEndpoint =
|
|
735
|
-
const localInterval = this.
|
|
732
|
+
const rebasedEndpoint = this.computeRebasedPositions(localOpMetadata, squash);
|
|
733
|
+
const localInterval = this.getIntervalById(id);
|
|
736
734
|
// if the interval slid off the string, rebase the op to be a noop and delete the interval.
|
|
737
735
|
if (rebasedEndpoint === "detached") {
|
|
738
736
|
if (localInterval !== undefined &&
|
|
@@ -755,8 +753,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
755
753
|
this.localCollection.add(interval);
|
|
756
754
|
this.emitChange(interval, old, true, true);
|
|
757
755
|
}
|
|
758
|
-
|
|
759
|
-
this.client.removeLocalReferencePosition(old.end);
|
|
756
|
+
old.dispose();
|
|
760
757
|
}
|
|
761
758
|
return {
|
|
762
759
|
...original,
|
|
@@ -805,10 +802,13 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
805
802
|
}
|
|
806
803
|
// `interval`'s endpoints will get modified in-place, so clone it prior to doing so for event emission.
|
|
807
804
|
const oldInterval = interval.clone();
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
805
|
+
const isLatestInterval = this.getIntervalById(id) === interval;
|
|
806
|
+
if (isLatestInterval) {
|
|
807
|
+
// In this case, where we change the start or end of an interval,
|
|
808
|
+
// it is necessary to remove and re-add the interval listeners.
|
|
809
|
+
// This ensures that the correct listeners are added to the LocalReferencePosition.
|
|
810
|
+
this.localCollection.removeExistingInterval(interval);
|
|
811
|
+
}
|
|
812
812
|
if (!this.client) {
|
|
813
813
|
throw new LoggingError("client does not exist");
|
|
814
814
|
}
|
|
@@ -850,19 +850,18 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
850
850
|
oldInterval.end.refType = ReferenceType.Transient;
|
|
851
851
|
oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
|
|
852
852
|
}
|
|
853
|
-
|
|
854
|
-
|
|
853
|
+
if (isLatestInterval) {
|
|
854
|
+
this.localCollection.add(interval);
|
|
855
|
+
this.emitChange(interval, oldInterval, true, true, op);
|
|
856
|
+
}
|
|
855
857
|
}
|
|
856
858
|
}
|
|
857
859
|
ackAdd(serializedInterval, local, op, localOpMetadata) {
|
|
858
860
|
const { id, properties } = getSerializedProperties(serializedInterval);
|
|
859
861
|
if (local) {
|
|
860
862
|
assert(localOpMetadata !== undefined, 0x553 /* op metadata should be defined for local op */);
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
this.ackInterval(localInterval, op);
|
|
864
|
-
}
|
|
865
|
-
return;
|
|
863
|
+
this.ackInterval(localOpMetadata.interval, op);
|
|
864
|
+
return localOpMetadata.interval;
|
|
866
865
|
}
|
|
867
866
|
if (!this.localCollection) {
|
|
868
867
|
throw new LoggingError("attachSequence must be called");
|
|
@@ -887,7 +886,7 @@ export class IntervalCollection extends TypedEventEmitter {
|
|
|
887
886
|
throw new LoggingError("attach must be called prior to deleting intervals");
|
|
888
887
|
}
|
|
889
888
|
const { id } = getSerializedProperties(serializedInterval);
|
|
890
|
-
const interval = this.
|
|
889
|
+
const interval = this.getIntervalById(id);
|
|
891
890
|
if (interval) {
|
|
892
891
|
this.deleteExistingInterval({ interval, local, op });
|
|
893
892
|
}
|