@fluidframework/sequence 2.0.0-dev.5.2.0.169897 → 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 (70) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/intervalCollection.d.ts +9 -19
  7. package/dist/intervalCollection.d.ts.map +1 -1
  8. package/dist/intervalCollection.js +26 -105
  9. package/dist/intervalCollection.js.map +1 -1
  10. package/dist/intervalIndex/index.d.ts +8 -0
  11. package/dist/intervalIndex/index.d.ts.map +1 -0
  12. package/dist/intervalIndex/index.js +12 -0
  13. package/dist/intervalIndex/index.js.map +1 -0
  14. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
  15. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
  16. package/dist/intervalIndex/overlappingIntervalsIndex.js +103 -0
  17. package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
  18. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
  19. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
  20. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +33 -0
  21. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
  22. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
  23. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
  24. package/dist/intervalIndex/sequenceIntervalIndexes.js +7 -0
  25. package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
  26. package/dist/packageVersion.d.ts +1 -1
  27. package/dist/packageVersion.js +1 -1
  28. package/dist/packageVersion.js.map +1 -1
  29. package/dist/revertibles.d.ts.map +1 -1
  30. package/dist/revertibles.js +47 -7
  31. package/dist/revertibles.js.map +1 -1
  32. package/lib/index.d.ts +1 -0
  33. package/lib/index.d.ts.map +1 -1
  34. package/lib/index.js +1 -0
  35. package/lib/index.js.map +1 -1
  36. package/lib/intervalCollection.d.ts +9 -19
  37. package/lib/intervalCollection.d.ts.map +1 -1
  38. package/lib/intervalCollection.js +26 -106
  39. package/lib/intervalCollection.js.map +1 -1
  40. package/lib/intervalIndex/index.d.ts +8 -0
  41. package/lib/intervalIndex/index.d.ts.map +1 -0
  42. package/lib/intervalIndex/index.js +7 -0
  43. package/lib/intervalIndex/index.js.map +1 -0
  44. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +32 -0
  45. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
  46. package/lib/intervalIndex/overlappingIntervalsIndex.js +98 -0
  47. package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
  48. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
  49. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
  50. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +29 -0
  51. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
  52. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
  53. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
  54. package/lib/intervalIndex/sequenceIntervalIndexes.js +6 -0
  55. package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
  56. package/lib/packageVersion.d.ts +1 -1
  57. package/lib/packageVersion.js +1 -1
  58. package/lib/packageVersion.js.map +1 -1
  59. package/lib/revertibles.d.ts.map +1 -1
  60. package/lib/revertibles.js +48 -8
  61. package/lib/revertibles.js.map +1 -1
  62. package/package.json +18 -18
  63. package/src/index.ts +6 -0
  64. package/src/intervalCollection.ts +41 -143
  65. package/src/intervalIndex/index.ts +11 -0
  66. package/src/intervalIndex/overlappingIntervalsIndex.ts +166 -0
  67. package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +71 -0
  68. package/src/intervalIndex/sequenceIntervalIndexes.ts +32 -0
  69. package/src/packageVersion.ts +1 -1
  70. package/src/revertibles.ts +61 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/sequence",
3
- "version": "2.0.0-dev.5.2.0.169897",
3
+ "version": "2.0.0-dev.5.3.2.178189",
4
4
  "description": "Distributed sequence",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -37,30 +37,30 @@
37
37
  "dependencies": {
38
38
  "@fluidframework/common-definitions": "^0.20.1",
39
39
  "@fluidframework/common-utils": "^1.1.1",
40
- "@fluidframework/container-utils": "2.0.0-dev.5.2.0.169897",
41
- "@fluidframework/core-interfaces": "2.0.0-dev.5.2.0.169897",
42
- "@fluidframework/datastore-definitions": "2.0.0-dev.5.2.0.169897",
43
- "@fluidframework/merge-tree": "2.0.0-dev.5.2.0.169897",
40
+ "@fluidframework/container-utils": "2.0.0-dev.5.3.2.178189",
41
+ "@fluidframework/core-interfaces": "2.0.0-dev.5.3.2.178189",
42
+ "@fluidframework/datastore-definitions": "2.0.0-dev.5.3.2.178189",
43
+ "@fluidframework/merge-tree": "2.0.0-dev.5.3.2.178189",
44
44
  "@fluidframework/protocol-definitions": "^1.1.0",
45
- "@fluidframework/runtime-definitions": "2.0.0-dev.5.2.0.169897",
46
- "@fluidframework/runtime-utils": "2.0.0-dev.5.2.0.169897",
47
- "@fluidframework/shared-object-base": "2.0.0-dev.5.2.0.169897",
48
- "@fluidframework/telemetry-utils": "2.0.0-dev.5.2.0.169897",
45
+ "@fluidframework/runtime-definitions": "2.0.0-dev.5.3.2.178189",
46
+ "@fluidframework/runtime-utils": "2.0.0-dev.5.3.2.178189",
47
+ "@fluidframework/shared-object-base": "2.0.0-dev.5.3.2.178189",
48
+ "@fluidframework/telemetry-utils": "2.0.0-dev.5.3.2.178189",
49
49
  "uuid": "^8.3.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@fluid-internal/stochastic-test-utils": "2.0.0-dev.5.2.0.169897",
53
- "@fluid-internal/test-dds-utils": "2.0.0-dev.5.2.0.169897",
54
- "@fluid-tools/benchmark": "^0.47.0",
55
- "@fluid-tools/build-cli": "^0.20.0-169245",
52
+ "@fluid-internal/stochastic-test-utils": "2.0.0-dev.5.3.2.178189",
53
+ "@fluid-internal/test-dds-utils": "2.0.0-dev.5.3.2.178189",
54
+ "@fluid-tools/benchmark": "^0.48.0",
55
+ "@fluid-tools/build-cli": "^0.21.0",
56
56
  "@fluidframework/build-common": "^1.2.0",
57
- "@fluidframework/build-tools": "^0.20.0-169245",
57
+ "@fluidframework/build-tools": "^0.21.0",
58
58
  "@fluidframework/eslint-config-fluid": "^2.0.0",
59
59
  "@fluidframework/gitresources": "^0.1039.1000",
60
- "@fluidframework/mocha-test-setup": "2.0.0-dev.5.2.0.169897",
61
- "@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.0.0-internal.5.0.0",
60
+ "@fluidframework/mocha-test-setup": "2.0.0-dev.5.3.2.178189",
61
+ "@fluidframework/sequence-previous": "npm:@fluidframework/sequence@2.0.0-internal.5.2.0",
62
62
  "@fluidframework/server-services-client": "^0.1039.1000",
63
- "@fluidframework/test-runtime-utils": "2.0.0-dev.5.2.0.169897",
63
+ "@fluidframework/test-runtime-utils": "2.0.0-dev.5.3.2.178189",
64
64
  "@microsoft/api-extractor": "^7.34.4",
65
65
  "@types/diff": "^3.5.1",
66
66
  "@types/mocha": "^9.1.1",
@@ -124,6 +124,6 @@
124
124
  "testfarm": "node dist/test/testFarm.js",
125
125
  "tsc": "tsc",
126
126
  "typetests:gen": "fluid-type-test-generator",
127
- "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
127
+ "typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
128
128
  }
129
129
  }
package/src/index.ts CHANGED
@@ -39,6 +39,12 @@ export {
39
39
  createStartpointInRangeIndex,
40
40
  } from "./intervalCollection";
41
41
  export { IInterval, IntervalConflictResolver } from "./intervalTree";
42
+ export {
43
+ SequenceIntervalIndexes,
44
+ IOverlappingIntervalsIndex,
45
+ createOverlappingIntervalsIndex,
46
+ createOverlappingSequenceIntervalsIndex,
47
+ } from "./intervalIndex";
42
48
  export {
43
49
  appendAddIntervalToRevertibles,
44
50
  appendChangeIntervalToRevertibles,
@@ -13,6 +13,7 @@ import {
13
13
  Client,
14
14
  compareReferencePositions,
15
15
  createMap,
16
+ getSlideToSegoff,
16
17
  ICombiningOp,
17
18
  ISegment,
18
19
  MergeTreeDeltaType,
@@ -43,7 +44,8 @@ import {
43
44
  IValueTypeOperationValue,
44
45
  SequenceOptions,
45
46
  } from "./defaultMapInterfaces";
46
- import { IInterval, IntervalConflictResolver, IntervalTree, IntervalNode } from "./intervalTree";
47
+ import { IInterval, IntervalConflictResolver } from "./intervalTree";
48
+ import { IOverlappingIntervalsIndex, createOverlappingIntervalsIndex } from "./intervalIndex";
47
49
 
48
50
  const reservedIntervalIdKey = "intervalId";
49
51
 
@@ -772,7 +774,7 @@ export class SequenceInterval implements ISerializableInterval {
772
774
  }
773
775
  }
774
776
 
775
- function createPositionReferenceFromSegoff(
777
+ export function createPositionReferenceFromSegoff(
776
778
  client: Client,
777
779
  segoff: { segment: ISegment | undefined; offset: number | undefined },
778
780
  refType: ReferenceType,
@@ -828,7 +830,7 @@ function createPositionReference(
828
830
  referenceSequenceNumber: op.referenceSequenceNumber,
829
831
  clientId: op.clientId,
830
832
  });
831
- segoff = client.getSlideToSegment(segoff);
833
+ segoff = getSlideToSegoff(segoff);
832
834
  } else {
833
835
  assert(
834
836
  (refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
@@ -889,6 +891,7 @@ export function createSequenceInterval(
889
891
  undefined,
890
892
  startReferenceSlidingPreference(stickiness),
891
893
  );
894
+
892
895
  const endLref = createPositionReference(
893
896
  client,
894
897
  end,
@@ -898,6 +901,7 @@ export function createSequenceInterval(
898
901
  undefined,
899
902
  endReferenceSlidingPreference(stickiness),
900
903
  );
904
+
901
905
  const rangeProp = {
902
906
  [reservedRangeLabelsKey]: [label],
903
907
  };
@@ -949,135 +953,6 @@ export interface IntervalIndex<TInterval extends ISerializableInterval> {
949
953
  remove(interval: TInterval): void;
950
954
  }
951
955
 
952
- class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
953
- implements IntervalIndex<TInterval>
954
- {
955
- private readonly intervalTree = new IntervalTree<TInterval>();
956
-
957
- constructor(
958
- private readonly client: Client,
959
- private readonly helpers: IIntervalHelpers<TInterval>,
960
- ) {}
961
-
962
- public map(fn: (interval: TInterval) => void) {
963
- this.intervalTree.map(fn);
964
- }
965
-
966
- public mapUntil(fn: (interval: TInterval) => boolean) {
967
- this.intervalTree.mapUntil(fn);
968
- }
969
-
970
- public gatherIterationResults(
971
- results: TInterval[],
972
- iteratesForward: boolean,
973
- start?: number,
974
- end?: number,
975
- ) {
976
- if (this.intervalTree.intervals.isEmpty()) {
977
- return;
978
- }
979
-
980
- if (start === undefined && end === undefined) {
981
- // No start/end provided. Gather the whole tree in the specified order.
982
- if (iteratesForward) {
983
- this.intervalTree.map((interval: TInterval) => {
984
- results.push(interval);
985
- });
986
- } else {
987
- this.intervalTree.mapBackward((interval: TInterval) => {
988
- results.push(interval);
989
- });
990
- }
991
- } else {
992
- const transientInterval: TInterval = this.helpers.create(
993
- "transient",
994
- start,
995
- end,
996
- this.client,
997
- IntervalType.Transient,
998
- );
999
-
1000
- if (start === undefined) {
1001
- // Only end position provided. Since the tree is not sorted by end position,
1002
- // walk the whole tree in the specified order, gathering intervals that match the end.
1003
- if (iteratesForward) {
1004
- this.intervalTree.map((interval: TInterval) => {
1005
- if (transientInterval.compareEnd(interval) === 0) {
1006
- results.push(interval);
1007
- }
1008
- });
1009
- } else {
1010
- this.intervalTree.mapBackward((interval: TInterval) => {
1011
- if (transientInterval.compareEnd(interval) === 0) {
1012
- results.push(interval);
1013
- }
1014
- });
1015
- }
1016
- } else {
1017
- // Start and (possibly) end provided. Walk the subtrees that may contain
1018
- // this start position.
1019
- const compareFn =
1020
- end === undefined
1021
- ? (node: IntervalNode<TInterval>) => {
1022
- return transientInterval.compareStart(node.key);
1023
- }
1024
- : (node: IntervalNode<TInterval>) => {
1025
- return transientInterval.compare(node.key);
1026
- };
1027
- const continueLeftFn = (cmpResult: number) => cmpResult <= 0;
1028
- const continueRightFn = (cmpResult: number) => cmpResult >= 0;
1029
- const actionFn = (node: IntervalNode<TInterval>) => {
1030
- results.push(node.key);
1031
- };
1032
-
1033
- if (iteratesForward) {
1034
- this.intervalTree.intervals.walkExactMatchesForward(
1035
- compareFn,
1036
- actionFn,
1037
- continueLeftFn,
1038
- continueRightFn,
1039
- );
1040
- } else {
1041
- this.intervalTree.intervals.walkExactMatchesBackward(
1042
- compareFn,
1043
- actionFn,
1044
- continueLeftFn,
1045
- continueRightFn,
1046
- );
1047
- }
1048
- }
1049
- }
1050
- }
1051
-
1052
- /**
1053
- * @returns an array of all intervals contained in this collection that overlap the range
1054
- * `[startPosition, endPosition)`.
1055
- */
1056
- public findOverlappingIntervals(startPosition: number, endPosition: number) {
1057
- if (endPosition < startPosition || this.intervalTree.intervals.isEmpty()) {
1058
- return [];
1059
- }
1060
- const transientInterval = this.helpers.create(
1061
- "transient",
1062
- startPosition,
1063
- endPosition,
1064
- this.client,
1065
- IntervalType.Transient,
1066
- );
1067
-
1068
- const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
1069
- return overlappingIntervalNodes.map((node) => node.key);
1070
- }
1071
-
1072
- public remove(interval: TInterval) {
1073
- this.intervalTree.removeExisting(interval);
1074
- }
1075
-
1076
- public add(interval: TInterval) {
1077
- this.intervalTree.put(interval);
1078
- }
1079
- }
1080
-
1081
956
  class IdIntervalIndex<TInterval extends ISerializableInterval>
1082
957
  implements IntervalIndex<TInterval>, Iterable<TInterval>
1083
958
  {
@@ -1380,7 +1255,7 @@ export function createStartpointInRangeIndex<TInterval extends ISerializableInte
1380
1255
 
1381
1256
  export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1382
1257
  private static readonly legacyIdPrefix = "legacy";
1383
- public readonly overlappingIntervalsIndex: OverlappingIntervalsIndex<TInterval>;
1258
+ public readonly overlappingIntervalsIndex: IOverlappingIntervalsIndex<TInterval>;
1384
1259
  public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
1385
1260
  public readonly endIntervalIndex: EndpointIndex<TInterval>;
1386
1261
  private readonly indexes: Set<IntervalIndex<TInterval>>;
@@ -1395,7 +1270,7 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1395
1270
  previousInterval: TInterval,
1396
1271
  ) => void,
1397
1272
  ) {
1398
- this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
1273
+ this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
1399
1274
  this.idIntervalIndex = new IdIntervalIndex();
1400
1275
  this.endIntervalIndex = new EndpointIndex(client, helpers);
1401
1276
  this.indexes = new Set([
@@ -1493,6 +1368,17 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1493
1368
  }
1494
1369
 
1495
1370
  if (props) {
1371
+ // This check is intended to prevent scenarios where a random interval is created and then
1372
+ // inserted into a collection. The aim is to ensure that the collection is created first
1373
+ // then the user can create/add intervals based on the collection
1374
+ if (
1375
+ props[reservedRangeLabelsKey] !== undefined &&
1376
+ props[reservedRangeLabelsKey][0] !== this.label
1377
+ ) {
1378
+ throw new LoggingError(
1379
+ "Adding an interval that belongs to another interval collection is not permitted",
1380
+ );
1381
+ }
1496
1382
  interval.addProperties(props);
1497
1383
  }
1498
1384
  interval.properties[reservedIntervalIdKey] ??= uuid();
@@ -1839,6 +1725,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
1839
1725
  * endpoints. These references should be used for position information only.
1840
1726
  * `local` reflects whether the change originated locally.
1841
1727
  * `op` is defined if and only if the server has acked this change.
1728
+ * `slide` is true if the change is due to sliding on removal of position
1842
1729
  */
1843
1730
  (
1844
1731
  event: "changeInterval",
@@ -1847,6 +1734,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
1847
1734
  previousInterval: TInterval,
1848
1735
  local: boolean,
1849
1736
  op: ISequencedDocumentMessage | undefined,
1737
+ slide: boolean,
1850
1738
  ) => void,
1851
1739
  );
1852
1740
  /**
@@ -2112,7 +2000,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2112
2000
  // if segment is undefined, it slid off the string
2113
2001
  assert(segment !== undefined, 0x54e /* No segment found */);
2114
2002
 
2115
- const segoff = this.client.getSlideToSegment({ segment, offset }) ?? segment;
2003
+ const segoff = getSlideToSegoff({ segment, offset }) ?? segment;
2116
2004
 
2117
2005
  // case happens when rebasing op, but concurrently entire string has been deleted
2118
2006
  if (segoff.segment === undefined || segoff.offset === undefined) {
@@ -2176,7 +2064,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2176
2064
  client,
2177
2065
  label,
2178
2066
  this.helpers,
2179
- (interval, previousInterval) => this.emitChange(interval, previousInterval, true),
2067
+ (interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
2180
2068
  );
2181
2069
  if (this.savedSerializedIntervals) {
2182
2070
  for (const serializedInterval of this.savedSerializedIntervals) {
@@ -2216,6 +2104,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2216
2104
  interval: TInterval,
2217
2105
  previousInterval: TInterval,
2218
2106
  local: boolean,
2107
+ slide: boolean,
2219
2108
  op?: ISequencedDocumentMessage,
2220
2109
  ): void {
2221
2110
  // Temporarily make references transient so that positional queries work (non-transient refs
@@ -2228,11 +2117,11 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2228
2117
  endRefType = previousInterval.end.refType;
2229
2118
  previousInterval.start.refType = ReferenceType.Transient;
2230
2119
  previousInterval.end.refType = ReferenceType.Transient;
2231
- this.emit("changeInterval", interval, previousInterval, local, op);
2120
+ this.emit("changeInterval", interval, previousInterval, local, op, slide);
2232
2121
  previousInterval.start.refType = startRefType;
2233
2122
  previousInterval.end.refType = endRefType;
2234
2123
  } else {
2235
- this.emit("changeInterval", interval, previousInterval, local, op);
2124
+ this.emit("changeInterval", interval, previousInterval, local, op, slide);
2236
2125
  }
2237
2126
  }
2238
2127
 
@@ -2351,6 +2240,13 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2351
2240
  if (!props) {
2352
2241
  throw new LoggingError("changeProperties should be called with a property set");
2353
2242
  }
2243
+ // prevent the overwriting of an interval label, it should remain unchanged
2244
+ // once it has been inserted into the collection.
2245
+ if (props[reservedRangeLabelsKey] !== undefined) {
2246
+ throw new LoggingError(
2247
+ "The label property should not be modified once inserted to the collection",
2248
+ );
2249
+ }
2354
2250
 
2355
2251
  const interval = this.getIntervalById(id);
2356
2252
  if (interval) {
@@ -2402,7 +2298,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2402
2298
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
2403
2299
  this.emitter.emit("change", undefined, serializedInterval, { localSeq });
2404
2300
  this.addPendingChange(id, serializedInterval);
2405
- this.emitChange(newInterval, interval, true);
2301
+ this.emitChange(newInterval, interval, true, false);
2406
2302
  return newInterval;
2407
2303
  }
2408
2304
  // No interval to change
@@ -2538,7 +2434,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2538
2434
  }
2539
2435
 
2540
2436
  if (newInterval !== interval) {
2541
- this.emitChange(newInterval, interval, local, op);
2437
+ this.emitChange(newInterval, interval, local, false, op);
2542
2438
  }
2543
2439
 
2544
2440
  const changedProperties = Object.keys(newProps).length > 0;
@@ -2654,7 +2550,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2654
2550
  return rebased;
2655
2551
  }
2656
2552
 
2657
- private getSlideToSegment(lref: LocalReferencePosition) {
2553
+ private getSlideToSegment(
2554
+ lref: LocalReferencePosition,
2555
+ ): { segment: ISegment | undefined; offset: number | undefined } | undefined {
2658
2556
  if (!this.client) {
2659
2557
  throw new LoggingError("client does not exist");
2660
2558
  }
@@ -2662,7 +2560,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2662
2560
  if (segoff.segment?.localRefs?.has(lref) !== true) {
2663
2561
  return undefined;
2664
2562
  }
2665
- const newSegoff = this.client.getSlideToSegment(segoff);
2563
+ const newSegoff = getSlideToSegoff(segoff);
2666
2564
  const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
2667
2565
  segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
2668
2566
  ? undefined
@@ -2761,7 +2659,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2761
2659
  oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
2762
2660
  }
2763
2661
  this.localCollection.add(interval);
2764
- this.emitChange(interval, oldInterval as TInterval, true, op);
2662
+ this.emitChange(interval, oldInterval as TInterval, true, true, op);
2765
2663
  }
2766
2664
  }
2767
2665
 
@@ -0,0 +1,11 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ export { SequenceIntervalIndexes } from "./sequenceIntervalIndexes";
7
+ export {
8
+ IOverlappingIntervalsIndex,
9
+ createOverlappingIntervalsIndex,
10
+ } from "./overlappingIntervalsIndex";
11
+ export { createOverlappingSequenceIntervalsIndex } from "./overlappingSequenceIntervalsIndex";
@@ -0,0 +1,166 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { Client } from "@fluidframework/merge-tree";
7
+ import {
8
+ IntervalType,
9
+ IIntervalHelpers,
10
+ IntervalIndex,
11
+ ISerializableInterval,
12
+ } from "../intervalCollection";
13
+ import { IntervalNode, IntervalTree } from "../intervalTree";
14
+
15
+ export interface IOverlappingIntervalsIndex<TInterval extends ISerializableInterval>
16
+ extends IntervalIndex<TInterval> {
17
+ /**
18
+ * @returns an array of all intervals contained in this collection that overlap the range
19
+ * `[start end]`.
20
+ */
21
+ findOverlappingIntervals(start: number, end: number): TInterval[];
22
+
23
+ /**
24
+ * Gathers the interval results based on specified parameters.
25
+ */
26
+ gatherIterationResults(
27
+ results: TInterval[],
28
+ iteratesForward: boolean,
29
+ start?: number,
30
+ end?: number,
31
+ ): void;
32
+ }
33
+
34
+ export class OverlappingIntervalsIndex<TInterval extends ISerializableInterval>
35
+ implements IOverlappingIntervalsIndex<TInterval>
36
+ {
37
+ protected readonly intervalTree = new IntervalTree<TInterval>();
38
+ protected readonly client: Client;
39
+ protected readonly helpers: IIntervalHelpers<TInterval>;
40
+
41
+ constructor(client: Client, helpers: IIntervalHelpers<TInterval>) {
42
+ this.client = client;
43
+ this.helpers = helpers;
44
+ }
45
+
46
+ public map(fn: (interval: TInterval) => void) {
47
+ this.intervalTree.map(fn);
48
+ }
49
+
50
+ public mapUntil(fn: (interval: TInterval) => boolean) {
51
+ this.intervalTree.mapUntil(fn);
52
+ }
53
+
54
+ public gatherIterationResults(
55
+ results: TInterval[],
56
+ iteratesForward: boolean,
57
+ start?: number,
58
+ end?: number,
59
+ ): void {
60
+ if (this.intervalTree.intervals.isEmpty()) {
61
+ return;
62
+ }
63
+
64
+ if (start === undefined && end === undefined) {
65
+ // No start/end provided. Gather the whole tree in the specified order.
66
+ if (iteratesForward) {
67
+ this.intervalTree.map((interval: TInterval) => {
68
+ results.push(interval);
69
+ });
70
+ } else {
71
+ this.intervalTree.mapBackward((interval: TInterval) => {
72
+ results.push(interval);
73
+ });
74
+ }
75
+ } else {
76
+ const transientInterval: TInterval = this.helpers.create(
77
+ "transient",
78
+ start,
79
+ end,
80
+ this.client,
81
+ IntervalType.Transient,
82
+ );
83
+
84
+ if (start === undefined) {
85
+ // Only end position provided. Since the tree is not sorted by end position,
86
+ // walk the whole tree in the specified order, gathering intervals that match the end.
87
+ if (iteratesForward) {
88
+ this.intervalTree.map((interval: TInterval) => {
89
+ if (transientInterval.compareEnd(interval) === 0) {
90
+ results.push(interval);
91
+ }
92
+ });
93
+ } else {
94
+ this.intervalTree.mapBackward((interval: TInterval) => {
95
+ if (transientInterval.compareEnd(interval) === 0) {
96
+ results.push(interval);
97
+ }
98
+ });
99
+ }
100
+ } else {
101
+ // Start and (possibly) end provided. Walk the subtrees that may contain
102
+ // this start position.
103
+ const compareFn =
104
+ end === undefined
105
+ ? (node: IntervalNode<TInterval>) => {
106
+ return transientInterval.compareStart(node.key);
107
+ }
108
+ : (node: IntervalNode<TInterval>) => {
109
+ return transientInterval.compare(node.key);
110
+ };
111
+ const continueLeftFn = (cmpResult: number) => cmpResult <= 0;
112
+ const continueRightFn = (cmpResult: number) => cmpResult >= 0;
113
+ const actionFn = (node: IntervalNode<TInterval>) => {
114
+ results.push(node.key);
115
+ };
116
+
117
+ if (iteratesForward) {
118
+ this.intervalTree.intervals.walkExactMatchesForward(
119
+ compareFn,
120
+ actionFn,
121
+ continueLeftFn,
122
+ continueRightFn,
123
+ );
124
+ } else {
125
+ this.intervalTree.intervals.walkExactMatchesBackward(
126
+ compareFn,
127
+ actionFn,
128
+ continueLeftFn,
129
+ continueRightFn,
130
+ );
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ public findOverlappingIntervals(start: number, end: number): TInterval[] {
137
+ if (end < start || this.intervalTree.intervals.isEmpty()) {
138
+ return [];
139
+ }
140
+ const transientInterval = this.helpers.create(
141
+ "transient",
142
+ start,
143
+ end,
144
+ this.client,
145
+ IntervalType.Transient,
146
+ );
147
+
148
+ const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
149
+ return overlappingIntervalNodes.map((node) => node.key);
150
+ }
151
+
152
+ public remove(interval: TInterval) {
153
+ this.intervalTree.removeExisting(interval);
154
+ }
155
+
156
+ public add(interval: TInterval) {
157
+ this.intervalTree.put(interval);
158
+ }
159
+ }
160
+
161
+ export function createOverlappingIntervalsIndex<TInterval extends ISerializableInterval>(
162
+ client: Client,
163
+ helpers: IIntervalHelpers<TInterval>,
164
+ ): IOverlappingIntervalsIndex<TInterval> {
165
+ return new OverlappingIntervalsIndex<TInterval>(client, helpers);
166
+ }
@@ -0,0 +1,71 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ Client,
8
+ ISegment,
9
+ ReferenceType,
10
+ compareReferencePositions,
11
+ reservedRangeLabelsKey,
12
+ } from "@fluidframework/merge-tree";
13
+ import {
14
+ sequenceIntervalHelpers,
15
+ IntervalType,
16
+ SequenceInterval,
17
+ createPositionReferenceFromSegoff,
18
+ } from "../intervalCollection";
19
+ import { SequenceIntervalIndexes } from "./sequenceIntervalIndexes";
20
+ import { OverlappingIntervalsIndex } from "./overlappingIntervalsIndex";
21
+
22
+ class OverlappingSequenceIntervalsIndex
23
+ extends OverlappingIntervalsIndex<SequenceInterval>
24
+ implements SequenceIntervalIndexes.Overlapping
25
+ {
26
+ constructor(client: Client) {
27
+ super(client, sequenceIntervalHelpers);
28
+ }
29
+
30
+ public findOverlappingIntervalsBySegoff(
31
+ startSegoff: { segment: ISegment | undefined; offset: number | undefined },
32
+ endSegoff: { segment: ISegment | undefined; offset: number | undefined },
33
+ ): Iterable<SequenceInterval> {
34
+ if (this.intervalTree.intervals.isEmpty()) {
35
+ return [];
36
+ }
37
+
38
+ const startLref = createPositionReferenceFromSegoff(
39
+ this.client,
40
+ startSegoff,
41
+ ReferenceType.Transient,
42
+ );
43
+
44
+ const endLref = createPositionReferenceFromSegoff(
45
+ this.client,
46
+ endSegoff,
47
+ ReferenceType.Transient,
48
+ );
49
+
50
+ if (compareReferencePositions(startLref, endLref) > 0) {
51
+ return [];
52
+ }
53
+
54
+ const transientInterval = new SequenceInterval(
55
+ this.client,
56
+ startLref,
57
+ endLref,
58
+ IntervalType.Transient,
59
+ { [reservedRangeLabelsKey]: ["transient"] },
60
+ );
61
+
62
+ const overlappingIntervalNodes = this.intervalTree.match(transientInterval);
63
+ return overlappingIntervalNodes.map((node) => node.key);
64
+ }
65
+ }
66
+
67
+ export function createOverlappingSequenceIntervalsIndex(
68
+ client: Client,
69
+ ): SequenceIntervalIndexes.Overlapping {
70
+ return new OverlappingSequenceIntervalsIndex(client);
71
+ }
@@ -0,0 +1,32 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ISegment } from "@fluidframework/merge-tree";
7
+ import { SequenceInterval } from "../intervalCollection";
8
+ import { IOverlappingIntervalsIndex } from "./overlappingIntervalsIndex";
9
+
10
+ /**
11
+ * This namespace contains specialiazations of indexes which support spatial queries
12
+ * specifically for `SequenceInterval`s.
13
+ */
14
+ // eslint-disable-next-line @typescript-eslint/no-namespace
15
+ export namespace SequenceIntervalIndexes {
16
+ /**
17
+ * Collection of intervals.
18
+ *
19
+ * Provides additional APIs to support efficiently querying a collection of intervals based on segments and offset.
20
+ */
21
+ export interface Overlapping extends IOverlappingIntervalsIndex<SequenceInterval> {
22
+ /**
23
+ * Finds overlapping intervals within the specified range.
24
+ *
25
+ * @returns an array of all intervals that overlap with the specified SegOff range (includes both ends)
26
+ */
27
+ findOverlappingIntervalsBySegoff(
28
+ startSegoff: { segment: ISegment | undefined; offset: number | undefined },
29
+ endSegoff: { segment: ISegment | undefined; offset: number | undefined },
30
+ );
31
+ }
32
+ }