@fluidframework/sequence 2.43.0 → 2.50.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/intervalCollection.d.ts +4 -5
  3. package/dist/intervalCollection.d.ts.map +1 -1
  4. package/dist/intervalCollection.js +93 -97
  5. package/dist/intervalCollection.js.map +1 -1
  6. package/dist/intervalCollectionMapInterfaces.d.ts +2 -11
  7. package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -1
  8. package/dist/intervalCollectionMapInterfaces.js.map +1 -1
  9. package/dist/intervals/sequenceInterval.d.ts +7 -3
  10. package/dist/intervals/sequenceInterval.d.ts.map +1 -1
  11. package/dist/intervals/sequenceInterval.js +72 -30
  12. package/dist/intervals/sequenceInterval.js.map +1 -1
  13. package/dist/packageVersion.d.ts +1 -1
  14. package/dist/packageVersion.js +1 -1
  15. package/dist/packageVersion.js.map +1 -1
  16. package/lib/intervalCollection.d.ts +4 -5
  17. package/lib/intervalCollection.d.ts.map +1 -1
  18. package/lib/intervalCollection.js +93 -97
  19. package/lib/intervalCollection.js.map +1 -1
  20. package/lib/intervalCollectionMapInterfaces.d.ts +2 -11
  21. package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
  22. package/lib/intervalCollectionMapInterfaces.js.map +1 -1
  23. package/lib/intervals/sequenceInterval.d.ts +7 -3
  24. package/lib/intervals/sequenceInterval.d.ts.map +1 -1
  25. package/lib/intervals/sequenceInterval.js +74 -32
  26. package/lib/intervals/sequenceInterval.js.map +1 -1
  27. package/lib/packageVersion.d.ts +1 -1
  28. package/lib/packageVersion.js +1 -1
  29. package/lib/packageVersion.js.map +1 -1
  30. package/package.json +17 -17
  31. package/src/intervalCollection.ts +109 -106
  32. package/src/intervalCollectionMapInterfaces.ts +2 -13
  33. package/src/intervals/sequenceInterval.ts +88 -32
  34. package/src/packageVersion.ts +1 -1
@@ -7,7 +7,12 @@
7
7
 
8
8
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
9
9
  import { IEvent } from "@fluidframework/core-interfaces";
10
- import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
10
+ import {
11
+ assert,
12
+ DoublyLinkedList,
13
+ unreachableCase,
14
+ type ListNode,
15
+ } from "@fluidframework/core-utils/internal";
11
16
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
12
17
  import {
13
18
  Client,
@@ -23,8 +28,6 @@ import {
23
28
  endpointPosAndSide,
24
29
  type ISegmentInternal,
25
30
  createLocalReconnectingPerspective,
26
- DoublyLinkedList,
27
- type ListNode,
28
31
  SlidingPreference,
29
32
  } from "@fluidframework/merge-tree/internal";
30
33
  import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
@@ -194,7 +197,6 @@ export class LocalIntervalCollection {
194
197
  end: SequencePlace,
195
198
  props?: PropertySet,
196
199
  op?: ISequencedDocumentMessage,
197
- rollback?: boolean,
198
200
  ) {
199
201
  // This check is intended to prevent scenarios where a random interval is created and then
200
202
  // inserted into a collection. The aim is to ensure that the collection is created first
@@ -218,7 +220,7 @@ export class LocalIntervalCollection {
218
220
  undefined,
219
221
  this.options.mergeTreeReferencesCanSlideToEndpoint,
220
222
  props,
221
- rollback,
223
+ false,
222
224
  );
223
225
 
224
226
  this.add(interval);
@@ -690,6 +692,7 @@ type PendingChanges = Partial<
690
692
  endpointChanges?: DoublyLinkedList<
691
693
  IntervalAddLocalMetadata | IntervalChangeLocalMetadata
692
694
  >;
695
+ consensus: SequenceIntervalClass | undefined;
693
696
  }
694
697
  >
695
698
  >;
@@ -706,8 +709,7 @@ function removeMetadataFromPendingChanges(
706
709
 
707
710
  function clearEmptyPendingEntry(pendingChanges: PendingChanges, id: string) {
708
711
  const pending = pendingChanges[id];
709
- assert(pending !== undefined, 0xbbf /* pending must exist for local process */);
710
- if (pending.local.empty) {
712
+ if (pending !== undefined && pending.local.empty) {
711
713
  assert(
712
714
  pending.endpointChanges?.empty !== false,
713
715
  0xbc0 /* endpointChanges must be empty if not pending changes */,
@@ -715,6 +717,7 @@ function clearEmptyPendingEntry(pendingChanges: PendingChanges, id: string) {
715
717
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
716
718
  delete pendingChanges[id];
717
719
  }
720
+ return pending;
718
721
  }
719
722
 
720
723
  function hasEndpointChanges(
@@ -744,6 +747,7 @@ export class IntervalCollection
744
747
  private readonly submitDelta: (
745
748
  op: IIntervalCollectionTypeOperationValue,
746
749
  md: IntervalMessageLocalMetadata,
750
+ consensus?: SequenceIntervalClass,
747
751
  ) => void;
748
752
 
749
753
  constructor(
@@ -753,15 +757,15 @@ export class IntervalCollection
753
757
  ) {
754
758
  super();
755
759
 
756
- this.submitDelta = (op, md) => {
760
+ this.submitDelta = (op, md, consensus) => {
757
761
  const { id } = getSerializedProperties(op.value);
758
762
  const pending = (this.pending[id] ??= {
759
763
  local: new DoublyLinkedList(),
764
+ consensus,
760
765
  });
761
766
  if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
762
767
  const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
763
768
  md.endpointChangesNode = endpointChanges.push(md).last;
764
- md.rebased = undefined;
765
769
  }
766
770
  submitDelta(op, pending.local.push(md).last);
767
771
  };
@@ -811,48 +815,45 @@ export class IntervalCollection
811
815
  const localOpMetadata = removeMetadataFromPendingChanges(maybeMetadata);
812
816
  const { value } = op;
813
817
  const { id, properties } = getSerializedProperties(value);
814
- const { type } = localOpMetadata;
818
+ const pending = clearEmptyPendingEntry(this.pending, id);
819
+ const previous = pending?.local.empty
820
+ ? pending.consensus
821
+ : pending?.local.last?.data.interval;
822
+ const { type, interval } = localOpMetadata;
815
823
  switch (type) {
816
824
  case "add": {
817
- const interval = this.getIntervalById(id);
818
- if (interval) {
819
- this.deleteExistingInterval({ interval, local: true, rollback: true });
820
- }
825
+ this.deleteExistingInterval({ interval, local: true, rollback: true });
826
+ interval.dispose();
821
827
  break;
822
828
  }
823
829
  case "change": {
824
- const { previous } = localOpMetadata;
825
- const endpointsChanged = hasEndpointChanges(value);
826
- const start = endpointsChanged
827
- ? toOptionalSequencePlace(previous.start, previous.startSide)
830
+ const changeProperties = Object.keys(properties).length > 0;
831
+ const deltaProps = changeProperties
832
+ ? interval.changeProperties(properties, undefined, true)
828
833
  : undefined;
829
- const end = endpointsChanged
830
- ? toOptionalSequencePlace(previous.end, previous.endSide)
831
- : undefined;
832
- this.change(id, {
833
- start,
834
- end,
835
- props: Object.keys(properties).length > 0 ? properties : undefined,
836
- rollback: true,
837
- });
834
+ if (localOpMetadata.endpointChangesNode !== undefined) {
835
+ this.localCollection?.removeExistingInterval(interval);
836
+ assert(previous !== undefined, 0xbd2 /* must have existed to change */);
837
+ this.localCollection?.add(previous);
838
+ this.emitChange(previous, interval, true, true);
839
+ }
840
+ if (previous !== interval) {
841
+ interval.dispose();
842
+ }
843
+ if (changeProperties) {
844
+ this.emit("propertyChanged", previous, deltaProps, true, undefined);
845
+ }
838
846
  break;
839
847
  }
840
848
  case "delete": {
841
- const { previous } = localOpMetadata;
842
- this.add({
843
- id,
844
- start: toSequencePlace(previous.start, previous.startSide),
845
- end: toSequencePlace(previous.end, previous.endSide),
846
- props: Object.keys(properties).length > 0 ? properties : undefined,
847
- rollback: true,
848
- });
849
+ assert(previous !== undefined, 0xbd3 /* must have existed to delete */);
850
+ this.localCollection?.add(previous);
851
+ this.emit("addInterval", previous, true, undefined);
849
852
  break;
850
853
  }
851
854
  default:
852
855
  unreachableCase(type);
853
856
  }
854
-
855
- clearEmptyPendingEntry(this.pending, id);
856
857
  }
857
858
 
858
859
  public process(
@@ -866,13 +867,15 @@ export class IntervalCollection
866
867
  : undefined;
867
868
 
868
869
  const { opName, value } = op;
870
+ const { id } = getSerializedProperties(value);
869
871
  assert(
870
872
  (local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type,
871
873
  0xbc1 /* must be same type */,
872
874
  );
875
+ let newConsensus = localOpMetadata?.interval;
873
876
  switch (opName) {
874
877
  case "add": {
875
- this.ackAdd(
878
+ newConsensus = this.ackAdd(
876
879
  value,
877
880
  local,
878
881
  message,
@@ -889,16 +892,22 @@ export class IntervalCollection
889
892
  }
890
893
 
891
894
  case "change": {
892
- this.ackChange(value, local, message);
895
+ newConsensus = this.ackChange(
896
+ value,
897
+ local,
898
+ message, // this cast is safe because of the above assert which
899
+ // validates the op and metadata types match for local changes
900
+ localOpMetadata as IntervalChangeLocalMetadata | undefined,
901
+ );
893
902
  break;
894
903
  }
895
904
  default:
896
905
  unreachableCase(opName);
897
906
  }
907
+ const pending = clearEmptyPendingEntry(this.pending, id);
898
908
 
899
- if (local) {
900
- const { id } = getSerializedProperties(value);
901
- clearEmptyPendingEntry(this.pending, id);
909
+ if (pending !== undefined) {
910
+ pending.consensus = newConsensus;
902
911
  }
903
912
  }
904
913
 
@@ -1037,7 +1046,7 @@ export class IntervalCollection
1037
1046
  for (const pending of Object.values(this.pending)) {
1038
1047
  if (pending?.endpointChanges !== undefined) {
1039
1048
  for (const local of pending.endpointChanges) {
1040
- local.data.rebased = this.computeRebasedPositions(local.data, squash);
1049
+ this.rebaseLocalInterval(local.data.interval.serialize(), local.data, squash);
1041
1050
  }
1042
1051
  }
1043
1052
  }
@@ -1147,13 +1156,11 @@ export class IntervalCollection
1147
1156
  start,
1148
1157
  end,
1149
1158
  props,
1150
- rollback,
1151
1159
  }: {
1152
1160
  id?: string;
1153
1161
  start: SequencePlace;
1154
1162
  end: SequencePlace;
1155
1163
  props?: PropertySet;
1156
- rollback?: boolean;
1157
1164
  }): SequenceIntervalClass {
1158
1165
  if (!this.localCollection) {
1159
1166
  throw new LoggingError("attach must be called prior to adding intervals");
@@ -1179,7 +1186,6 @@ export class IntervalCollection
1179
1186
  toSequencePlace(endPos, endSide),
1180
1187
  props,
1181
1188
  undefined,
1182
- rollback,
1183
1189
  );
1184
1190
 
1185
1191
  if (interval) {
@@ -1189,7 +1195,7 @@ export class IntervalCollection
1189
1195
  }
1190
1196
  const serializedInterval: ISerializedInterval = interval.serialize();
1191
1197
  const localSeq = this.getNextLocalSeq();
1192
- if (this.isCollaborating && rollback !== true) {
1198
+ if (this.isCollaborating) {
1193
1199
  this.submitDelta(
1194
1200
  {
1195
1201
  opName: "add",
@@ -1238,8 +1244,8 @@ export class IntervalCollection
1238
1244
  {
1239
1245
  type: "delete",
1240
1246
  localSeq: this.getNextLocalSeq(),
1241
- previous: value,
1242
1247
  },
1248
+ interval,
1243
1249
  );
1244
1250
  } else {
1245
1251
  if (this.onDeserialize) {
@@ -1258,7 +1264,7 @@ export class IntervalCollection
1258
1264
  if (!this.localCollection) {
1259
1265
  throw new LoggingError("Attach must be called before accessing intervals");
1260
1266
  }
1261
- const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
1267
+ const interval = this.getIntervalById(id);
1262
1268
  if (interval) {
1263
1269
  this.deleteExistingInterval({ interval, local: true });
1264
1270
  }
@@ -1326,7 +1332,6 @@ export class IntervalCollection
1326
1332
  const metadata: IntervalChangeLocalMetadata = {
1327
1333
  type: "change",
1328
1334
  localSeq,
1329
- previous: interval.serialize(),
1330
1335
  interval: newInterval ?? interval,
1331
1336
  };
1332
1337
 
@@ -1336,6 +1341,7 @@ export class IntervalCollection
1336
1341
  value: serializedInterval,
1337
1342
  },
1338
1343
  metadata,
1344
+ interval,
1339
1345
  );
1340
1346
  }
1341
1347
  if (deltaProps !== undefined) {
@@ -1374,6 +1380,7 @@ export class IntervalCollection
1374
1380
  serializedInterval: SerializedIntervalDelta,
1375
1381
  local: boolean,
1376
1382
  op: ISequencedDocumentMessage,
1383
+ localOpMetadata: IntervalChangeLocalMetadata | undefined,
1377
1384
  ) {
1378
1385
  if (!this.localCollection) {
1379
1386
  throw new LoggingError("Attach must be called before accessing intervals");
@@ -1384,55 +1391,51 @@ export class IntervalCollection
1384
1391
  // strip it out of the properties here.
1385
1392
  const { id, properties } = getSerializedProperties(serializedInterval);
1386
1393
  assert(id !== undefined, 0x3fe /* id must exist on the interval */);
1387
- const interval: SequenceIntervalClass | undefined = this.getIntervalById(id);
1388
-
1389
- if (!interval) {
1390
- // The interval has been removed locally; no-op.
1391
- return;
1392
- }
1393
1394
 
1394
1395
  if (local) {
1396
+ assert(localOpMetadata !== undefined, 0xbd4 /* local must have metadata */);
1397
+ const { interval } = localOpMetadata;
1395
1398
  interval.ackPropertiesChange(properties, op);
1396
1399
 
1397
1400
  this.ackInterval(interval, op);
1401
+ return interval;
1398
1402
  } else {
1399
- // If there are pending changes with this ID, don't apply the remote start/end change, as the local ack
1400
- // should be the winning change.
1401
- let start: number | "start" | "end" | undefined;
1402
- let end: number | "start" | "end" | undefined;
1403
- // Track pending start/end independently of one another.
1404
- if (!this.hasPendingEndpointChanges(id)) {
1405
- start = serializedInterval.start;
1406
- end = serializedInterval.end;
1407
- }
1403
+ const latestInterval = this.getIntervalById(id);
1404
+ const intervalToChange: SequenceIntervalClass | undefined =
1405
+ this.pending[id]?.consensus ?? latestInterval;
1408
1406
 
1409
- let newInterval = interval;
1410
- if (start !== undefined || end !== undefined) {
1411
- // If changeInterval gives us a new interval, work with that one. Otherwise keep working with
1412
- // the one we originally found in the tree.
1413
- newInterval =
1414
- this.localCollection.changeInterval(
1415
- interval,
1416
- toOptionalSequencePlace(start, serializedInterval.startSide ?? Side.Before),
1417
- toOptionalSequencePlace(end, serializedInterval.endSide ?? Side.Before),
1418
- op,
1419
- ) ?? interval;
1420
- }
1421
- const deltaProps = newInterval.changeProperties(properties, op);
1407
+ const isLatestInterval = intervalToChange === latestInterval;
1422
1408
 
1423
- if (this.onDeserialize) {
1424
- this.onDeserialize(newInterval);
1409
+ if (!intervalToChange) {
1410
+ return intervalToChange;
1425
1411
  }
1426
1412
 
1427
- if (newInterval !== interval) {
1428
- this.emitChange(newInterval, interval, local, false, op);
1413
+ const deltaProps = intervalToChange.changeProperties(properties, op);
1414
+
1415
+ let newInterval = intervalToChange;
1416
+ if (hasEndpointChanges(serializedInterval)) {
1417
+ const { start, end, startSide, endSide } = serializedInterval;
1418
+ newInterval = intervalToChange.modify(
1419
+ "",
1420
+ toOptionalSequencePlace(start, startSide ?? Side.Before),
1421
+ toOptionalSequencePlace(end, endSide ?? Side.Before),
1422
+ op,
1423
+ );
1424
+ if (isLatestInterval) {
1425
+ this.localCollection.removeExistingInterval(intervalToChange);
1426
+ this.localCollection.add(newInterval);
1427
+ this.emitChange(newInterval, intervalToChange, local, false, op);
1428
+ if (this.onDeserialize) {
1429
+ this.onDeserialize(newInterval);
1430
+ }
1431
+ }
1429
1432
  }
1430
1433
 
1431
- const changedProperties = Object.keys(properties).length > 0;
1432
- if (changedProperties) {
1433
- this.emit("propertyChanged", interval, deltaProps, local, op);
1434
- this.emit("changed", interval, deltaProps, undefined, local, false);
1434
+ if (deltaProps !== undefined && Object.keys(deltaProps).length > 0) {
1435
+ this.emit("propertyChanged", latestInterval, deltaProps, local, op);
1436
+ this.emit("changed", latestInterval, deltaProps, undefined, local, false);
1435
1437
  }
1438
+ return newInterval;
1436
1439
  }
1437
1440
  }
1438
1441
 
@@ -1475,11 +1478,9 @@ export class IntervalCollection
1475
1478
 
1476
1479
  const { localSeq, interval } = localOpMetadata;
1477
1480
  const { id } = getSerializedProperties(original);
1478
- const rebasedEndpoint = (localOpMetadata.rebased ??= this.computeRebasedPositions(
1479
- localOpMetadata,
1480
- squash,
1481
- ));
1482
- const localInterval = this.localCollection.idIntervalIndex.getIntervalById(id);
1481
+
1482
+ const rebasedEndpoint = this.computeRebasedPositions(localOpMetadata, squash);
1483
+ const localInterval = this.getIntervalById(id);
1483
1484
 
1484
1485
  // if the interval slid off the string, rebase the op to be a noop and delete the interval.
1485
1486
  if (rebasedEndpoint === "detached") {
@@ -1508,8 +1509,7 @@ export class IntervalCollection
1508
1509
  this.localCollection.add(interval);
1509
1510
  this.emitChange(interval, old, true, true);
1510
1511
  }
1511
- this.client.removeLocalReferencePosition(old.start);
1512
- this.client.removeLocalReferencePosition(old.end);
1512
+ old.dispose();
1513
1513
  }
1514
1514
 
1515
1515
  return {
@@ -1580,10 +1580,14 @@ export class IntervalCollection
1580
1580
  // `interval`'s endpoints will get modified in-place, so clone it prior to doing so for event emission.
1581
1581
  const oldInterval = interval.clone();
1582
1582
 
1583
- // In this case, where we change the start or end of an interval,
1584
- // it is necessary to remove and re-add the interval listeners.
1585
- // This ensures that the correct listeners are added to the LocalReferencePosition.
1586
- this.localCollection.removeExistingInterval(interval);
1583
+ const isLatestInterval = this.getIntervalById(id) === interval;
1584
+
1585
+ if (isLatestInterval) {
1586
+ // In this case, where we change the start or end of an interval,
1587
+ // it is necessary to remove and re-add the interval listeners.
1588
+ // This ensures that the correct listeners are added to the LocalReferencePosition.
1589
+ this.localCollection.removeExistingInterval(interval);
1590
+ }
1587
1591
  if (!this.client) {
1588
1592
  throw new LoggingError("client does not exist");
1589
1593
  }
@@ -1626,8 +1630,10 @@ export class IntervalCollection
1626
1630
  oldInterval.end.refType = ReferenceType.Transient;
1627
1631
  oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
1628
1632
  }
1629
- this.localCollection.add(interval);
1630
- this.emitChange(interval, oldInterval, true, true, op);
1633
+ if (isLatestInterval) {
1634
+ this.localCollection.add(interval);
1635
+ this.emitChange(interval, oldInterval, true, true, op);
1636
+ }
1631
1637
  }
1632
1638
  }
1633
1639
 
@@ -1636,7 +1642,7 @@ export class IntervalCollection
1636
1642
  local: boolean,
1637
1643
  op: ISequencedDocumentMessage,
1638
1644
  localOpMetadata: IntervalAddLocalMetadata | undefined,
1639
- ) {
1645
+ ): SequenceIntervalClass {
1640
1646
  const { id, properties } = getSerializedProperties(serializedInterval);
1641
1647
 
1642
1648
  if (local) {
@@ -1644,11 +1650,8 @@ export class IntervalCollection
1644
1650
  localOpMetadata !== undefined,
1645
1651
  0x553 /* op metadata should be defined for local op */,
1646
1652
  );
1647
- const localInterval = this.getIntervalById(id);
1648
- if (localInterval) {
1649
- this.ackInterval(localInterval, op);
1650
- }
1651
- return;
1653
+ this.ackInterval(localOpMetadata.interval, op);
1654
+ return localOpMetadata.interval;
1652
1655
  }
1653
1656
 
1654
1657
  if (!this.localCollection) {
@@ -1691,7 +1694,7 @@ export class IntervalCollection
1691
1694
  }
1692
1695
 
1693
1696
  const { id } = getSerializedProperties(serializedInterval);
1694
- const interval = this.localCollection.idIntervalIndex.getIntervalById(id);
1697
+ const interval = this.getIntervalById(id);
1695
1698
  if (interval) {
1696
1699
  this.deleteExistingInterval({ interval, local, op });
1697
1700
  }
@@ -3,12 +3,9 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import type { ListNode } from "@fluidframework/core-utils/internal";
6
7
  import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
7
- import {
8
- IMergeTreeOptions,
9
- ListNode,
10
- type ISegmentInternal,
11
- } from "@fluidframework/merge-tree/internal";
8
+ import { IMergeTreeOptions } from "@fluidframework/merge-tree/internal";
12
9
 
13
10
  import type {
14
11
  IntervalCollection,
@@ -26,25 +23,17 @@ export interface IntervalAddLocalMetadata {
26
23
  type: typeof IntervalDeltaOpType.ADD;
27
24
  localSeq: number;
28
25
  endpointChangesNode?: ListNode<IntervalAddLocalMetadata | IntervalChangeLocalMetadata>;
29
- rebased?:
30
- | Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
31
- | "detached";
32
26
  interval: SequenceIntervalClass;
33
27
  }
34
28
  export interface IntervalChangeLocalMetadata {
35
29
  type: typeof IntervalDeltaOpType.CHANGE;
36
30
  localSeq: number;
37
- previous: ISerializedInterval;
38
31
  endpointChangesNode?: ListNode<IntervalChangeLocalMetadata | IntervalChangeLocalMetadata>;
39
- rebased?:
40
- | Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
41
- | "detached";
42
32
  interval: SequenceIntervalClass;
43
33
  }
44
34
  export interface IntervalDeleteLocalMetadata {
45
35
  type: typeof IntervalDeltaOpType.DELETE;
46
36
  localSeq: number;
47
- previous: ISerializedInterval;
48
37
  endpointChangesNode?: undefined;
49
38
  interval?: undefined;
50
39
  }