@fluidframework/merge-tree 0.59.4002 → 1.0.2

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 +127 -0
  4. package/dist/client.d.ts +17 -0
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +47 -41
  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 +4 -0
  17. package/dist/localReference.d.ts.map +1 -1
  18. package/dist/localReference.js +24 -3
  19. package/dist/localReference.js.map +1 -1
  20. package/dist/mergeTree.d.ts +20 -0
  21. package/dist/mergeTree.d.ts.map +1 -1
  22. package/dist/mergeTree.js +139 -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 +17 -0
  39. package/lib/client.d.ts.map +1 -1
  40. package/lib/client.js +47 -41
  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 +4 -0
  51. package/lib/localReference.d.ts.map +1 -1
  52. package/lib/localReference.js +22 -2
  53. package/lib/localReference.js.map +1 -1
  54. package/lib/mergeTree.d.ts +20 -0
  55. package/lib/mergeTree.d.ts.map +1 -1
  56. package/lib/mergeTree.js +140 -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 +69 -74
  73. package/src/client.ts +27 -18
  74. package/src/collections.ts +5 -4
  75. package/src/index.ts +1 -1
  76. package/src/localReference.ts +24 -2
  77. package/src/mergeTree.ts +133 -39
  78. package/src/mergeTreeDeltaCallback.ts +8 -10
  79. package/src/ops.ts +13 -10
  80. package/src/referencePositions.ts +3 -2
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 } from "./localReference";
19
+ import { LocalReference, _validateReferenceType } from "./localReference";
20
20
  import {
21
21
  CollaborationWindow,
22
22
  compareStrings,
@@ -575,13 +575,14 @@ export class Client {
575
575
 
576
576
  /**
577
577
  * Gets the client args from the op if remote, otherwise uses the local clients info
578
- * @param opArgs - The op arg to get the client sequence args for
578
+ * @param sequencedMessage - The sequencedMessage to get the client sequence args for
579
579
  */
580
- private getClientSequenceArgs(opArgs: IMergeTreeDeltaOpArgs): IMergeTreeClientSequenceArgs {
580
+ private getClientSequenceArgsForMessage(sequencedMessage: ISequencedDocumentMessage | undefined):
581
+ IMergeTreeClientSequenceArgs {
581
582
  // If there this no sequenced message, then the op is local
582
583
  // and unacked, so use this clients sequenced args
583
584
  //
584
- if (!opArgs.sequencedMessage) {
585
+ if (!sequencedMessage) {
585
586
  const segWindow = this.getCollabWindow();
586
587
  return {
587
588
  clientId: segWindow.clientId,
@@ -590,13 +591,21 @@ export class Client {
590
591
  };
591
592
  } else {
592
593
  return {
593
- clientId: this.getShortClientId(opArgs.sequencedMessage.clientId),
594
- referenceSequenceNumber: opArgs.sequencedMessage.referenceSequenceNumber,
595
- sequenceNumber: opArgs.sequencedMessage.sequenceNumber,
594
+ clientId: this.getOrAddShortClientId(sequencedMessage.clientId),
595
+ referenceSequenceNumber: sequencedMessage.referenceSequenceNumber,
596
+ sequenceNumber: sequencedMessage.sequenceNumber,
596
597
  };
597
598
  }
598
599
  }
599
600
 
601
+ /**
602
+ * Gets the client args from the op if remote, otherwise uses the local clients info
603
+ * @param opArgs - The op arg to get the client sequence args for
604
+ */
605
+ private getClientSequenceArgs(opArgs: IMergeTreeDeltaOpArgs): IMergeTreeClientSequenceArgs {
606
+ return this.getClientSequenceArgsForMessage(opArgs.sequencedMessage);
607
+ }
608
+
600
609
  private ackPendingSegment(opArgs: IMergeTreeDeltaOpArgs) {
601
610
  const ackOp = (deltaOpArgs: IMergeTreeDeltaOpArgs) => {
602
611
  let trace: Trace | undefined;
@@ -1021,17 +1030,17 @@ export class Client {
1021
1030
  }
1022
1031
 
1023
1032
  getContainingSegment<T extends ISegment>(pos: number, op?: ISequencedDocumentMessage) {
1024
- let seq: number;
1025
- let clientId: number;
1026
- if (op) {
1027
- clientId = this.getOrAddShortClientId(op.clientId);
1028
- seq = op.referenceSequenceNumber;
1029
- } else {
1030
- const segWindow = this.mergeTree.getCollabWindow();
1031
- seq = segWindow.currentSeq;
1032
- clientId = segWindow.clientId;
1033
- }
1034
- return this.mergeTree.getContainingSegment<T>(pos, seq, clientId);
1033
+ const args = this.getClientSequenceArgsForMessage(op);
1034
+ return this.mergeTree.getContainingSegment<T>(pos, args.referenceSequenceNumber, args.clientId);
1035
+ }
1036
+
1037
+ /**
1038
+ * Returns the position to slide a reference to if a slide is required.
1039
+ * @param segoff - The segment and offset to slide from
1040
+ * @returns - segment and offset to slide the reference to
1041
+ */
1042
+ getSlideToSegment(segoff: { segment: ISegment | undefined; offset: number | undefined; }) {
1043
+ return this.mergeTree._getSlideToSegment(segoff);
1035
1044
  }
1036
1045
 
1037
1046
  getPropertiesAtPosition(pos: number) {
@@ -245,10 +245,11 @@ export class Heap<T> {
245
245
  /* eslint-enable no-bitwise */
246
246
  }
247
247
 
248
- export const enum RBColor {
249
- RED,
250
- BLACK,
251
- }
248
+ export const RBColor = {
249
+ RED: 0,
250
+ BLACK: 1,
251
+ } as const;
252
+ export type RBColor = typeof RBColor[keyof typeof RBColor];
252
253
 
253
254
  export interface RBNode<TKey, TData> {
254
255
  key: TKey;
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 * from "./localReference";
10
+ export { LocalReference, LocalReferenceCollection } from "./localReference";
11
11
  export * from "./mergeTree";
12
12
  export * from "./mergeTreeDeltaCallback";
13
13
  export * from "./mergeTreeTracking";
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { assert } from "@fluidframework/common-utils";
7
+ import { UsageError } from "@fluidframework/container-utils";
7
8
  import { Client } from "./client";
8
9
  import { List, ListMakeHead, ListRemoveEntry } from "./collections";
9
10
  import {
@@ -25,6 +26,26 @@ import {
25
26
  refTypeIncludesFlag,
26
27
  } from "./referencePositions";
27
28
 
29
+ /**
30
+ * @internal
31
+ */
32
+ export function _validateReferenceType(refType: ReferenceType) {
33
+ let exclusiveCount = 0;
34
+ if (refTypeIncludesFlag(refType, ReferenceType.Transient)) {
35
+ ++exclusiveCount;
36
+ }
37
+ if (refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove)) {
38
+ ++exclusiveCount;
39
+ }
40
+ if (refTypeIncludesFlag(refType, ReferenceType.StayOnRemove)) {
41
+ ++exclusiveCount;
42
+ }
43
+ if (exclusiveCount > 1) {
44
+ throw new UsageError(
45
+ "Reference types can only be one of Transient, SlideOnRemove, and StayOnRemove");
46
+ }
47
+ }
48
+
28
49
  /**
29
50
  * @deprecated - Use ReferencePosition
30
51
  */
@@ -57,6 +78,7 @@ export class LocalReference implements ReferencePosition {
57
78
  public refType = ReferenceType.Simple,
58
79
  properties?: PropertySet,
59
80
  ) {
81
+ _validateReferenceType(refType);
60
82
  this.segment = initSegment;
61
83
  this.properties = properties;
62
84
  }
@@ -312,8 +334,8 @@ export class LocalReferenceCollection {
312
334
  !refTypeIncludesFlag(lref, ReferenceType.Transient),
313
335
  0x2df /* "transient references cannot be bound to segments" */);
314
336
  assertLocalReferences(lref);
315
- const refsAtOffset = this.refsByOffset[lref.getOffset()] =
316
- this.refsByOffset[lref.getOffset()]
337
+ const refsAtOffset = this.refsByOffset[lref.offset] =
338
+ this.refsByOffset[lref.offset]
317
339
  ?? { at: ListMakeHead() };
318
340
  const atRefs = refsAtOffset.at =
319
341
  refsAtOffset.at
package/src/mergeTree.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  /* eslint-disable @typescript-eslint/prefer-optional-chain, no-bitwise */
10
10
 
11
11
  import { assert } from "@fluidframework/common-utils";
12
+ import { UsageError } from "@fluidframework/container-utils";
12
13
  import {
13
14
  Comparer,
14
15
  Heap,
@@ -109,6 +110,15 @@ export function toRemovalInfo(maybe: Partial<IRemovalInfo> | undefined): IRemova
109
110
  0x2bf /* "both removedClientIds and removedSeq should be set or not set" */);
110
111
  }
111
112
 
113
+ function isRemoved(segment: ISegment): boolean {
114
+ return toRemovalInfo(segment) !== undefined;
115
+ }
116
+
117
+ function isRemovedAndAcked(segment: ISegment): boolean {
118
+ const removalInfo = toRemovalInfo(segment);
119
+ return removalInfo !== undefined && removalInfo.removedSeq !== UnassignedSequenceNumber;
120
+ }
121
+
112
122
  /**
113
123
  * A segment representing a portion of the merge tree.
114
124
  */
@@ -554,7 +564,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
554
564
  return true;
555
565
 
556
566
  case MergeTreeDeltaType.REMOVE:
557
-
558
567
  const removalInfo: IRemovalInfo | undefined = toRemovalInfo(this);
559
568
  assert(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */);
560
569
  this.localRemovedSeq = undefined;
@@ -562,7 +571,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
562
571
  removalInfo.removedSeq = opArgs.sequencedMessage!.sequenceNumber;
563
572
  return true;
564
573
  }
565
-
566
574
  return false;
567
575
 
568
576
  default:
@@ -1414,6 +1422,109 @@ export class MergeTree {
1414
1422
  return { segment, offset };
1415
1423
  }
1416
1424
 
1425
+ /**
1426
+ * @internal must only be used by client
1427
+ * @param segoff - The segment and offset to slide from
1428
+ * @returns The segment and offset to slide to
1429
+ */
1430
+ public _getSlideToSegment(segoff: { segment: ISegment | undefined; offset: number | undefined; }) {
1431
+ if (!segoff.segment || !isRemovedAndAcked(segoff.segment)) {
1432
+ return segoff;
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
+ let slideToSegment: ISegment | undefined;
1441
+ this.walkAllSegments(this.root, (seg) => {
1442
+ if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAcked(seg)) {
1443
+ slideToSegment = seg;
1444
+ if (foundStart) {
1445
+ foundSegmentPastStart = true;
1446
+ return false;
1447
+ }
1448
+ }
1449
+ if (!foundStart && seg === segoff.segment) {
1450
+ foundStart = true;
1451
+ }
1452
+ return true;
1453
+ });
1454
+ let offset = 0;
1455
+ if (slideToSegment && !foundSegmentPastStart) {
1456
+ // If slid nearer then offset should be at the end of the segment
1457
+ offset = slideToSegment.cachedLength - 1;
1458
+ }
1459
+ return { segment: slideToSegment, offset };
1460
+ }
1461
+
1462
+ /**
1463
+ * This method should only be called when the current client sequence number is
1464
+ * max(remove segment sequence number, add reference sequence number).
1465
+ * Otherwise eventual consistency is not guaranteed.
1466
+ * See `packages\dds\merge-tree\REFERENCEPOSITIONS.md`
1467
+ */
1468
+ private slideReferences(segment: ISegment, refsToSlide: LocalReference[]) {
1469
+ assert(
1470
+ isRemovedAndAcked(segment),
1471
+ 0x2f1 /* slideReferences from a segment which has not been removed and acked */);
1472
+ assert(!!segment.localRefs, 0x2f2 /* Ref not in the segment localRefs */);
1473
+ const newSegoff = this._getSlideToSegment({ segment, offset: 0 });
1474
+ const newSegment = newSegoff.segment;
1475
+ if (newSegment && !newSegment.localRefs) {
1476
+ newSegment.localRefs = new LocalReferenceCollection(newSegment);
1477
+ }
1478
+ for (const ref of refsToSlide) {
1479
+ const removedRef = segment.localRefs.removeLocalRef(ref);
1480
+ assert(ref === removedRef, 0x2f3 /* Ref not in the segment localRefs */);
1481
+ if (!newSegment) {
1482
+ // No valid segments (all nodes removed or not yet created)
1483
+ ref.segment = undefined;
1484
+ ref.offset = 0;
1485
+ } else {
1486
+ ref.segment = newSegment;
1487
+ ref.offset = newSegoff.offset ?? 0;
1488
+ assert(!!newSegment.localRefs, 0x2f4 /* localRefs must be allocated */);
1489
+ newSegment.localRefs.addLocalRef(ref);
1490
+ }
1491
+ }
1492
+ // TODO is it required to update the path lengths?
1493
+ if (newSegment) {
1494
+ this.blockUpdatePathLengths(newSegment.parent, TreeMaintenanceSequenceNumber,
1495
+ LocalClientId);
1496
+ }
1497
+ }
1498
+
1499
+ private updateSegmentRefsAfterMarkRemoved(segment: ISegment, pending: boolean) {
1500
+ if (!segment.localRefs || segment.localRefs.empty) {
1501
+ return;
1502
+ }
1503
+ const refsToSlide: LocalReference[] = [];
1504
+ const refsToStay: LocalReference[] = [];
1505
+ for (const lref of segment.localRefs) {
1506
+ if (refTypeIncludesFlag(lref, ReferenceType.StayOnRemove)) {
1507
+ refsToStay.push(lref);
1508
+ } else if (refTypeIncludesFlag(lref, ReferenceType.SlideOnRemove)) {
1509
+ if (pending) {
1510
+ refsToStay.push(lref);
1511
+ } else {
1512
+ refsToSlide.push(lref);
1513
+ }
1514
+ }
1515
+ }
1516
+ // Rethink implementation of keeping and sliding refs once other reference
1517
+ // changes are complete. This works but is fragile and possibly slow.
1518
+ if (!pending) {
1519
+ this.slideReferences(segment, refsToSlide);
1520
+ }
1521
+ segment.localRefs.clear();
1522
+ for (const lref of refsToStay) {
1523
+ lref.segment = segment;
1524
+ segment.localRefs.addLocalRef(lref);
1525
+ }
1526
+ }
1527
+
1417
1528
  private blockLength(node: IMergeBlock, refSeq: number, clientId: number) {
1418
1529
  if ((this.collabWindow.collaborating) && (clientId !== this.collabWindow.clientId)) {
1419
1530
  return node.partialLengths!.getPartialLength(refSeq, clientId);
@@ -1676,7 +1787,13 @@ export class MergeTree {
1676
1787
  if (pendingSegmentGroup !== undefined) {
1677
1788
  const deltaSegments: IMergeTreeSegmentDelta[] = [];
1678
1789
  pendingSegmentGroup.segments.map((pendingSegment) => {
1679
- overwrite = !pendingSegment.ack(pendingSegmentGroup, opArgs, this) || overwrite;
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;
1793
+
1794
+ if (modified && opArgs.op.type === MergeTreeDeltaType.REMOVE) {
1795
+ this.updateSegmentRefsAfterMarkRemoved(pendingSegment, false);
1796
+ }
1680
1797
  if (MergeTree.options.zamboniSegments) {
1681
1798
  this.addToLRUSet(pendingSegment, seq);
1682
1799
  }
@@ -2338,7 +2455,7 @@ export class MergeTree {
2338
2455
  this.ensureIntervalBoundary(end, refSeq, clientId);
2339
2456
  let segmentGroup: SegmentGroup;
2340
2457
  const removedSegments: IMergeTreeSegmentDelta[] = [];
2341
- const savedLocalRefs: LocalReferenceCollection[] = [];
2458
+ const segmentsWithRefs: ISegment[] = [];
2342
2459
  const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
2343
2460
  const markRemoved = (segment: ISegment, pos: number, _start: number, _end: number) => {
2344
2461
  const existingRemovalInfo = toRemovalInfo(segment);
@@ -2362,10 +2479,9 @@ export class MergeTree {
2362
2479
  segment.localRemovedSeq = localSeq;
2363
2480
 
2364
2481
  removedSegments.push({ segment });
2365
- if (segment.localRefs && !segment.localRefs.empty) {
2366
- savedLocalRefs.push(segment.localRefs);
2367
- }
2368
- segment.localRefs = undefined;
2482
+ }
2483
+ if (segment.localRefs && !segment.localRefs.empty) {
2484
+ segmentsWithRefs.push(segment);
2369
2485
  }
2370
2486
 
2371
2487
  // Save segment so can assign removed sequence number when acked by server
@@ -2389,37 +2505,9 @@ export class MergeTree {
2389
2505
  return true;
2390
2506
  };
2391
2507
  this.mapRange({ leaf: markRemoved, post: afterMarkRemoved }, refSeq, clientId, undefined, start, end);
2392
- if (savedLocalRefs.length > 0) {
2393
- const length = this.getLength(refSeq, clientId);
2394
- let refSegment: ISegment | undefined;
2395
- if (start < length) {
2396
- const afterSegOff = this.getContainingSegment(start, refSeq, clientId);
2397
- refSegment = afterSegOff.segment;
2398
- assert(!!refSegment, 0x052 /* "Missing reference segment!" */);
2399
- if (!refSegment.localRefs) {
2400
- refSegment.localRefs = new LocalReferenceCollection(refSegment);
2401
- }
2402
- refSegment.localRefs.addBeforeTombstones(...savedLocalRefs);
2403
- } else if (length > 0) {
2404
- const beforeSegOff = this.getContainingSegment(length - 1, refSeq, clientId);
2405
- refSegment = beforeSegOff.segment;
2406
- assert(!!refSegment, 0x053 /* "Missing reference segment!" */);
2407
- if (!refSegment.localRefs) {
2408
- refSegment.localRefs = new LocalReferenceCollection(refSegment);
2409
- }
2410
- refSegment.localRefs.addAfterTombstones(...savedLocalRefs);
2411
- } else {
2412
- // TODO: The tree is empty, so there isn't anywhere to put these
2413
- // they should be preserved somehow
2414
- for (const refsCollection of savedLocalRefs) {
2415
- refsCollection.clear();
2416
- }
2417
- }
2418
-
2419
- if (refSegment) {
2420
- this.blockUpdatePathLengths(refSegment.parent, TreeMaintenanceSequenceNumber,
2421
- LocalClientId);
2422
- }
2508
+ const pending = this.collabWindow.collaborating && clientId === this.collabWindow.clientId;
2509
+ for (const segment of segmentsWithRefs) {
2510
+ this.updateSegmentRefsAfterMarkRemoved(segment, pending);
2423
2511
  }
2424
2512
 
2425
2513
  // opArgs == undefined => test code
@@ -2460,6 +2548,12 @@ export class MergeTree {
2460
2548
  segment: ISegment, offset: number, refType: ReferenceType, properties: PropertySet | undefined,
2461
2549
  client: Client,
2462
2550
  ): ReferencePosition {
2551
+ if (isRemoved(segment)) {
2552
+ if (!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient)) {
2553
+ throw new UsageError(
2554
+ "Can only create SlideOnRemove or Transient local reference position on a removed segment");
2555
+ }
2556
+ }
2463
2557
  const localRefs = segment.localRefs ?? new LocalReferenceCollection(segment);
2464
2558
  segment.localRefs = localRefs;
2465
2559
 
@@ -13,28 +13,26 @@ import { PropertySet } from "./properties";
13
13
  import { ISegment } from "./mergeTree";
14
14
 
15
15
  export type MergeTreeDeltaOperationType =
16
- MergeTreeDeltaType.ANNOTATE | MergeTreeDeltaType.INSERT | MergeTreeDeltaType.REMOVE;
16
+ typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT | typeof MergeTreeDeltaType.REMOVE;
17
17
 
18
18
  // Note: Assigned negative integers to avoid clashing with MergeTreeDeltaType
19
- export const enum MergeTreeMaintenanceType {
20
- APPEND = -1,
21
- SPLIT = -2,
19
+ export const MergeTreeMaintenanceType = {
20
+ APPEND: -1,
21
+ SPLIT: -2,
22
22
  /**
23
23
  * Notification that a segment has been unlinked from the MergeTree. This occurs during
24
24
  * Zamboni when:
25
25
  *
26
- * a) The minSeq has moved past the segment's removeSeq, in which case the segment
27
- * can no longer be referenced by incoming remote ops, and...
28
- *
29
26
  * b) The segment's tracking collection is empty (e.g., not being tracked for undo/redo).
30
27
  */
31
- UNLINK = -3,
28
+ UNLINK: -3,
32
29
  /**
33
30
  * Notification that a local change has been acknowledged by the server.
34
31
  * This means that it has made the round trip to the server and has had a sequence number assigned.
35
32
  */
36
- ACKNOWLEDGED = -4,
37
- }
33
+ ACKNOWLEDGED: -4,
34
+ } as const;
35
+ export type MergeTreeMaintenanceType = typeof MergeTreeMaintenanceType[keyof typeof MergeTreeMaintenanceType];
38
36
 
39
37
  export type MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationType | MergeTreeMaintenanceType;
40
38
 
package/src/ops.ts CHANGED
@@ -11,6 +11,7 @@ export enum ReferenceType {
11
11
  RangeBegin = 0x10,
12
12
  RangeEnd = 0x20,
13
13
  SlideOnRemove = 0x40,
14
+ StayOnRemove = 0x80,
14
15
  Transient = 0x100,
15
16
  }
16
17
 
@@ -19,12 +20,14 @@ export interface IMarkerDef {
19
20
  }
20
21
 
21
22
  // Note: Assigned positive integers to avoid clashing with MergeTreeMaintenanceType
22
- export const enum MergeTreeDeltaType {
23
- INSERT = 0,
24
- REMOVE = 1,
25
- ANNOTATE = 2,
26
- GROUP = 3,
27
- }
23
+ export const MergeTreeDeltaType = {
24
+ INSERT: 0,
25
+ REMOVE: 1,
26
+ ANNOTATE: 2,
27
+ GROUP: 3,
28
+ } as const;
29
+
30
+ export type MergeTreeDeltaType = typeof MergeTreeDeltaType[keyof typeof MergeTreeDeltaType];
28
31
 
29
32
  export interface IMergeTreeDelta {
30
33
  /**
@@ -54,7 +57,7 @@ export interface IRelativePosition {
54
57
  }
55
58
 
56
59
  export interface IMergeTreeInsertMsg extends IMergeTreeDelta {
57
- type: MergeTreeDeltaType.INSERT;
60
+ type: typeof MergeTreeDeltaType.INSERT;
58
61
  pos1?: number;
59
62
  relativePos1?: IRelativePosition;
60
63
  pos2?: number;
@@ -63,7 +66,7 @@ export interface IMergeTreeInsertMsg extends IMergeTreeDelta {
63
66
  }
64
67
 
65
68
  export interface IMergeTreeRemoveMsg extends IMergeTreeDelta {
66
- type: MergeTreeDeltaType.REMOVE;
69
+ type: typeof MergeTreeDeltaType.REMOVE;
67
70
  pos1?: number;
68
71
  relativePos1?: IRelativePosition;
69
72
  pos2?: number;
@@ -78,7 +81,7 @@ export interface ICombiningOp {
78
81
  }
79
82
 
80
83
  export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta {
81
- type: MergeTreeDeltaType.ANNOTATE;
84
+ type: typeof MergeTreeDeltaType.ANNOTATE;
82
85
  pos1?: number;
83
86
  relativePos1?: IRelativePosition;
84
87
  pos2?: number;
@@ -88,7 +91,7 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta {
88
91
  }
89
92
 
90
93
  export interface IMergeTreeGroupMsg extends IMergeTreeDelta {
91
- type: MergeTreeDeltaType.GROUP;
94
+ type: typeof MergeTreeDeltaType.GROUP;
92
95
  ops: IMergeTreeDeltaOp[];
93
96
  }
94
97
 
@@ -11,9 +11,10 @@ import { PropertySet, MapLike } from "./properties";
11
11
  export const reservedTileLabelsKey = "referenceTileLabels";
12
12
  export const reservedRangeLabelsKey = "referenceRangeLabels";
13
13
 
14
- export function refTypeIncludesFlag(refPos: ReferencePosition, flags: ReferenceType): boolean {
14
+ export function refTypeIncludesFlag(refPosOrType: ReferencePosition | ReferenceType, flags: ReferenceType): boolean {
15
+ const refType = typeof refPosOrType === "number" ? refPosOrType : refPosOrType.refType;
15
16
  // eslint-disable-next-line no-bitwise
16
- return (refPos.refType & flags) !== 0;
17
+ return (refType & flags) !== 0;
17
18
  }
18
19
 
19
20
  export const refGetTileLabels = (refPos: ReferencePosition): string[] | undefined =>