@fluidframework/sequence 2.0.0-internal.2.3.1 → 2.0.0-internal.3.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/dist/intervalCollection.d.ts +7 -3
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +96 -45
- package/dist/intervalCollection.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/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +4 -33
- package/dist/sequence.js.map +1 -1
- package/lib/intervalCollection.d.ts +7 -3
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +96 -45
- package/lib/intervalCollection.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/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +4 -33
- package/lib/sequence.js.map +1 -1
- package/package.json +21 -20
- package/src/intervalCollection.ts +116 -51
- package/src/packageVersion.ts +1 -1
- package/src/sequence.ts +5 -33
|
@@ -622,15 +622,20 @@ function createPositionReferenceFromSegoff(
|
|
|
622
622
|
client: Client,
|
|
623
623
|
segoff: { segment: ISegment | undefined; offset: number | undefined; },
|
|
624
624
|
refType: ReferenceType,
|
|
625
|
-
op?: ISequencedDocumentMessage
|
|
625
|
+
op?: ISequencedDocumentMessage,
|
|
626
|
+
localSeq?: number): LocalReferencePosition {
|
|
626
627
|
if (segoff.segment) {
|
|
627
628
|
const ref = client.createLocalReferencePosition(segoff.segment, segoff.offset, refType, undefined);
|
|
628
629
|
return ref;
|
|
629
630
|
}
|
|
630
631
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
632
|
+
// Creating references on detached segments is allowed for:
|
|
633
|
+
// - Transient segments
|
|
634
|
+
// - References coming from a remote client (location may have been concurrently removed)
|
|
635
|
+
// - References being rebased to a new sequence number
|
|
636
|
+
// (segment they originally referred to may have been removed with no suitable replacement)
|
|
637
|
+
if (!op && !localSeq && !refTypeIncludesFlag(refType, ReferenceType.Transient)) {
|
|
638
|
+
throw new UsageError("Non-transient references need segment");
|
|
634
639
|
}
|
|
635
640
|
|
|
636
641
|
return createDetachedLocalReferencePosition(refType);
|
|
@@ -647,14 +652,14 @@ function createPositionReference(
|
|
|
647
652
|
let segoff;
|
|
648
653
|
if (op) {
|
|
649
654
|
assert((refType & ReferenceType.SlideOnRemove) !== 0, 0x2f5 /* op create references must be SlideOnRemove */);
|
|
650
|
-
segoff = client.getContainingSegment(pos, op);
|
|
655
|
+
segoff = client.getContainingSegment(pos, { referenceSequenceNumber: op.referenceSequenceNumber, clientId: op.clientId });
|
|
651
656
|
segoff = client.getSlideToSegment(segoff);
|
|
652
657
|
} else {
|
|
653
658
|
assert((refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
|
|
654
659
|
0x2f6 /* SlideOnRemove references must be op created */);
|
|
655
660
|
segoff = client.getContainingSegment(pos, undefined, localSeq);
|
|
656
661
|
}
|
|
657
|
-
return createPositionReferenceFromSegoff(client, segoff, refType, op);
|
|
662
|
+
return createPositionReferenceFromSegoff(client, segoff, refType, op, localSeq);
|
|
658
663
|
}
|
|
659
664
|
|
|
660
665
|
export function createSequenceInterval(
|
|
@@ -1039,19 +1044,26 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
|
|
|
1039
1044
|
};
|
|
1040
1045
|
if (interval instanceof SequenceInterval) {
|
|
1041
1046
|
let previousInterval: TInterval & SequenceInterval | undefined;
|
|
1047
|
+
let pendingChanges = 0;
|
|
1042
1048
|
interval.addPositionChangeListeners(
|
|
1043
1049
|
() => {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1050
|
+
pendingChanges++;
|
|
1051
|
+
// Note: both start and end can change and invoke beforeSlide on each endpoint before afterSlide.
|
|
1052
|
+
if (!previousInterval) {
|
|
1053
|
+
previousInterval = interval.clone() as TInterval & SequenceInterval;
|
|
1054
|
+
previousInterval.start = cloneRef(previousInterval.start);
|
|
1055
|
+
previousInterval.end = cloneRef(previousInterval.end);
|
|
1056
|
+
this.removeIntervalFromIndex(interval);
|
|
1057
|
+
}
|
|
1049
1058
|
},
|
|
1050
1059
|
() => {
|
|
1051
1060
|
assert(previousInterval !== undefined, 0x3fa /* Invalid interleaving of before/after slide */);
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1061
|
+
pendingChanges--;
|
|
1062
|
+
if (pendingChanges === 0) {
|
|
1063
|
+
this.addIntervalToIndex(interval);
|
|
1064
|
+
this.onPositionChange?.(interval, previousInterval);
|
|
1065
|
+
previousInterval = undefined;
|
|
1066
|
+
}
|
|
1055
1067
|
},
|
|
1056
1068
|
);
|
|
1057
1069
|
}
|
|
@@ -1176,14 +1188,14 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValu
|
|
|
1176
1188
|
[[
|
|
1177
1189
|
"add",
|
|
1178
1190
|
{
|
|
1179
|
-
process: (collection, params, local, op) => {
|
|
1191
|
+
process: (collection, params, local, op, localOpMetadata) => {
|
|
1180
1192
|
// if params is undefined, the interval was deleted during
|
|
1181
1193
|
// rebasing
|
|
1182
1194
|
if (!params) {
|
|
1183
1195
|
return;
|
|
1184
1196
|
}
|
|
1185
1197
|
assert(op !== undefined, 0x3fb /* op should exist here */);
|
|
1186
|
-
collection.ackAdd(params, local, op);
|
|
1198
|
+
collection.ackAdd(params, local, op, localOpMetadata);
|
|
1187
1199
|
},
|
|
1188
1200
|
rebase,
|
|
1189
1201
|
},
|
|
@@ -1204,14 +1216,14 @@ export function makeOpsMap<T extends ISerializableInterval>(): Map<string, IValu
|
|
|
1204
1216
|
[
|
|
1205
1217
|
"change",
|
|
1206
1218
|
{
|
|
1207
|
-
process: (collection, params, local, op) => {
|
|
1219
|
+
process: (collection, params, local, op, localOpMetadata) => {
|
|
1208
1220
|
// if params is undefined, the interval was deleted during
|
|
1209
1221
|
// rebasing
|
|
1210
1222
|
if (!params) {
|
|
1211
1223
|
return;
|
|
1212
1224
|
}
|
|
1213
1225
|
assert(op !== undefined, 0x3fd /* op should exist here */);
|
|
1214
|
-
collection.ackChange(params, local, op);
|
|
1226
|
+
collection.ackChange(params, local, op, localOpMetadata);
|
|
1215
1227
|
},
|
|
1216
1228
|
rebase,
|
|
1217
1229
|
},
|
|
@@ -1312,6 +1324,8 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1312
1324
|
private localCollection: LocalIntervalCollection<TInterval> | undefined;
|
|
1313
1325
|
private onDeserialize: DeserializeCallback | undefined;
|
|
1314
1326
|
private client: Client | undefined;
|
|
1327
|
+
private readonly localSeqToSerializedInterval = new Map<number, ISerializedInterval | SerializedIntervalDelta>();
|
|
1328
|
+
private readonly localSeqToRebasedInterval = new Map<number, ISerializedInterval | SerializedIntervalDelta>();
|
|
1315
1329
|
private readonly pendingChangesStart: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
|
|
1316
1330
|
private readonly pendingChangesEnd: Map<string, ISerializedInterval[]> = new Map<string, ISerializedInterval[]>();
|
|
1317
1331
|
|
|
@@ -1333,6 +1347,46 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1333
1347
|
: serializedIntervals.intervals.map((i) => decompressInterval(i, serializedIntervals.label));
|
|
1334
1348
|
}
|
|
1335
1349
|
|
|
1350
|
+
private rebasePositionWithSegmentSlide(
|
|
1351
|
+
pos: number,
|
|
1352
|
+
seqNumberFrom: number,
|
|
1353
|
+
localSeq: number
|
|
1354
|
+
): number | undefined {
|
|
1355
|
+
if (!this.client) {
|
|
1356
|
+
throw new LoggingError("mergeTree client must exist");
|
|
1357
|
+
}
|
|
1358
|
+
const { clientId } = this.client.getCollabWindow();
|
|
1359
|
+
const { segment, offset } = this.client.getContainingSegment(pos, { referenceSequenceNumber: seqNumberFrom, clientId: this.client.getLongClientId(clientId) }, localSeq);
|
|
1360
|
+
|
|
1361
|
+
// if segment is undefined, it slid off the string
|
|
1362
|
+
assert(segment !== undefined, 0x54e /* No segment found */);
|
|
1363
|
+
|
|
1364
|
+
const segoff = this.client.getSlideToSegment({ segment, offset }) ?? segment;
|
|
1365
|
+
|
|
1366
|
+
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
1367
|
+
if (segoff.segment === undefined || segoff.offset === undefined) {
|
|
1368
|
+
return DetachedReferencePosition;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
assert(offset !== undefined && 0 <= offset && offset < segment.cachedLength, 0x54f /* Invalid offset */);
|
|
1372
|
+
return this.client.findReconnectionPosition(segoff.segment, localSeq) + segoff.offset;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private computeRebasedPositions(localSeq: number): ISerializedInterval | SerializedIntervalDelta {
|
|
1376
|
+
assert(this.client !== undefined, 0x550 /* Client should be defined when computing rebased position */);
|
|
1377
|
+
const original = this.localSeqToSerializedInterval.get(localSeq);
|
|
1378
|
+
assert(original !== undefined, 0x551 /* Failed to store pending serialized interval info for this localSeq. */);
|
|
1379
|
+
const rebased = { ...original };
|
|
1380
|
+
const { start, end, sequenceNumber } = original;
|
|
1381
|
+
if (start !== undefined) {
|
|
1382
|
+
rebased.start = this.rebasePositionWithSegmentSlide(start, sequenceNumber, localSeq);
|
|
1383
|
+
}
|
|
1384
|
+
if (end !== undefined) {
|
|
1385
|
+
rebased.end = this.rebasePositionWithSegmentSlide(end, sequenceNumber, localSeq);
|
|
1386
|
+
}
|
|
1387
|
+
return rebased;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1336
1390
|
/** @internal */
|
|
1337
1391
|
public attachGraph(client: Client, label: string) {
|
|
1338
1392
|
if (this.attached) {
|
|
@@ -1345,6 +1399,14 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1345
1399
|
|
|
1346
1400
|
// Instantiate the local interval collection based on the saved intervals
|
|
1347
1401
|
this.client = client;
|
|
1402
|
+
if (client) {
|
|
1403
|
+
client.on("normalize", () => {
|
|
1404
|
+
for (const localSeq of this.localSeqToSerializedInterval.keys()) {
|
|
1405
|
+
this.localSeqToRebasedInterval.set(localSeq, this.computeRebasedPositions(localSeq));
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1348
1410
|
this.localCollection = new LocalIntervalCollection<TInterval>(
|
|
1349
1411
|
client,
|
|
1350
1412
|
label,
|
|
@@ -1450,8 +1512,10 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1450
1512
|
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1451
1513
|
start,
|
|
1452
1514
|
};
|
|
1515
|
+
const localSeq = this.getNextLocalSeq();
|
|
1516
|
+
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1453
1517
|
// Local ops get submitted to the server. Remote ops have the deserializer run.
|
|
1454
|
-
this.emitter.emit("add", undefined, serializedInterval, { localSeq
|
|
1518
|
+
this.emitter.emit("add", undefined, serializedInterval, { localSeq });
|
|
1455
1519
|
}
|
|
1456
1520
|
|
|
1457
1521
|
this.emit("addInterval", interval, true, undefined);
|
|
@@ -1531,7 +1595,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1531
1595
|
|
|
1532
1596
|
serializedInterval.properties = props;
|
|
1533
1597
|
serializedInterval.properties[reservedIntervalIdKey] = interval.getIntervalId();
|
|
1534
|
-
|
|
1598
|
+
const localSeq = this.getNextLocalSeq();
|
|
1599
|
+
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1600
|
+
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
1535
1601
|
this.emit("propertyChanged", interval, deltaProps, true, undefined);
|
|
1536
1602
|
}
|
|
1537
1603
|
}
|
|
@@ -1567,7 +1633,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1567
1633
|
{
|
|
1568
1634
|
[reservedIntervalIdKey]: interval.getIntervalId(),
|
|
1569
1635
|
};
|
|
1570
|
-
|
|
1636
|
+
const localSeq = this.getNextLocalSeq();
|
|
1637
|
+
this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
|
|
1638
|
+
this.emitter.emit("change", undefined, serializedInterval, { localSeq });
|
|
1571
1639
|
this.addPendingChange(id, serializedInterval);
|
|
1572
1640
|
this.emitChange(newInterval, interval, true);
|
|
1573
1641
|
return newInterval;
|
|
@@ -1638,12 +1706,19 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1638
1706
|
}
|
|
1639
1707
|
|
|
1640
1708
|
/** @internal */
|
|
1641
|
-
public ackChange(
|
|
1709
|
+
public ackChange(
|
|
1710
|
+
serializedInterval: ISerializedInterval,
|
|
1711
|
+
local: boolean,
|
|
1712
|
+
op: ISequencedDocumentMessage,
|
|
1713
|
+
localOpMetadata: IMapMessageLocalMetadata | undefined,
|
|
1714
|
+
) {
|
|
1642
1715
|
if (!this.localCollection) {
|
|
1643
1716
|
throw new LoggingError("Attach must be called before accessing intervals");
|
|
1644
1717
|
}
|
|
1645
1718
|
|
|
1646
1719
|
if (local) {
|
|
1720
|
+
assert(localOpMetadata !== undefined, 0x552 /* op metadata should be defined for local op */);
|
|
1721
|
+
this.localSeqToSerializedInterval.delete(localOpMetadata?.localSeq);
|
|
1647
1722
|
// This is an ack from the server. Remove the pending change.
|
|
1648
1723
|
this.removePendingChange(serializedInterval);
|
|
1649
1724
|
}
|
|
@@ -1745,11 +1820,10 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1745
1820
|
throw new LoggingError("attachSequence must be called");
|
|
1746
1821
|
}
|
|
1747
1822
|
|
|
1748
|
-
const {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
this.client.rebasePosition(end, sequenceNumber, localSeq);
|
|
1823
|
+
const { intervalType, properties } = serializedInterval;
|
|
1824
|
+
|
|
1825
|
+
const { start: startRebased, end: endRebased } = this.localSeqToRebasedInterval.get(localSeq)
|
|
1826
|
+
?? this.computeRebasedPositions(localSeq);
|
|
1753
1827
|
|
|
1754
1828
|
const intervalId = properties?.[reservedIntervalIdKey];
|
|
1755
1829
|
const localInterval = this.localCollection?.getIntervalById(intervalId);
|
|
@@ -1767,8 +1841,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1767
1841
|
this.addPendingChange(intervalId, rebased);
|
|
1768
1842
|
}
|
|
1769
1843
|
|
|
1770
|
-
// if the interval slid off the string, rebase the op to be a noop and
|
|
1771
|
-
// delete the interval
|
|
1844
|
+
// if the interval slid off the string, rebase the op to be a noop and delete the interval.
|
|
1772
1845
|
if (startRebased === DetachedReferencePosition || endRebased === DetachedReferencePosition) {
|
|
1773
1846
|
if (localInterval) {
|
|
1774
1847
|
this.localCollection?.removeExistingInterval(localInterval);
|
|
@@ -1776,27 +1849,15 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1776
1849
|
return undefined;
|
|
1777
1850
|
}
|
|
1778
1851
|
|
|
1779
|
-
if (
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const startSegment = this.getSlideToSegment(localInterval.start);
|
|
1790
|
-
const endSegment = this.getSlideToSegment(localInterval.end);
|
|
1791
|
-
|
|
1792
|
-
// we need to slide because the reference has been removed
|
|
1793
|
-
if (startSegment || endSegment) {
|
|
1794
|
-
const newStart =
|
|
1795
|
-
startSegment && this.client.getPosition(startSegment.segment, localSeq) + (startSegment.offset ?? 0);
|
|
1796
|
-
const newEnd =
|
|
1797
|
-
endSegment && this.client.getPosition(endSegment.segment, localSeq) + (endSegment.offset ?? 0);
|
|
1798
|
-
|
|
1799
|
-
this.localCollection?.changeInterval(localInterval, newStart, newEnd, undefined, localSeq);
|
|
1852
|
+
if (localInterval !== undefined) {
|
|
1853
|
+
// we know we must be using `SequenceInterval` because `this.client` exists
|
|
1854
|
+
assert(
|
|
1855
|
+
localInterval instanceof SequenceInterval,
|
|
1856
|
+
0x3a0 /* localInterval must be `SequenceInterval` when used with client */,
|
|
1857
|
+
);
|
|
1858
|
+
// The rebased op may place this interval's endpoints on different segments. Calling `changeInterval` here
|
|
1859
|
+
// updates the local client's state to be consistent with the emitted op.
|
|
1860
|
+
this.localCollection?.changeInterval(localInterval, startRebased, endRebased, undefined, localSeq);
|
|
1800
1861
|
}
|
|
1801
1862
|
|
|
1802
1863
|
return rebased;
|
|
@@ -1905,8 +1966,12 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
|
|
|
1905
1966
|
public ackAdd(
|
|
1906
1967
|
serializedInterval: ISerializedInterval,
|
|
1907
1968
|
local: boolean,
|
|
1908
|
-
op: ISequencedDocumentMessage
|
|
1969
|
+
op: ISequencedDocumentMessage,
|
|
1970
|
+
localOpMetadata: IMapMessageLocalMetadata | undefined,
|
|
1971
|
+
) {
|
|
1909
1972
|
if (local) {
|
|
1973
|
+
assert(localOpMetadata !== undefined, 0x553 /* op metadata should be defined for local op */);
|
|
1974
|
+
this.localSeqToSerializedInterval.delete(localOpMetadata.localSeq);
|
|
1910
1975
|
const id: string = serializedInterval.properties?.[reservedIntervalIdKey];
|
|
1911
1976
|
const localInterval = this.getIntervalById(id);
|
|
1912
1977
|
if (localInterval) {
|
package/src/packageVersion.ts
CHANGED
package/src/sequence.ts
CHANGED
|
@@ -200,40 +200,12 @@ export abstract class SharedSegmentSequence<T extends ISegment>
|
|
|
200
200
|
ChildLogger.create(this.logger, "SharedSegmentSequence.MergeTreeClient"),
|
|
201
201
|
mergeTreeOptions);
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
case "sequenceDelta":
|
|
206
|
-
if (!this.client.mergeTreeDeltaCallback) {
|
|
207
|
-
this.client.mergeTreeDeltaCallback = (opArgs, deltaArgs) => {
|
|
208
|
-
this.emit("sequenceDelta", new SequenceDeltaEvent(opArgs, deltaArgs, this.client), this);
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
case "maintenance":
|
|
213
|
-
if (!this.client.mergeTreeMaintenanceCallback) {
|
|
214
|
-
this.client.mergeTreeMaintenanceCallback = (args, opArgs) => {
|
|
215
|
-
this.emit("maintenance", new SequenceMaintenanceEvent(opArgs, args, this.client), this);
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
break;
|
|
219
|
-
default:
|
|
220
|
-
}
|
|
203
|
+
this.client.on("delta", (opArgs, deltaArgs) => {
|
|
204
|
+
this.emit("sequenceDelta", new SequenceDeltaEvent(opArgs, deltaArgs, this.client), this);
|
|
221
205
|
});
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (super.listenerCount(event) === 0) {
|
|
226
|
-
this.client.mergeTreeDeltaCallback = undefined;
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
case "maintenance":
|
|
230
|
-
if (super.listenerCount(event) === 0) {
|
|
231
|
-
this.client.mergeTreeMaintenanceCallback = undefined;
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
default:
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
206
|
+
|
|
207
|
+
this.client.on("maintenance", (args, opArgs) => {
|
|
208
|
+
this.emit("maintenance", new SequenceMaintenanceEvent(opArgs, args, this.client), this);
|
|
237
209
|
});
|
|
238
210
|
|
|
239
211
|
this.intervalCollections = new DefaultMap(
|