@fluidframework/merge-tree 1.0.1 → 1.1.0-75972

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/merge-tree",
3
- "version": "1.0.1",
3
+ "version": "1.1.0-75972",
4
4
  "description": "Merge tree",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -61,23 +61,23 @@
61
61
  "dependencies": {
62
62
  "@fluidframework/common-definitions": "^0.20.1",
63
63
  "@fluidframework/common-utils": "^0.32.1",
64
- "@fluidframework/container-definitions": "^1.0.1",
65
- "@fluidframework/container-utils": "^1.0.1",
66
- "@fluidframework/core-interfaces": "^1.0.1",
67
- "@fluidframework/datastore-definitions": "^1.0.1",
64
+ "@fluidframework/container-definitions": "1.1.0-75972",
65
+ "@fluidframework/container-utils": "1.1.0-75972",
66
+ "@fluidframework/core-interfaces": "1.1.0-75972",
67
+ "@fluidframework/datastore-definitions": "1.1.0-75972",
68
68
  "@fluidframework/protocol-definitions": "^0.1028.2000",
69
- "@fluidframework/runtime-definitions": "^1.0.1",
70
- "@fluidframework/runtime-utils": "^1.0.1",
71
- "@fluidframework/shared-object-base": "^1.0.1",
72
- "@fluidframework/telemetry-utils": "^1.0.1"
69
+ "@fluidframework/runtime-definitions": "1.1.0-75972",
70
+ "@fluidframework/runtime-utils": "1.1.0-75972",
71
+ "@fluidframework/shared-object-base": "1.1.0-75972",
72
+ "@fluidframework/telemetry-utils": "1.1.0-75972"
73
73
  },
74
74
  "devDependencies": {
75
- "@fluidframework/build-common": "^0.23.0",
75
+ "@fluidframework/build-common": "^0.24.0-0",
76
76
  "@fluidframework/build-tools": "^0.2.71273",
77
77
  "@fluidframework/eslint-config-fluid": "^0.28.2000",
78
- "@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@^0.58.0",
79
- "@fluidframework/mocha-test-setup": "^1.0.1",
80
- "@fluidframework/test-runtime-utils": "^1.0.1",
78
+ "@fluidframework/merge-tree-previous": "npm:@fluidframework/merge-tree@^1.0.0",
79
+ "@fluidframework/mocha-test-setup": "1.1.0-75972",
80
+ "@fluidframework/test-runtime-utils": "1.1.0-75972",
81
81
  "@microsoft/api-extractor": "^7.22.2",
82
82
  "@rushstack/eslint-config": "^2.5.1",
83
83
  "@types/diff": "^3.5.1",
@@ -98,82 +98,10 @@
98
98
  "typescript-formatter": "7.1.0"
99
99
  },
100
100
  "typeValidation": {
101
- "version": "1.0.0",
101
+ "version": "1.1.0",
102
102
  "broken": {
103
- "ClassDeclaration_BaseSegment": {
104
- "forwardCompat": false
105
- },
106
103
  "ClassDeclaration_Client": {
107
104
  "forwardCompat": false
108
- },
109
- "InterfaceDeclaration_IConsensusInfo": {
110
- "backCompat": false,
111
- "forwardCompat": false
112
- },
113
- "InterfaceDeclaration_IHierBlock": {
114
- "forwardCompat": false
115
- },
116
- "InterfaceDeclaration_IMergeBlock": {
117
- "forwardCompat": false
118
- },
119
- "TypeAliasDeclaration_IMergeNode": {
120
- "forwardCompat": false
121
- },
122
- "InterfaceDeclaration_IMergeNodeCommon": {
123
- "forwardCompat": false
124
- },
125
- "ClassDeclaration_IncrementalMapState": {
126
- "forwardCompat": false
127
- },
128
- "InterfaceDeclaration_InsertContext": {
129
- "forwardCompat": false
130
- },
131
- "InterfaceDeclaration_IRemovalInfo": {
132
- "forwardCompat": false
133
- },
134
- "InterfaceDeclaration_ISegmentChanges": {
135
- "forwardCompat": false
136
- },
137
- "ClassDeclaration_LocalReference": {
138
- "backCompat": false,
139
- "forwardCompat": false
140
- },
141
- "ClassDeclaration_LocalReferenceCollection": {
142
- "forwardCompat": false
143
- },
144
- "InterfaceDeclaration_LRUSegment": {
145
- "forwardCompat": false
146
- },
147
- "ClassDeclaration_Marker": {
148
- "backCompat": false,
149
- "forwardCompat": false
150
- },
151
- "ClassDeclaration_MergeBlock": {
152
- "forwardCompat": false
153
- },
154
- "ClassDeclaration_MergeNode": {
155
- "forwardCompat": false
156
- },
157
- "ClassDeclaration_MergeTree": {
158
- "forwardCompat": false
159
- },
160
- "ClassDeclaration_SnapshotLegacy": {
161
- "forwardCompat": false
162
- },
163
- "ClassDeclaration_TextSegment": {
164
- "forwardCompat": false
165
- },
166
- "ClassDeclaration_List": {
167
- "forwardCompat": false
168
- },
169
- "InterfaceDeclaration_IJSONMarkerSegment": {
170
- "backCompat": false
171
- },
172
- "InterfaceDeclaration_IMarkerDef": {
173
- "backCompat": false
174
- },
175
- "EnumDeclaration_ReferenceType": {
176
- "backCompat": false
177
105
  }
178
106
  }
179
107
  }
package/src/client.ts CHANGED
@@ -16,7 +16,7 @@ import { LoggingError } from "@fluidframework/telemetry-utils";
16
16
  import { IIntegerRange } from "./base";
17
17
  import { RedBlackTree } from "./collections";
18
18
  import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants";
19
- import { LocalReference, _validateReferenceType } from "./localReference";
19
+ import { LocalReference, LocalReferencePosition } from "./localReference";
20
20
  import {
21
21
  CollaborationWindow,
22
22
  compareStrings,
@@ -53,7 +53,7 @@ import { SnapshotLegacy } from "./snapshotlegacy";
53
53
  import { SnapshotLoader } from "./snapshotLoader";
54
54
  import { MergeTreeTextHelper } from "./textSegment";
55
55
  import { SnapshotV1 } from "./snapshotV1";
56
- import { ReferencePosition, RangeStackMap, DetachedReferencePosition } from "./referencePositions";
56
+ import { ReferencePosition, RangeStackMap } from "./referencePositions";
57
57
  import {
58
58
  IMergeTreeClientSequenceArgs,
59
59
  IMergeTreeDeltaOpArgs,
@@ -333,21 +333,17 @@ export class Client {
333
333
  }
334
334
 
335
335
  public createLocalReferencePosition(
336
- segment: ISegment, offset: number, refType: ReferenceType, properties: PropertySet | undefined,
337
- ): ReferencePosition {
336
+ segment: ISegment, offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined,
337
+ ): LocalReferencePosition {
338
338
  return this.mergeTree.createLocalReferencePosition(segment, offset, refType, properties, this);
339
339
  }
340
340
 
341
- public removeLocalReferencePosition(lref: ReferencePosition) {
341
+ public removeLocalReferencePosition(lref: LocalReferencePosition) {
342
342
  return this.mergeTree.removeLocalReferencePosition(lref);
343
343
  }
344
344
 
345
345
  public localReferencePositionToPosition(lref: ReferencePosition) {
346
- const segment = lref.getSegment();
347
- if (segment === undefined) {
348
- return DetachedReferencePosition;
349
- }
350
- return this.getPosition(segment) + lref.getOffset();
346
+ return this.mergeTree.referencePositionToLocalPosition(lref);
351
347
  }
352
348
 
353
349
  /**
@@ -713,6 +709,61 @@ export class Client {
713
709
  return segmentPosition;
714
710
  }
715
711
 
712
+ /**
713
+ * Rebases a (local) position from the perspective `{ seq: seqNumberFrom, localSeq }` to the perspective
714
+ * of the current sequence number. This is desirable when rebasing operations for reconnection.
715
+ *
716
+ * If the position refers to a segment/offset that was removed by some operation between `seqNumberFrom` and
717
+ * the current sequence number, the returned position will align with the position of a reference given
718
+ * `SlideOnRemove` semantics.
719
+ */
720
+ public rebasePosition(
721
+ pos: number,
722
+ seqNumberFrom: number,
723
+ localSeq: number,
724
+ ): number {
725
+ assert(localSeq <= this.mergeTree.collabWindow.localSeq, 0x300 /* localSeq greater than collab window */);
726
+ let segment: ISegment | undefined;
727
+ let posAccumulated = 0;
728
+ let offset = pos;
729
+ const isInsertedInView = (seg: ISegment) =>
730
+ (seg.seq !== undefined && seg.seq !== UnassignedSequenceNumber && seg.seq <= seqNumberFrom)
731
+ || (seg.localSeq !== undefined && seg.localSeq <= localSeq);
732
+
733
+ const isRemovedFromView = ({ removedSeq, localRemovedSeq }: ISegment) =>
734
+ (removedSeq !== undefined && removedSeq !== UnassignedSequenceNumber && removedSeq <= seqNumberFrom)
735
+ || (localRemovedSeq !== undefined && localRemovedSeq <= localSeq);
736
+
737
+ this.mergeTree.walkAllSegments(this.mergeTree.root, (seg) => {
738
+ assert(seg.seq !== undefined || seg.localSeq !== undefined,
739
+ 0x301 /* Either seq or localSeq should be defined */);
740
+ segment = seg;
741
+
742
+ if (isInsertedInView(seg) && !isRemovedFromView(seg)) {
743
+ posAccumulated += seg.cachedLength;
744
+ if (offset >= seg.cachedLength) {
745
+ offset -= seg.cachedLength;
746
+ }
747
+ }
748
+
749
+ // Keep going while we've yet to reach the segment at the desired position
750
+ return posAccumulated <= pos;
751
+ });
752
+
753
+ assert(segment !== undefined, 0x302 /* No segment found */);
754
+ const seqNumberTo = this.getCollabWindow().currentSeq;
755
+ if ((segment.removedSeq !== undefined &&
756
+ segment.removedSeq !== UnassignedSequenceNumber &&
757
+ segment.removedSeq <= seqNumberTo)
758
+ || (segment.localRemovedSeq !== undefined && segment.localRemovedSeq <= localSeq)) {
759
+ // Segment that the position was in has been removed: null out offset.
760
+ offset = 0;
761
+ }
762
+
763
+ assert(0 <= offset && offset < segment.cachedLength, 0x303 /* Invalid offset */);
764
+ return this.findReconnectionPosition(segment, localSeq) + offset;
765
+ }
766
+
716
767
  private resetPendingDeltaToOps(
717
768
  resetOp: IMergeTreeDeltaOp,
718
769
  segmentGroup: SegmentGroup): IMergeTreeDeltaOp[] {
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ export * from "./base";
7
7
  export * from "./client";
8
8
  export * from "./collections";
9
9
  export * from "./constants";
10
- export { LocalReference, LocalReferenceCollection } from "./localReference";
10
+ export { LocalReference, LocalReferencePosition, LocalReferenceCollection } from "./localReference";
11
11
  export * from "./mergeTree";
12
12
  export * from "./mergeTreeDeltaCallback";
13
13
  export * from "./mergeTreeTracking";
@@ -46,10 +46,14 @@ export function _validateReferenceType(refType: ReferenceType) {
46
46
  }
47
47
  }
48
48
 
49
+ export interface LocalReferencePosition extends ReferencePosition {
50
+ callbacks?: Partial<Record<"beforeSlide" | "afterSlide", () => void>>;
51
+ }
52
+
49
53
  /**
50
- * @deprecated - Use ReferencePosition
54
+ * @deprecated - Use LocalReferencePosition
51
55
  */
52
- export class LocalReference implements ReferencePosition {
56
+ export class LocalReference implements LocalReferencePosition {
53
57
  /**
54
58
  * @deprecated - use DetachedReferencePosition
55
59
  */
@@ -65,6 +69,8 @@ export class LocalReference implements ReferencePosition {
65
69
  */
66
70
  public segment: ISegment | undefined;
67
71
 
72
+ public callbacks?: Partial<Record<"beforeSlide" | "afterSlide", () => void>> | undefined;
73
+
68
74
  /**
69
75
  * @deprecated - use createReferencePosition
70
76
  */
@@ -166,9 +172,6 @@ export class LocalReference implements ReferencePosition {
166
172
  }
167
173
 
168
174
  public getOffset() {
169
- if (this.segment?.removedSeq) {
170
- return 0;
171
- }
172
175
  return this.offset;
173
176
  }
174
177
 
@@ -308,7 +311,7 @@ export class LocalReferenceCollection {
308
311
  * @internal - this method should only be called by mergeTree
309
312
  */
310
313
  public createLocalRef(
311
- offset: number,
314
+ offset: number | undefined,
312
315
  refType: ReferenceType,
313
316
  properties: PropertySet | undefined,
314
317
  client: Client): ReferencePosition {
package/src/mergeTree.ts CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  UnassignedSequenceNumber,
25
25
  UniversalSequenceNumber,
26
26
  } from "./constants";
27
- import { LocalReference, LocalReferenceCollection } from "./localReference";
27
+ import { LocalReference, LocalReferenceCollection, LocalReferencePosition } from "./localReference";
28
28
  import {
29
29
  IMergeTreeDeltaOpArgs,
30
30
  IMergeTreeSegmentDelta,
@@ -1431,32 +1431,31 @@ export class MergeTree {
1431
1431
  if (!segoff.segment || !isRemovedAndAcked(segoff.segment)) {
1432
1432
  return segoff;
1433
1433
  }
1434
- // Slide to the next farthest valid segment in the tree. If no such segment is found
1435
- // slide to the last valid segment.
1436
- // TODO this walks the whole tree to find the segment - could write a more efficient
1437
- // walk that starts at the segment
1438
- let foundStart = false;
1439
- let foundSegmentPastStart = false;
1440
1434
  let slideToSegment: ISegment | undefined;
1441
- this.walkAllSegments(this.root, (seg) => {
1435
+ const goFurtherToFindSlideToSegment = (seg) => {
1442
1436
  if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAcked(seg)) {
1443
1437
  slideToSegment = seg;
1444
- if (foundStart) {
1445
- foundSegmentPastStart = true;
1446
- return false;
1447
- }
1448
- }
1449
- if (!foundStart && seg === segoff.segment) {
1450
- foundStart = true;
1438
+ return false;
1451
1439
  }
1452
1440
  return true;
1453
- });
1454
- let offset = 0;
1455
- if (slideToSegment && !foundSegmentPastStart) {
1441
+ };
1442
+ // Slide to the next farthest valid segment in the tree.
1443
+ this.rightExcursion(segoff.segment, goFurtherToFindSlideToSegment);
1444
+ if (slideToSegment) {
1445
+ return { segment: slideToSegment, offset: 0 };
1446
+ }
1447
+ // If no such segment is found, slide to the last valid segment.
1448
+ this.leftExcursion(segoff.segment, goFurtherToFindSlideToSegment);
1449
+
1450
+ // Workaround TypeScript issue (https://github.com/microsoft/TypeScript/issues/9998)
1451
+ slideToSegment = slideToSegment as ISegment | undefined;
1452
+
1453
+ if (slideToSegment) {
1456
1454
  // If slid nearer then offset should be at the end of the segment
1457
- offset = slideToSegment.cachedLength - 1;
1455
+ return { segment: slideToSegment, offset: slideToSegment.cachedLength - 1 };
1458
1456
  }
1459
- return { segment: slideToSegment, offset };
1457
+
1458
+ return { segment: undefined, offset: 0 };
1460
1459
  }
1461
1460
 
1462
1461
  /**
@@ -1476,6 +1475,7 @@ export class MergeTree {
1476
1475
  newSegment.localRefs = new LocalReferenceCollection(newSegment);
1477
1476
  }
1478
1477
  for (const ref of refsToSlide) {
1478
+ ref.callbacks?.beforeSlide?.();
1479
1479
  const removedRef = segment.localRefs.removeLocalRef(ref);
1480
1480
  assert(ref === removedRef, 0x2f3 /* Ref not in the segment localRefs */);
1481
1481
  if (!newSegment) {
@@ -1488,6 +1488,7 @@ export class MergeTree {
1488
1488
  assert(!!newSegment.localRefs, 0x2f4 /* localRefs must be allocated */);
1489
1489
  newSegment.localRefs.addLocalRef(ref);
1490
1490
  }
1491
+ ref.callbacks?.afterSlide?.();
1491
1492
  }
1492
1493
  // TODO is it required to update the path lengths?
1493
1494
  if (newSegment) {
@@ -1787,11 +1788,10 @@ export class MergeTree {
1787
1788
  if (pendingSegmentGroup !== undefined) {
1788
1789
  const deltaSegments: IMergeTreeSegmentDelta[] = [];
1789
1790
  pendingSegmentGroup.segments.map((pendingSegment) => {
1790
- const modified = pendingSegment.ack(pendingSegmentGroup, opArgs, this);
1791
- // This computation of overwrite appears incorrect. Leaving as is to avoid breaking something.
1792
- overwrite = !modified || overwrite;
1791
+ const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs, this);
1792
+ overwrite = overlappingRemove || overwrite;
1793
1793
 
1794
- if (modified && opArgs.op.type === MergeTreeDeltaType.REMOVE) {
1794
+ if (!overlappingRemove && opArgs.op.type === MergeTreeDeltaType.REMOVE) {
1795
1795
  this.updateSegmentRefsAfterMarkRemoved(pendingSegment, false);
1796
1796
  }
1797
1797
  if (MergeTree.options.zamboniSegments) {
@@ -2533,7 +2533,7 @@ export class MergeTree {
2533
2533
  }
2534
2534
  }
2535
2535
 
2536
- public removeLocalReferencePosition(lref: ReferencePosition): ReferencePosition | undefined {
2536
+ public removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined {
2537
2537
  const segment = lref.getSegment();
2538
2538
  if (segment) {
2539
2539
  const removedRefs = segment?.localRefs?.removeLocalRef(lref);
@@ -2545,9 +2545,9 @@ export class MergeTree {
2545
2545
  }
2546
2546
  }
2547
2547
  public createLocalReferencePosition(
2548
- segment: ISegment, offset: number, refType: ReferenceType, properties: PropertySet | undefined,
2548
+ segment: ISegment, offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined,
2549
2549
  client: Client,
2550
- ): ReferencePosition {
2550
+ ): LocalReferencePosition {
2551
2551
  if (isRemoved(segment)) {
2552
2552
  if (!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove)) {
2553
2553
  throw new UsageError("Can only create SlideOnRemove local reference position on a removed segment");