@fluidframework/sequence 2.0.0-dev.4.4.0.162574 → 2.0.0-dev.5.3.2.178189

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.
Files changed (107) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/defaultMap.d.ts +3 -2
  3. package/dist/defaultMap.d.ts.map +1 -1
  4. package/dist/defaultMap.js +4 -3
  5. package/dist/defaultMap.js.map +1 -1
  6. package/dist/defaultMapInterfaces.d.ts +12 -1
  7. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  8. package/dist/defaultMapInterfaces.js.map +1 -1
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +17 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/intervalCollection.d.ts +240 -78
  14. package/dist/intervalCollection.d.ts.map +1 -1
  15. package/dist/intervalCollection.js +313 -190
  16. package/dist/intervalCollection.js.map +1 -1
  17. package/dist/intervalIndex/index.d.ts +8 -0
  18. package/dist/intervalIndex/index.d.ts.map +1 -0
  19. package/dist/intervalIndex/index.js +12 -0
  20. package/dist/intervalIndex/index.js.map +1 -0
  21. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
  22. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
  23. package/dist/intervalIndex/overlappingIntervalsIndex.js +103 -0
  24. package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
  25. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
  26. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
  27. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +33 -0
  28. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
  29. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
  30. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
  31. package/dist/intervalIndex/sequenceIntervalIndexes.js +7 -0
  32. package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
  33. package/dist/packageVersion.d.ts +1 -1
  34. package/dist/packageVersion.js +1 -1
  35. package/dist/packageVersion.js.map +1 -1
  36. package/dist/revertibles.d.ts +104 -0
  37. package/dist/revertibles.d.ts.map +1 -0
  38. package/dist/revertibles.js +414 -0
  39. package/dist/revertibles.js.map +1 -0
  40. package/dist/sequence.d.ts +4 -4
  41. package/dist/sequence.d.ts.map +1 -1
  42. package/dist/sequence.js +3 -3
  43. package/dist/sequence.js.map +1 -1
  44. package/dist/sharedIntervalCollection.d.ts +3 -3
  45. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  46. package/dist/sharedIntervalCollection.js +1 -1
  47. package/dist/sharedIntervalCollection.js.map +1 -1
  48. package/dist/tsdoc-metadata.json +11 -0
  49. package/lib/defaultMap.d.ts +3 -2
  50. package/lib/defaultMap.d.ts.map +1 -1
  51. package/lib/defaultMap.js +4 -3
  52. package/lib/defaultMap.js.map +1 -1
  53. package/lib/defaultMapInterfaces.d.ts +12 -1
  54. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  55. package/lib/defaultMapInterfaces.js.map +1 -1
  56. package/lib/index.d.ts +4 -2
  57. package/lib/index.d.ts.map +1 -1
  58. package/lib/index.js +3 -1
  59. package/lib/index.js.map +1 -1
  60. package/lib/intervalCollection.d.ts +240 -78
  61. package/lib/intervalCollection.d.ts.map +1 -1
  62. package/lib/intervalCollection.js +310 -190
  63. package/lib/intervalCollection.js.map +1 -1
  64. package/lib/intervalIndex/index.d.ts +8 -0
  65. package/lib/intervalIndex/index.d.ts.map +1 -0
  66. package/lib/intervalIndex/index.js +7 -0
  67. package/lib/intervalIndex/index.js.map +1 -0
  68. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
  69. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
  70. package/lib/intervalIndex/overlappingIntervalsIndex.js +98 -0
  71. package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
  72. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
  73. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
  74. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +29 -0
  75. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
  76. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
  77. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
  78. package/lib/intervalIndex/sequenceIntervalIndexes.js +6 -0
  79. package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
  80. package/lib/packageVersion.d.ts +1 -1
  81. package/lib/packageVersion.js +1 -1
  82. package/lib/packageVersion.js.map +1 -1
  83. package/lib/revertibles.d.ts +104 -0
  84. package/lib/revertibles.d.ts.map +1 -0
  85. package/lib/revertibles.js +404 -0
  86. package/lib/revertibles.js.map +1 -0
  87. package/lib/sequence.d.ts +4 -4
  88. package/lib/sequence.d.ts.map +1 -1
  89. package/lib/sequence.js +3 -3
  90. package/lib/sequence.js.map +1 -1
  91. package/lib/sharedIntervalCollection.d.ts +3 -3
  92. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  93. package/lib/sharedIntervalCollection.js +1 -1
  94. package/lib/sharedIntervalCollection.js.map +1 -1
  95. package/package.json +22 -24
  96. package/src/defaultMap.ts +4 -1
  97. package/src/defaultMapInterfaces.ts +13 -1
  98. package/src/index.ts +27 -5
  99. package/src/intervalCollection.ts +660 -216
  100. package/src/intervalIndex/index.ts +11 -0
  101. package/src/intervalIndex/overlappingIntervalsIndex.ts +166 -0
  102. package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +71 -0
  103. package/src/intervalIndex/sequenceIntervalIndexes.ts +32 -0
  104. package/src/packageVersion.ts +1 -1
  105. package/src/revertibles.ts +626 -0
  106. package/src/sequence.ts +12 -2
  107. package/src/sharedIntervalCollection.ts +4 -2
@@ -0,0 +1,626 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ /* eslint-disable no-bitwise */
6
+
7
+ import { assert, unreachableCase } from "@fluidframework/common-utils";
8
+ import {
9
+ appendToMergeTreeDeltaRevertibles,
10
+ discardMergeTreeDeltaRevertible,
11
+ isMergeTreeDeltaRevertible,
12
+ LocalReferencePosition,
13
+ MergeTreeDeltaOperationType,
14
+ MergeTreeDeltaRevertible,
15
+ MergeTreeDeltaType,
16
+ PropertySet,
17
+ ReferenceType,
18
+ refTypeIncludesFlag,
19
+ revertMergeTreeDeltaRevertibles,
20
+ SortedSet,
21
+ getSlideToSegoff,
22
+ } from "@fluidframework/merge-tree";
23
+ import { IntervalOpType, SequenceInterval } from "./intervalCollection";
24
+ import { SharedString, SharedStringSegment } from "./sharedString";
25
+ import { ISequenceDeltaRange, SequenceDeltaEvent } from "./sequenceDeltaEvent";
26
+
27
+ /**
28
+ * Data for undoing edits on SharedStrings and Intervals.
29
+ *
30
+ * Revertibles are new and require the option mergeTreeUseNewLengthCalculations to
31
+ * be set as true on the underlying merge tree in order to function correctly.
32
+ *
33
+ * @alpha
34
+ */
35
+ export type SharedStringRevertible = MergeTreeDeltaRevertible | IntervalRevertible;
36
+
37
+ const idMap = new Map<string, string>();
38
+
39
+ type IntervalOpType = typeof IntervalOpType[keyof typeof IntervalOpType];
40
+
41
+ /**
42
+ * Data for undoing edits affecting Intervals.
43
+ *
44
+ * Revertibles are new and require the option mergeTreeUseNewLengthCalculations to
45
+ * be set as true on the underlying merge tree in order to function correctly.
46
+ *
47
+ * @alpha
48
+ */
49
+ export type IntervalRevertible =
50
+ | {
51
+ event: typeof IntervalOpType.CHANGE;
52
+ interval: SequenceInterval;
53
+ start: LocalReferencePosition;
54
+ end: LocalReferencePosition;
55
+ }
56
+ | {
57
+ event: typeof IntervalOpType.ADD;
58
+ interval: SequenceInterval;
59
+ }
60
+ | {
61
+ event: typeof IntervalOpType.DELETE;
62
+ interval: SequenceInterval;
63
+ start: LocalReferencePosition;
64
+ end: LocalReferencePosition;
65
+ }
66
+ | {
67
+ event: typeof IntervalOpType.PROPERTY_CHANGED;
68
+ interval: SequenceInterval;
69
+ propertyDeltas: PropertySet;
70
+ }
71
+ | {
72
+ event: typeof IntervalOpType.POSITION_REMOVE;
73
+ intervals: {
74
+ intervalId: string;
75
+ label: string;
76
+ startOffset?: number; // interval start index within a removed range
77
+ endOffset?: number; // interval end index within a removed range
78
+ }[];
79
+ // local refs used by IntervalOpType.CHANGE and DELETE revertibles
80
+ revertibleRefs: {
81
+ revertible: IntervalRevertible;
82
+ offset: number;
83
+ isStart: boolean;
84
+ }[];
85
+ mergeTreeRevertible: MergeTreeDeltaRevertible;
86
+ };
87
+
88
+ type TypedRevertible<T extends IntervalRevertible["event"]> = IntervalRevertible & { event: T };
89
+
90
+ function getUpdatedIdFromInterval(interval: SequenceInterval): string {
91
+ const maybeId = interval.getIntervalId();
92
+ return getUpdatedId(maybeId);
93
+ }
94
+
95
+ function getUpdatedId(intervalId: string): string {
96
+ return idMap.get(intervalId) ?? intervalId;
97
+ }
98
+
99
+ /**
100
+ * Create revertibles for adding an interval
101
+ * @alpha
102
+ */
103
+ export function appendAddIntervalToRevertibles(
104
+ interval: SequenceInterval,
105
+ revertibles: SharedStringRevertible[],
106
+ ) {
107
+ revertibles.push({
108
+ event: IntervalOpType.ADD,
109
+ interval,
110
+ });
111
+
112
+ return revertibles;
113
+ }
114
+
115
+ /**
116
+ * Create revertibles for deleting an interval
117
+ * @alpha
118
+ */
119
+ export function appendDeleteIntervalToRevertibles(
120
+ string: SharedString,
121
+ interval: SequenceInterval,
122
+ revertibles: SharedStringRevertible[],
123
+ ) {
124
+ const startSeg = interval.start.getSegment() as SharedStringSegment;
125
+ const startType =
126
+ startSeg.removedSeq !== undefined
127
+ ? ReferenceType.SlideOnRemove | ReferenceType.RangeBegin
128
+ : ReferenceType.StayOnRemove | ReferenceType.RangeBegin;
129
+ const endSeg = interval.end.getSegment() as SharedStringSegment;
130
+ const endType =
131
+ endSeg.removedSeq !== undefined
132
+ ? ReferenceType.SlideOnRemove | ReferenceType.RangeEnd
133
+ : ReferenceType.StayOnRemove | ReferenceType.RangeEnd;
134
+ const startRef = string.createLocalReferencePosition(
135
+ startSeg,
136
+ interval.start.getOffset(),
137
+ startType,
138
+ undefined,
139
+ interval.start.slidingPreference,
140
+ );
141
+ const endRef = string.createLocalReferencePosition(
142
+ endSeg,
143
+ interval.end.getOffset(),
144
+ endType,
145
+ undefined,
146
+ interval.end.slidingPreference,
147
+ );
148
+ const revertible = {
149
+ event: IntervalOpType.DELETE,
150
+ interval,
151
+ start: startRef,
152
+ end: endRef,
153
+ };
154
+ revertible.start.addProperties({ revertible });
155
+ revertible.end.addProperties({ revertible });
156
+ revertibles.push(revertible);
157
+
158
+ return revertibles;
159
+ }
160
+
161
+ /**
162
+ * Create revertibles for moving endpoints of an interval
163
+ * @alpha
164
+ */
165
+ export function appendChangeIntervalToRevertibles(
166
+ string: SharedString,
167
+ newInterval: SequenceInterval,
168
+ previousInterval: SequenceInterval,
169
+ revertibles: SharedStringRevertible[],
170
+ ) {
171
+ const startSeg = previousInterval.start.getSegment() as SharedStringSegment;
172
+ // This logic is needed because the ReferenceType StayOnRemove cannot be used
173
+ // on removed segments. This works for revertibles because the old position of the
174
+ // interval within the removed segment is handled by the remove range revertible.
175
+ const startType =
176
+ startSeg.removedSeq !== undefined
177
+ ? ReferenceType.SlideOnRemove | ReferenceType.RangeBegin
178
+ : ReferenceType.StayOnRemove | ReferenceType.RangeBegin;
179
+ const endSeg = previousInterval.end.getSegment() as SharedStringSegment;
180
+ const endType =
181
+ endSeg.removedSeq !== undefined
182
+ ? ReferenceType.SlideOnRemove | ReferenceType.RangeEnd
183
+ : ReferenceType.StayOnRemove | ReferenceType.RangeEnd;
184
+ const prevStartRef = string.createLocalReferencePosition(
185
+ startSeg,
186
+ previousInterval.start.getOffset(),
187
+ startType,
188
+ undefined,
189
+ previousInterval.start.slidingPreference,
190
+ );
191
+ const prevEndRef = string.createLocalReferencePosition(
192
+ endSeg,
193
+ previousInterval.end.getOffset(),
194
+ endType,
195
+ undefined,
196
+ previousInterval.end.slidingPreference,
197
+ );
198
+ const revertible = {
199
+ event: IntervalOpType.CHANGE,
200
+ interval: newInterval,
201
+ start: prevStartRef,
202
+ end: prevEndRef,
203
+ };
204
+ revertible.start.addProperties({ revertible });
205
+ revertible.end.addProperties({ revertible });
206
+ revertibles.push(revertible);
207
+
208
+ return revertibles;
209
+ }
210
+
211
+ /**
212
+ * Create revertibles for changing properties of an interval
213
+ * @alpha
214
+ */
215
+ export function appendIntervalPropertyChangedToRevertibles(
216
+ interval: SequenceInterval,
217
+ deltas: PropertySet,
218
+ revertibles: SharedStringRevertible[],
219
+ ) {
220
+ revertibles.push({
221
+ event: IntervalOpType.PROPERTY_CHANGED,
222
+ interval,
223
+ propertyDeltas: deltas,
224
+ });
225
+
226
+ return revertibles;
227
+ }
228
+
229
+ function addIfIntervalEndpoint(
230
+ ref: LocalReferencePosition,
231
+ segmentLengths: number,
232
+ startIntervals: { offset: number; interval: SequenceInterval }[],
233
+ endIntervals: { offset: number; interval: SequenceInterval }[],
234
+ ) {
235
+ if (refTypeIncludesFlag(ref.refType, ReferenceType.RangeBegin)) {
236
+ const interval = ref.properties?.interval;
237
+ if (interval && interval instanceof SequenceInterval) {
238
+ startIntervals.push({ offset: segmentLengths + interval.start.getOffset(), interval });
239
+ return true;
240
+ }
241
+ } else if (refTypeIncludesFlag(ref.refType, ReferenceType.RangeEnd)) {
242
+ const interval = ref.properties?.interval;
243
+ if (interval && interval instanceof SequenceInterval) {
244
+ endIntervals.push({ offset: segmentLengths + interval.end.getOffset(), interval });
245
+ return true;
246
+ }
247
+ }
248
+ return false;
249
+ }
250
+
251
+ function addIfRevertibleRef(
252
+ ref: LocalReferencePosition,
253
+ segmentLengths: number,
254
+ revertibleRefs: {
255
+ revertible: IntervalRevertible;
256
+ offset: number;
257
+ isStart: boolean;
258
+ }[],
259
+ ) {
260
+ const revertible = ref.properties?.revertible;
261
+ if (revertible) {
262
+ revertibleRefs.push({
263
+ revertible,
264
+ offset: segmentLengths + ref.getOffset(),
265
+ isStart: refTypeIncludesFlag(ref.refType, ReferenceType.RangeBegin),
266
+ });
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Create revertibles for SharedStringDeltas, handling indirectly modified intervals
272
+ * (e.g. reverting remove of a range that contains an interval will move the interval back)
273
+ *
274
+ * Revertibles are new and require the option mergeTreeUseNewLengthCalculations to
275
+ * be set as true on the underlying merge tree in order to function correctly.
276
+ *
277
+ * @alpha
278
+ */
279
+ export function appendSharedStringDeltaToRevertibles(
280
+ string: SharedString,
281
+ delta: SequenceDeltaEvent,
282
+ revertibles: SharedStringRevertible[],
283
+ ) {
284
+ if (delta.ranges.length === 0) {
285
+ return;
286
+ }
287
+ if (delta.deltaOperation === MergeTreeDeltaType.REMOVE) {
288
+ const startIntervals: { offset: number; interval: SequenceInterval }[] = [];
289
+ const endIntervals: { offset: number; interval: SequenceInterval }[] = [];
290
+ const revertibleRefs: {
291
+ revertible: IntervalRevertible;
292
+ offset: number;
293
+ isStart: boolean;
294
+ }[] = [];
295
+ let segmentLengths = 0;
296
+
297
+ // find interval endpoints in each segment
298
+ for (const deltaRange of delta.ranges) {
299
+ const refs = deltaRange.segment.localRefs;
300
+ if (refs !== undefined && deltaRange.position !== -1) {
301
+ for (const ref of refs) {
302
+ addIfIntervalEndpoint(ref, segmentLengths, startIntervals, endIntervals);
303
+ addIfRevertibleRef(ref, segmentLengths, revertibleRefs);
304
+ }
305
+ }
306
+ segmentLengths += deltaRange.segment.cachedLength;
307
+ }
308
+
309
+ if (startIntervals.length > 0 || endIntervals.length > 0 || revertibleRefs.length > 0) {
310
+ const removeRevertibles: MergeTreeDeltaRevertible[] = [];
311
+ appendToMergeTreeDeltaRevertibles(delta.deltaArgs, removeRevertibles);
312
+ assert(
313
+ removeRevertibles.length === 1,
314
+ 0x6c4 /* Remove revertible should be a single delta */,
315
+ );
316
+
317
+ const revertible: TypedRevertible<typeof IntervalOpType.POSITION_REMOVE> = {
318
+ event: IntervalOpType.POSITION_REMOVE,
319
+ intervals: [],
320
+ revertibleRefs,
321
+ mergeTreeRevertible: removeRevertibles[0],
322
+ };
323
+
324
+ // add an interval for each startInterval, accounting for any corresponding endIntervals
325
+ startIntervals.forEach(({ interval, offset }) => {
326
+ // find any corresponding end for this interval
327
+ const endIntervalIndex = endIntervals.findIndex((end) => {
328
+ return end.interval === interval;
329
+ });
330
+ let endOffset: number | undefined;
331
+ if (endIntervalIndex !== -1) {
332
+ endOffset = endIntervals[endIntervalIndex].offset;
333
+ endIntervals.splice(endIntervalIndex, 1);
334
+ }
335
+
336
+ revertible.intervals.push({
337
+ intervalId: interval.getIntervalId(),
338
+ label: interval.properties.referenceRangeLabels[0],
339
+ startOffset: offset,
340
+ endOffset,
341
+ });
342
+ });
343
+
344
+ // add any remaining endIntervals that aren't matched with a startInterval
345
+ endIntervals.forEach(({ interval, offset }) => {
346
+ revertible.intervals.push({
347
+ intervalId: interval.getIntervalId(),
348
+ label: interval.properties.referenceRangeLabels[0],
349
+ endOffset: offset,
350
+ });
351
+ });
352
+
353
+ revertibles.push(revertible);
354
+ return;
355
+ }
356
+ }
357
+
358
+ // Handle any merge tree delta that is not REMOVE or is REMOVE with no interval endpoints
359
+ const mergeTreeRevertibles: MergeTreeDeltaRevertible[] = [];
360
+ // Allow merging MergeTreeDeltaRevertible with previous
361
+ if (revertibles.length > 0 && isMergeTreeDeltaRevertible(revertibles[revertibles.length - 1])) {
362
+ mergeTreeRevertibles.push(revertibles.pop() as MergeTreeDeltaRevertible);
363
+ }
364
+ appendToMergeTreeDeltaRevertibles(delta.deltaArgs, mergeTreeRevertibles);
365
+ revertibles.push(...mergeTreeRevertibles);
366
+ }
367
+
368
+ /**
369
+ * Clean up resources held by revertibles that are no longer needed.
370
+ * @alpha
371
+ */
372
+ export function discardSharedStringRevertibles(
373
+ sharedString: SharedString,
374
+ revertibles: SharedStringRevertible[],
375
+ ) {
376
+ revertibles.forEach((r) => {
377
+ if (isMergeTreeDeltaRevertible(r)) {
378
+ discardMergeTreeDeltaRevertible([r]);
379
+ } else if (r.event === IntervalOpType.CHANGE || r.event === IntervalOpType.DELETE) {
380
+ sharedString.removeLocalReferencePosition(r.start);
381
+ sharedString.removeLocalReferencePosition(r.end);
382
+ }
383
+ });
384
+ }
385
+
386
+ function getSlidePosition(string: SharedString, lref: LocalReferencePosition, pos: number): number {
387
+ const slide = getSlideToSegoff(
388
+ { segment: lref.getSegment(), offset: undefined },
389
+ lref.slidingPreference,
390
+ );
391
+ return slide?.segment !== undefined &&
392
+ slide.offset !== undefined &&
393
+ string.getPosition(slide.segment) !== -1 &&
394
+ (pos < 0 || pos >= string.getLength())
395
+ ? string.getPosition(slide.segment) + slide.offset
396
+ : pos;
397
+ }
398
+
399
+ function isValidRange(start: number, end: number, string: SharedString) {
400
+ return (
401
+ start >= 0 &&
402
+ start < string.getLength() &&
403
+ end >= 0 &&
404
+ end < string.getLength() &&
405
+ start <= end
406
+ );
407
+ }
408
+
409
+ function revertLocalAdd(
410
+ string: SharedString,
411
+ revertible: TypedRevertible<typeof IntervalOpType.ADD>,
412
+ ) {
413
+ const id = getUpdatedIdFromInterval(revertible.interval);
414
+ const label = revertible.interval.properties.referenceRangeLabels[0];
415
+ string.getIntervalCollection(label).removeIntervalById(id);
416
+ }
417
+
418
+ function revertLocalDelete(
419
+ string: SharedString,
420
+ revertible: TypedRevertible<typeof IntervalOpType.DELETE>,
421
+ ) {
422
+ const label = revertible.interval.properties.referenceRangeLabels[0];
423
+ const collection = string.getIntervalCollection(label);
424
+ const start = string.localReferencePositionToPosition(revertible.start);
425
+ const startSlidePos = getSlidePosition(string, revertible.start, start);
426
+ const end = string.localReferencePositionToPosition(revertible.end);
427
+ const endSlidePos = getSlidePosition(string, revertible.end, end);
428
+ const type = revertible.interval.intervalType;
429
+ // reusing the id causes eventual consistency bugs, so it is removed here and recreated in add
430
+ const { intervalId, ...props } = revertible.interval.properties;
431
+ if (!isValidRange(startSlidePos, endSlidePos, string)) return;
432
+ const int = collection.add(startSlidePos, endSlidePos, type, props);
433
+
434
+ idMap.forEach((newId, oldId) => {
435
+ if (intervalId === newId) {
436
+ idMap.set(oldId, getUpdatedIdFromInterval(int));
437
+ }
438
+ });
439
+ idMap.set(intervalId, int.getIntervalId());
440
+
441
+ string.removeLocalReferencePosition(revertible.start);
442
+ string.removeLocalReferencePosition(revertible.end);
443
+ }
444
+
445
+ function revertLocalChange(
446
+ string: SharedString,
447
+ revertible: TypedRevertible<typeof IntervalOpType.CHANGE>,
448
+ ) {
449
+ const label = revertible.interval.properties.referenceRangeLabels[0];
450
+ const collection = string.getIntervalCollection(label);
451
+ const id = getUpdatedIdFromInterval(revertible.interval);
452
+ const start = string.localReferencePositionToPosition(revertible.start);
453
+ const startSlidePos = getSlidePosition(string, revertible.start, start);
454
+ const end = string.localReferencePositionToPosition(revertible.end);
455
+ const endSlidePos = getSlidePosition(string, revertible.end, end);
456
+ if (!isValidRange(startSlidePos, endSlidePos, string)) return;
457
+ collection.change(id, startSlidePos, endSlidePos);
458
+
459
+ string.removeLocalReferencePosition(revertible.start);
460
+ string.removeLocalReferencePosition(revertible.end);
461
+ }
462
+
463
+ function revertLocalPropertyChanged(
464
+ string: SharedString,
465
+ revertible: TypedRevertible<typeof IntervalOpType.PROPERTY_CHANGED>,
466
+ ) {
467
+ const label = revertible.interval.properties.referenceRangeLabels[0];
468
+ const id = getUpdatedIdFromInterval(revertible.interval);
469
+ const newProps = revertible.propertyDeltas;
470
+ string.getIntervalCollection(label).changeProperties(id, newProps);
471
+ }
472
+
473
+ function newPosition(offset: number | undefined, restoredRanges: SortedRangeSet) {
474
+ if (offset === undefined) {
475
+ return undefined;
476
+ }
477
+
478
+ let offsetFromSegment = offset;
479
+ for (const rangeInfo of restoredRanges.items) {
480
+ if (offsetFromSegment < rangeInfo.length) {
481
+ // find the segment inside the range
482
+ for (const range of rangeInfo.ranges) {
483
+ if (range.segment.cachedLength > offsetFromSegment) {
484
+ return { segment: range.segment, offset: offsetFromSegment };
485
+ }
486
+ offsetFromSegment -= range.segment.cachedLength;
487
+ }
488
+ }
489
+ offsetFromSegment -= rangeInfo.length;
490
+ }
491
+
492
+ return undefined;
493
+ }
494
+
495
+ function newEndpointPosition(
496
+ offset: number | undefined,
497
+ restoredRanges: SortedRangeSet,
498
+ sharedString: SharedString,
499
+ ) {
500
+ const pos = newPosition(offset, restoredRanges);
501
+ return pos === undefined ? undefined : sharedString.getPosition(pos.segment) + pos.offset;
502
+ }
503
+
504
+ interface RangeInfo {
505
+ ranges: readonly Readonly<ISequenceDeltaRange<MergeTreeDeltaOperationType>>[];
506
+ length: number;
507
+ }
508
+
509
+ class SortedRangeSet extends SortedSet<RangeInfo, string> {
510
+ protected getKey(item: RangeInfo): string {
511
+ return item.ranges[0].segment.ordinal;
512
+ }
513
+ }
514
+
515
+ function revertLocalSequenceRemove(
516
+ sharedString: SharedString,
517
+ revertible: TypedRevertible<typeof IntervalOpType.POSITION_REMOVE>,
518
+ ) {
519
+ const restoredRanges = new SortedRangeSet();
520
+ const saveSegments = (event: SequenceDeltaEvent) => {
521
+ if (event.ranges.length > 0) {
522
+ let length = 0;
523
+ event.ranges.forEach((range) => {
524
+ length += range.segment.cachedLength;
525
+ });
526
+ restoredRanges.addOrUpdate({ ranges: event.ranges, length });
527
+ }
528
+ };
529
+ sharedString.on("sequenceDelta", saveSegments);
530
+ revertMergeTreeDeltaRevertibles(sharedString, [revertible.mergeTreeRevertible]);
531
+ sharedString.off("sequenceDelta", saveSegments);
532
+
533
+ revertible.intervals.forEach((intervalInfo) => {
534
+ const intervalCollection = sharedString.getIntervalCollection(intervalInfo.label);
535
+ const intervalId = getUpdatedId(intervalInfo.intervalId);
536
+ const interval = intervalCollection.getIntervalById(intervalId);
537
+ if (interval !== undefined) {
538
+ const newStart = newEndpointPosition(
539
+ intervalInfo.startOffset,
540
+ restoredRanges,
541
+ sharedString,
542
+ );
543
+ const newEnd = newEndpointPosition(
544
+ intervalInfo.endOffset,
545
+ restoredRanges,
546
+ sharedString,
547
+ );
548
+ if (newStart !== undefined || newEnd !== undefined) {
549
+ intervalCollection.change(intervalId, newStart, newEnd);
550
+ }
551
+ }
552
+ });
553
+
554
+ // fix up the local references used by delete and change revertibles
555
+ revertible.revertibleRefs.forEach((revertibleRef) => {
556
+ assert(
557
+ revertibleRef.revertible.event === IntervalOpType.CHANGE ||
558
+ revertibleRef.revertible.event === IntervalOpType.DELETE,
559
+ 0x6c5 /* revertible is not delete or change */,
560
+ );
561
+ const pos = newPosition(revertibleRef.offset, restoredRanges);
562
+ if (pos !== undefined) {
563
+ if (revertibleRef.isStart) {
564
+ sharedString.removeLocalReferencePosition(revertibleRef.revertible.start);
565
+ const newRef = sharedString.createLocalReferencePosition(
566
+ pos.segment as SharedStringSegment,
567
+ pos.offset,
568
+ ReferenceType.StayOnRemove | ReferenceType.RangeBegin,
569
+ { revertible: revertibleRef.revertible },
570
+ );
571
+ revertibleRef.revertible.start = newRef;
572
+ } else {
573
+ sharedString.removeLocalReferencePosition(revertibleRef.revertible.end);
574
+ const newRef = sharedString.createLocalReferencePosition(
575
+ pos.segment as SharedStringSegment,
576
+ pos.offset,
577
+ ReferenceType.StayOnRemove | ReferenceType.RangeEnd,
578
+ { revertible: revertibleRef.revertible },
579
+ );
580
+ revertibleRef.revertible.end = newRef;
581
+ }
582
+ }
583
+ });
584
+ }
585
+
586
+ /**
587
+ * Invoke revertibles to reverse prior edits
588
+ *
589
+ * Revertibles are new and require the option mergeTreeUseNewLengthCalculations to
590
+ * be set as true on the underlying merge tree in order to function correctly.
591
+ *
592
+ * @alpha
593
+ */
594
+ export function revertSharedStringRevertibles(
595
+ sharedString: SharedString,
596
+ revertibles: SharedStringRevertible[],
597
+ ) {
598
+ while (revertibles.length > 0) {
599
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
600
+ const r = revertibles.pop()!;
601
+ if ("event" in r) {
602
+ const event = r.event;
603
+ switch (event) {
604
+ case IntervalOpType.ADD:
605
+ revertLocalAdd(sharedString, r);
606
+ break;
607
+ case IntervalOpType.DELETE:
608
+ revertLocalDelete(sharedString, r);
609
+ break;
610
+ case IntervalOpType.CHANGE:
611
+ revertLocalChange(sharedString, r);
612
+ break;
613
+ case IntervalOpType.PROPERTY_CHANGED:
614
+ revertLocalPropertyChanged(sharedString, r);
615
+ break;
616
+ case IntervalOpType.POSITION_REMOVE:
617
+ revertLocalSequenceRemove(sharedString, r);
618
+ break;
619
+ default:
620
+ unreachableCase(event);
621
+ }
622
+ } else {
623
+ revertMergeTreeDeltaRevertibles(sharedString, [r]);
624
+ }
625
+ }
626
+ }
package/src/sequence.ts CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  ReferenceType,
36
36
  MergeTreeRevertibleDriver,
37
37
  SegmentGroup,
38
+ SlidingPreference,
38
39
  } from "@fluidframework/merge-tree";
39
40
  import { ObjectStoragePartition, SummaryTreeBuilder } from "@fluidframework/runtime-utils";
40
41
  import {
@@ -51,6 +52,7 @@ import { ISummaryTreeWithStats, ITelemetryContext } from "@fluidframework/runtim
51
52
  import { DefaultMap, IMapOperation } from "./defaultMap";
52
53
  import { IMapMessageLocalMetadata, IValueChanged } from "./defaultMapInterfaces";
53
54
  import {
55
+ IIntervalCollection,
54
56
  IntervalCollection,
55
57
  SequenceInterval,
56
58
  SequenceIntervalCollectionValueType,
@@ -219,6 +221,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
219
221
  this.handle,
220
222
  (op, localOpMetadata) => this.submitLocalMessage(op, localOpMetadata),
221
223
  new SequenceIntervalCollectionValueType(),
224
+ dataStoreRuntime.options,
222
225
  );
223
226
  }
224
227
 
@@ -307,8 +310,15 @@ export abstract class SharedSegmentSequence<T extends ISegment>
307
310
  offset: number,
308
311
  refType: ReferenceType,
309
312
  properties: PropertySet | undefined,
313
+ slidingPreference?: SlidingPreference,
310
314
  ): LocalReferencePosition {
311
- return this.client.createLocalReferencePosition(segment, offset, refType, properties);
315
+ return this.client.createLocalReferencePosition(
316
+ segment,
317
+ offset,
318
+ refType,
319
+ properties,
320
+ slidingPreference,
321
+ );
312
322
  }
313
323
 
314
324
  /**
@@ -442,7 +452,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
442
452
  * Retrieves the interval collection keyed on `label`. If no such interval collection exists,
443
453
  * creates one.
444
454
  */
445
- public getIntervalCollection(label: string): IntervalCollection<SequenceInterval> {
455
+ public getIntervalCollection(label: string): IIntervalCollection<SequenceInterval> {
446
456
  return this.intervalCollections.get(label);
447
457
  }
448
458
 
@@ -21,6 +21,7 @@ import {
21
21
  import {
22
22
  Interval,
23
23
  IntervalCollection,
24
+ IIntervalCollection,
24
25
  IntervalCollectionValueType,
25
26
  ISerializableInterval,
26
27
  } from "./intervalCollection";
@@ -75,7 +76,7 @@ export class SharedIntervalCollectionFactory implements IChannelFactory {
75
76
  }
76
77
 
77
78
  export interface ISharedIntervalCollection<TInterval extends ISerializableInterval> {
78
- getIntervalCollection(label: string): IntervalCollection<TInterval>;
79
+ getIntervalCollection(label: string): IIntervalCollection<TInterval>;
79
80
  }
80
81
 
81
82
  /**
@@ -122,10 +123,11 @@ export class SharedIntervalCollection
122
123
  this.handle,
123
124
  (op, localOpMetadata) => this.submitLocalMessage(op, localOpMetadata),
124
125
  new IntervalCollectionValueType(),
126
+ runtime.options,
125
127
  );
126
128
  }
127
129
 
128
- public getIntervalCollection(label: string): IntervalCollection<Interval> {
130
+ public getIntervalCollection(label: string): IIntervalCollection<Interval> {
129
131
  const realLabel = this.getIntervalCollectionPath(label);
130
132
  const sharedCollection = this.intervalCollections.get(realLabel);
131
133
  return sharedCollection;