@fluidframework/merge-tree 0.59.4001 → 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.
Files changed (80) hide show
  1. package/.eslintrc.js +1 -1
  2. package/README.md +1 -1
  3. package/REFERENCEPOSITIONS.md +199 -0
  4. package/dist/client.d.ts +30 -4
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +89 -47
  7. package/dist/client.js.map +1 -1
  8. package/dist/collections.d.ts +5 -4
  9. package/dist/collections.d.ts.map +1 -1
  10. package/dist/collections.js +17 -18
  11. package/dist/collections.js.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +4 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/localReference.d.ts +11 -3
  17. package/dist/localReference.d.ts.map +1 -1
  18. package/dist/localReference.js +25 -8
  19. package/dist/localReference.js.map +1 -1
  20. package/dist/mergeTree.d.ts +23 -3
  21. package/dist/mergeTree.d.ts.map +1 -1
  22. package/dist/mergeTree.js +136 -48
  23. package/dist/mergeTree.js.map +1 -1
  24. package/dist/mergeTreeDeltaCallback.d.ts +8 -10
  25. package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
  26. package/dist/mergeTreeDeltaCallback.js +6 -10
  27. package/dist/mergeTreeDeltaCallback.js.map +1 -1
  28. package/dist/opBuilder.js +6 -5
  29. package/dist/opBuilder.js.map +1 -1
  30. package/dist/ops.d.ts +12 -10
  31. package/dist/ops.d.ts.map +1 -1
  32. package/dist/ops.js +7 -7
  33. package/dist/ops.js.map +1 -1
  34. package/dist/referencePositions.d.ts +1 -1
  35. package/dist/referencePositions.d.ts.map +1 -1
  36. package/dist/referencePositions.js +3 -2
  37. package/dist/referencePositions.js.map +1 -1
  38. package/lib/client.d.ts +30 -4
  39. package/lib/client.d.ts.map +1 -1
  40. package/lib/client.js +89 -47
  41. package/lib/client.js.map +1 -1
  42. package/lib/collections.d.ts +5 -4
  43. package/lib/collections.d.ts.map +1 -1
  44. package/lib/collections.js +17 -18
  45. package/lib/collections.js.map +1 -1
  46. package/lib/index.d.ts +1 -1
  47. package/lib/index.d.ts.map +1 -1
  48. package/lib/index.js +1 -1
  49. package/lib/index.js.map +1 -1
  50. package/lib/localReference.d.ts +11 -3
  51. package/lib/localReference.d.ts.map +1 -1
  52. package/lib/localReference.js +23 -7
  53. package/lib/localReference.js.map +1 -1
  54. package/lib/mergeTree.d.ts +23 -3
  55. package/lib/mergeTree.d.ts.map +1 -1
  56. package/lib/mergeTree.js +137 -49
  57. package/lib/mergeTree.js.map +1 -1
  58. package/lib/mergeTreeDeltaCallback.d.ts +8 -10
  59. package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
  60. package/lib/mergeTreeDeltaCallback.js +6 -10
  61. package/lib/mergeTreeDeltaCallback.js.map +1 -1
  62. package/lib/opBuilder.js +6 -5
  63. package/lib/opBuilder.js.map +1 -1
  64. package/lib/ops.d.ts +12 -10
  65. package/lib/ops.d.ts.map +1 -1
  66. package/lib/ops.js +7 -7
  67. package/lib/ops.js.map +1 -1
  68. package/lib/referencePositions.d.ts +1 -1
  69. package/lib/referencePositions.d.ts.map +1 -1
  70. package/lib/referencePositions.js +3 -2
  71. package/lib/referencePositions.js.map +1 -1
  72. package/package.json +17 -94
  73. package/src/client.ts +87 -27
  74. package/src/collections.ts +5 -4
  75. package/src/index.ts +1 -1
  76. package/src/localReference.ts +33 -8
  77. package/src/mergeTree.ts +136 -43
  78. package/src/mergeTreeDeltaCallback.ts +8 -10
  79. package/src/ops.ts +13 -10
  80. package/src/referencePositions.ts +3 -2
package/lib/client.js CHANGED
@@ -10,11 +10,11 @@ import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants";
10
10
  import { LocalReference } from "./localReference";
11
11
  import { compareStrings, MergeTree, } from "./mergeTree";
12
12
  import { createAnnotateMarkerOp, createAnnotateRangeOp, createGroupOp, createInsertSegmentOp, createRemoveRangeOp, } from "./opBuilder";
13
+ import { MergeTreeDeltaType, } from "./ops";
13
14
  import { SnapshotLegacy } from "./snapshotlegacy";
14
15
  import { SnapshotLoader } from "./snapshotLoader";
15
16
  import { MergeTreeTextHelper } from "./textSegment";
16
17
  import { SnapshotV1 } from "./snapshotV1";
17
- import { DetachedReferencePosition } from "./referencePositions";
18
18
  function elapsedMicroseconds(trace) {
19
19
  return trace.trace().duration * 1000;
20
20
  }
@@ -221,11 +221,7 @@ export class Client {
221
221
  return this.mergeTree.removeLocalReferencePosition(lref);
222
222
  }
223
223
  localReferencePositionToPosition(lref) {
224
- const segment = lref.getSegment();
225
- if (segment === undefined) {
226
- return DetachedReferencePosition;
227
- }
228
- return this.getPosition(segment) + lref.getOffset();
224
+ return this.mergeTree.referencePositionToLocalPosition(lref);
229
225
  }
230
226
  /**
231
227
  * Given a position specified relative to a marker id, lookup the marker
@@ -244,7 +240,7 @@ export class Client {
244
240
  * @returns True if the remove was applied. False if it could not be.
245
241
  */
246
242
  applyRemoveRangeOp(opArgs) {
247
- assert(opArgs.op.type === 1 /* REMOVE */, 0x02d /* "Unexpected op type on range remove!" */);
243
+ assert(opArgs.op.type === MergeTreeDeltaType.REMOVE, 0x02d /* "Unexpected op type on range remove!" */);
248
244
  const op = opArgs.op;
249
245
  const clientArgs = this.getClientSequenceArgs(opArgs);
250
246
  const range = this.getValidOpRange(op, clientArgs);
@@ -265,7 +261,7 @@ export class Client {
265
261
  * @returns True if the annotate was applied. False if it could not be.
266
262
  */
267
263
  applyAnnotateRangeOp(opArgs) {
268
- assert(opArgs.op.type === 2 /* ANNOTATE */, 0x02e /* "Unexpected op type on range annotate!" */);
264
+ assert(opArgs.op.type === MergeTreeDeltaType.ANNOTATE, 0x02e /* "Unexpected op type on range annotate!" */);
269
265
  const op = opArgs.op;
270
266
  const clientArgs = this.getClientSequenceArgs(opArgs);
271
267
  const range = this.getValidOpRange(op, clientArgs);
@@ -286,7 +282,7 @@ export class Client {
286
282
  * @returns True if the insert was applied. False if it could not be.
287
283
  */
288
284
  applyInsertOp(opArgs) {
289
- assert(opArgs.op.type === 0 /* INSERT */, 0x02f /* "Unexpected op type on range insert!" */);
285
+ assert(opArgs.op.type === MergeTreeDeltaType.INSERT, 0x02f /* "Unexpected op type on range insert!" */);
290
286
  const op = opArgs.op;
291
287
  const clientArgs = this.getClientSequenceArgs(opArgs);
292
288
  const range = this.getValidOpRange(op, clientArgs);
@@ -355,12 +351,12 @@ export class Client {
355
351
  if (start === undefined
356
352
  || start < 0
357
353
  || start > length
358
- || start === length && op.type !== 0 /* INSERT */) {
354
+ || start === length && op.type !== MergeTreeDeltaType.INSERT) {
359
355
  invalidPositions.push("start");
360
356
  }
361
357
  // Validate end if not insert, or insert has end
362
358
  //
363
- if (op.type !== 0 /* INSERT */ || end !== undefined) {
359
+ if (op.type !== MergeTreeDeltaType.INSERT || end !== undefined) {
364
360
  if (end === undefined || end <= start) {
365
361
  invalidPositions.push("end");
366
362
  }
@@ -386,13 +382,13 @@ export class Client {
386
382
  }
387
383
  /**
388
384
  * Gets the client args from the op if remote, otherwise uses the local clients info
389
- * @param opArgs - The op arg to get the client sequence args for
385
+ * @param sequencedMessage - The sequencedMessage to get the client sequence args for
390
386
  */
391
- getClientSequenceArgs(opArgs) {
387
+ getClientSequenceArgsForMessage(sequencedMessage) {
392
388
  // If there this no sequenced message, then the op is local
393
389
  // and unacked, so use this clients sequenced args
394
390
  //
395
- if (!opArgs.sequencedMessage) {
391
+ if (!sequencedMessage) {
396
392
  const segWindow = this.getCollabWindow();
397
393
  return {
398
394
  clientId: segWindow.clientId,
@@ -402,12 +398,19 @@ export class Client {
402
398
  }
403
399
  else {
404
400
  return {
405
- clientId: this.getShortClientId(opArgs.sequencedMessage.clientId),
406
- referenceSequenceNumber: opArgs.sequencedMessage.referenceSequenceNumber,
407
- sequenceNumber: opArgs.sequencedMessage.sequenceNumber,
401
+ clientId: this.getOrAddShortClientId(sequencedMessage.clientId),
402
+ referenceSequenceNumber: sequencedMessage.referenceSequenceNumber,
403
+ sequenceNumber: sequencedMessage.sequenceNumber,
408
404
  };
409
405
  }
410
406
  }
407
+ /**
408
+ * Gets the client args from the op if remote, otherwise uses the local clients info
409
+ * @param opArgs - The op arg to get the client sequence args for
410
+ */
411
+ getClientSequenceArgs(opArgs) {
412
+ return this.getClientSequenceArgsForMessage(opArgs.sequencedMessage);
413
+ }
411
414
  ackPendingSegment(opArgs) {
412
415
  const ackOp = (deltaOpArgs) => {
413
416
  let trace;
@@ -415,7 +418,7 @@ export class Client {
415
418
  trace = Trace.start();
416
419
  }
417
420
  this.mergeTree.ackPendingSegment(deltaOpArgs);
418
- if (deltaOpArgs.op.type === 2 /* ANNOTATE */) {
421
+ if (deltaOpArgs.op.type === MergeTreeDeltaType.ANNOTATE) {
419
422
  if (deltaOpArgs.op.combiningOp && (deltaOpArgs.op.combiningOp.name === "consensus")) {
420
423
  this.updateConsensusProperty(deltaOpArgs.op, deltaOpArgs.sequencedMessage);
421
424
  }
@@ -426,7 +429,7 @@ export class Client {
426
429
  this.accumWindow += (this.getCurrentSeq() - this.getCollabWindow().minSeq);
427
430
  }
428
431
  };
429
- if (opArgs.op.type === 3 /* GROUP */) {
432
+ if (opArgs.op.type === MergeTreeDeltaType.GROUP) {
430
433
  for (const memberOp of opArgs.op.ops) {
431
434
  ackOp({
432
435
  groupOp: opArgs.op,
@@ -507,6 +510,47 @@ export class Client {
507
510
  });
508
511
  return segmentPosition;
509
512
  }
513
+ /**
514
+ * Rebases a (local) position from the perspective `{ seq: seqNumberFrom, localSeq }` to the perspective
515
+ * of the current sequence number. This is desirable when rebasing operations for reconnection.
516
+ *
517
+ * If the position refers to a segment/offset that was removed by some operation between `seqNumberFrom` and
518
+ * the current sequence number, the returned position will align with the position of a reference given
519
+ * `SlideOnRemove` semantics.
520
+ */
521
+ rebasePosition(pos, seqNumberFrom, localSeq) {
522
+ assert(localSeq <= this.mergeTree.collabWindow.localSeq, 0x300 /* localSeq greater than collab window */);
523
+ let segment;
524
+ let posAccumulated = 0;
525
+ let offset = pos;
526
+ const isInsertedInView = (seg) => (seg.seq !== undefined && seg.seq !== UnassignedSequenceNumber && seg.seq <= seqNumberFrom)
527
+ || (seg.localSeq !== undefined && seg.localSeq <= localSeq);
528
+ const isRemovedFromView = ({ removedSeq, localRemovedSeq }) => (removedSeq !== undefined && removedSeq !== UnassignedSequenceNumber && removedSeq <= seqNumberFrom)
529
+ || (localRemovedSeq !== undefined && localRemovedSeq <= localSeq);
530
+ this.mergeTree.walkAllSegments(this.mergeTree.root, (seg) => {
531
+ assert(seg.seq !== undefined || seg.localSeq !== undefined, 0x301 /* Either seq or localSeq should be defined */);
532
+ segment = seg;
533
+ if (isInsertedInView(seg) && !isRemovedFromView(seg)) {
534
+ posAccumulated += seg.cachedLength;
535
+ if (offset >= seg.cachedLength) {
536
+ offset -= seg.cachedLength;
537
+ }
538
+ }
539
+ // Keep going while we've yet to reach the segment at the desired position
540
+ return posAccumulated <= pos;
541
+ });
542
+ assert(segment !== undefined, 0x302 /* No segment found */);
543
+ const seqNumberTo = this.getCollabWindow().currentSeq;
544
+ if ((segment.removedSeq !== undefined &&
545
+ segment.removedSeq !== UnassignedSequenceNumber &&
546
+ segment.removedSeq <= seqNumberTo)
547
+ || (segment.localRemovedSeq !== undefined && segment.localRemovedSeq <= localSeq)) {
548
+ // Segment that the position was in has been removed: null out offset.
549
+ offset = 0;
550
+ }
551
+ assert(0 <= offset && offset < segment.cachedLength, 0x303 /* Invalid offset */);
552
+ return this.findReconnectionPosition(segment, localSeq) + offset;
553
+ }
510
554
  resetPendingDeltaToOps(resetOp, segmentGroup) {
511
555
  var _a, _b;
512
556
  assert(!!segmentGroup, 0x033 /* "Segment group undefined" */);
@@ -524,7 +568,7 @@ export class Client {
524
568
  const segmentPosition = this.findReconnectionPosition(segment, segmentGroup.localSeq);
525
569
  let newOp;
526
570
  switch (resetOp.type) {
527
- case 2 /* ANNOTATE */:
571
+ case MergeTreeDeltaType.ANNOTATE:
528
572
  assert(((_b = segment.propertyManager) === null || _b === void 0 ? void 0 : _b.hasPendingProperties()) === true, 0x036 /* "Segment has no pending properties" */);
529
573
  // if the segment has been removed, there's no need to send the annotate op
530
574
  // unless the remove was local, in which case the annotate must have come
@@ -533,11 +577,11 @@ export class Client {
533
577
  newOp = createAnnotateRangeOp(segmentPosition, segmentPosition + segment.cachedLength, resetOp.props, resetOp.combiningOp);
534
578
  }
535
579
  break;
536
- case 0 /* INSERT */:
580
+ case MergeTreeDeltaType.INSERT:
537
581
  assert(segment.seq === UnassignedSequenceNumber, 0x037 /* "Segment already has assigned sequence number" */);
538
582
  newOp = createInsertSegmentOp(segmentPosition, segment);
539
583
  break;
540
- case 1 /* REMOVE */:
584
+ case MergeTreeDeltaType.REMOVE:
541
585
  if (segment.localRemovedSeq !== undefined) {
542
586
  newOp = createRemoveRangeOp(segmentPosition, segmentPosition + segment.cachedLength);
543
587
  }
@@ -559,16 +603,16 @@ export class Client {
559
603
  const msg = opArgs.sequencedMessage;
560
604
  this.getOrAddShortClientId(msg.clientId);
561
605
  switch (op.type) {
562
- case 0 /* INSERT */:
606
+ case MergeTreeDeltaType.INSERT:
563
607
  this.applyInsertOp(opArgs);
564
608
  break;
565
- case 1 /* REMOVE */:
609
+ case MergeTreeDeltaType.REMOVE:
566
610
  this.applyRemoveRangeOp(opArgs);
567
611
  break;
568
- case 2 /* ANNOTATE */:
612
+ case MergeTreeDeltaType.ANNOTATE:
569
613
  this.applyAnnotateRangeOp(opArgs);
570
614
  break;
571
- case 3 /* GROUP */: {
615
+ case MergeTreeDeltaType.GROUP: {
572
616
  for (const memberOp of op.ops) {
573
617
  this.applyRemoteOp({
574
618
  op: memberOp,
@@ -585,19 +629,19 @@ export class Client {
585
629
  applyStashedOp(op) {
586
630
  let metadata;
587
631
  switch (op.type) {
588
- case 0 /* INSERT */:
632
+ case MergeTreeDeltaType.INSERT:
589
633
  this.applyInsertOp({ op });
590
634
  metadata = this.peekPendingSegmentGroups();
591
635
  break;
592
- case 1 /* REMOVE */:
636
+ case MergeTreeDeltaType.REMOVE:
593
637
  this.applyRemoveRangeOp({ op });
594
638
  metadata = this.peekPendingSegmentGroups();
595
639
  break;
596
- case 2 /* ANNOTATE */:
640
+ case MergeTreeDeltaType.ANNOTATE:
597
641
  this.applyAnnotateRangeOp({ op });
598
642
  metadata = this.peekPendingSegmentGroups();
599
643
  break;
600
- case 3 /* GROUP */:
644
+ case MergeTreeDeltaType.GROUP:
601
645
  return op.ops.map((o) => this.applyStashedOp(o));
602
646
  default:
603
647
  unreachableCase(op, "unrecognized op type");
@@ -652,7 +696,7 @@ export class Client {
652
696
  */
653
697
  regeneratePendingOp(resetOp, segmentGroup) {
654
698
  const opList = [];
655
- if (resetOp.type === 3 /* GROUP */) {
699
+ if (resetOp.type === MergeTreeDeltaType.GROUP) {
656
700
  if (Array.isArray(segmentGroup)) {
657
701
  assert(resetOp.ops.length === segmentGroup.length, 0x03a /* "Number of ops in 'resetOp' must match the number of segment groups provided." */);
658
702
  for (let i = 0; i < resetOp.ops.length; i++) {
@@ -667,7 +711,7 @@ export class Client {
667
711
  }
668
712
  }
669
713
  else {
670
- assert(resetOp.type !== 3 /* GROUP */, 0x03c /* "Reset op has 'group' delta type!" */);
714
+ assert(resetOp.type !== MergeTreeDeltaType.GROUP, 0x03c /* "Reset op has 'group' delta type!" */);
671
715
  assert(!Array.isArray(segmentGroup), 0x03d /* "segmentGroup is array rather than singleton!" */);
672
716
  opList.push(...this.resetPendingDeltaToOps(resetOp, segmentGroup));
673
717
  }
@@ -724,13 +768,13 @@ export class Client {
724
768
  groupOp,
725
769
  };
726
770
  switch (op.type) {
727
- case 0 /* INSERT */:
771
+ case MergeTreeDeltaType.INSERT:
728
772
  this.applyInsertOp(opArgs);
729
773
  break;
730
- case 2 /* ANNOTATE */:
774
+ case MergeTreeDeltaType.ANNOTATE:
731
775
  this.applyAnnotateRangeOp(opArgs);
732
776
  break;
733
- case 1 /* REMOVE */:
777
+ case MergeTreeDeltaType.REMOVE:
734
778
  this.applyRemoveRangeOp(opArgs);
735
779
  break;
736
780
  default:
@@ -761,18 +805,16 @@ export class Client {
761
805
  }
762
806
  }
763
807
  getContainingSegment(pos, op) {
764
- let seq;
765
- let clientId;
766
- if (op) {
767
- clientId = this.getOrAddShortClientId(op.clientId);
768
- seq = op.referenceSequenceNumber;
769
- }
770
- else {
771
- const segWindow = this.mergeTree.getCollabWindow();
772
- seq = segWindow.currentSeq;
773
- clientId = segWindow.clientId;
774
- }
775
- return this.mergeTree.getContainingSegment(pos, seq, clientId);
808
+ const args = this.getClientSequenceArgsForMessage(op);
809
+ return this.mergeTree.getContainingSegment(pos, args.referenceSequenceNumber, args.clientId);
810
+ }
811
+ /**
812
+ * Returns the position to slide a reference to if a slide is required.
813
+ * @param segoff - The segment and offset to slide from
814
+ * @returns - segment and offset to slide the reference to
815
+ */
816
+ getSlideToSegment(segoff) {
817
+ return this.mergeTree._getSlideToSegment(segoff);
776
818
  }
777
819
  getPropertiesAtPosition(pos) {
778
820
  let propertiesAtPosition;