@fluidframework/sequence 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258

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 (231) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/README.md +4 -3
  3. package/dist/defaultMap.d.ts +1 -1
  4. package/dist/defaultMap.d.ts.map +1 -1
  5. package/dist/defaultMap.js +9 -10
  6. package/dist/defaultMap.js.map +1 -1
  7. package/dist/defaultMapInterfaces.d.ts +1 -1
  8. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  9. package/dist/defaultMapInterfaces.js.map +1 -1
  10. package/dist/index.d.ts +3 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +15 -9
  13. package/dist/index.js.map +1 -1
  14. package/dist/intervalCollection.d.ts +14 -437
  15. package/dist/intervalCollection.d.ts.map +1 -1
  16. package/dist/intervalCollection.js +96 -916
  17. package/dist/intervalCollection.js.map +1 -1
  18. package/dist/intervalIndex/endpointInRangeIndex.d.ts +20 -0
  19. package/dist/intervalIndex/endpointInRangeIndex.d.ts.map +1 -0
  20. package/dist/intervalIndex/endpointInRangeIndex.js +60 -0
  21. package/dist/intervalIndex/endpointInRangeIndex.js.map +1 -0
  22. package/dist/intervalIndex/endpointIndex.d.ts +21 -0
  23. package/dist/intervalIndex/endpointIndex.d.ts.map +1 -0
  24. package/dist/intervalIndex/endpointIndex.js +42 -0
  25. package/dist/intervalIndex/endpointIndex.js.map +1 -0
  26. package/dist/intervalIndex/idIntervalIndex.d.ts +12 -0
  27. package/dist/intervalIndex/idIntervalIndex.d.ts.map +1 -0
  28. package/dist/intervalIndex/idIntervalIndex.js +41 -0
  29. package/dist/intervalIndex/idIntervalIndex.js.map +1 -0
  30. package/dist/intervalIndex/index.d.ts +13 -0
  31. package/dist/intervalIndex/index.d.ts.map +1 -0
  32. package/dist/intervalIndex/index.js +20 -0
  33. package/dist/intervalIndex/index.js.map +1 -0
  34. package/dist/intervalIndex/intervalIndex.d.ts +29 -0
  35. package/dist/intervalIndex/intervalIndex.d.ts.map +1 -0
  36. package/dist/intervalIndex/intervalIndex.js +7 -0
  37. package/dist/intervalIndex/intervalIndex.js.map +1 -0
  38. package/dist/intervalIndex/intervalIndexUtils.d.ts +17 -0
  39. package/dist/intervalIndex/intervalIndexUtils.d.ts.map +1 -0
  40. package/dist/intervalIndex/intervalIndexUtils.js +22 -0
  41. package/dist/intervalIndex/intervalIndexUtils.js.map +1 -0
  42. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +33 -0
  43. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
  44. package/dist/intervalIndex/overlappingIntervalsIndex.js +103 -0
  45. package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
  46. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
  47. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
  48. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +33 -0
  49. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
  50. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
  51. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
  52. package/dist/intervalIndex/sequenceIntervalIndexes.js +7 -0
  53. package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
  54. package/dist/intervalIndex/startpointInRangeIndex.d.ts +20 -0
  55. package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -0
  56. package/dist/intervalIndex/startpointInRangeIndex.js +62 -0
  57. package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -0
  58. package/dist/intervalTree.d.ts +2 -56
  59. package/dist/intervalTree.d.ts.map +1 -1
  60. package/dist/intervalTree.js +2 -11
  61. package/dist/intervalTree.js.map +1 -1
  62. package/dist/intervals/index.d.ts +8 -0
  63. package/dist/intervals/index.d.ts.map +1 -0
  64. package/dist/intervals/index.js +23 -0
  65. package/dist/intervals/index.js.map +1 -0
  66. package/dist/intervals/interval.d.ts +88 -0
  67. package/dist/intervals/interval.d.ts.map +1 -0
  68. package/dist/intervals/interval.js +180 -0
  69. package/dist/intervals/interval.js.map +1 -0
  70. package/dist/intervals/intervalUtils.d.ts +200 -0
  71. package/dist/intervals/intervalUtils.d.ts.map +1 -0
  72. package/dist/intervals/intervalUtils.js +79 -0
  73. package/dist/intervals/intervalUtils.js.map +1 -0
  74. package/dist/intervals/sequenceInterval.d.ts +132 -0
  75. package/dist/intervals/sequenceInterval.d.ts.map +1 -0
  76. package/dist/intervals/sequenceInterval.js +313 -0
  77. package/dist/intervals/sequenceInterval.js.map +1 -0
  78. package/dist/packageVersion.d.ts +1 -1
  79. package/dist/packageVersion.js +1 -1
  80. package/dist/packageVersion.js.map +1 -1
  81. package/dist/revertibles.d.ts +1 -1
  82. package/dist/revertibles.d.ts.map +1 -1
  83. package/dist/revertibles.js +85 -52
  84. package/dist/revertibles.js.map +1 -1
  85. package/dist/sequence.d.ts +33 -4
  86. package/dist/sequence.d.ts.map +1 -1
  87. package/dist/sequence.js +91 -47
  88. package/dist/sequence.js.map +1 -1
  89. package/dist/sequenceDeltaEvent.d.ts +8 -3
  90. package/dist/sequenceDeltaEvent.d.ts.map +1 -1
  91. package/dist/sequenceDeltaEvent.js +3 -4
  92. package/dist/sequenceDeltaEvent.js.map +1 -1
  93. package/dist/sharedIntervalCollection.d.ts +2 -1
  94. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  95. package/dist/sharedIntervalCollection.js +2 -2
  96. package/dist/sharedIntervalCollection.js.map +1 -1
  97. package/dist/sharedSequence.d.ts +9 -0
  98. package/dist/sharedSequence.d.ts.map +1 -1
  99. package/dist/sharedSequence.js +9 -6
  100. package/dist/sharedSequence.js.map +1 -1
  101. package/dist/sharedString.d.ts.map +1 -1
  102. package/dist/sharedString.js +9 -29
  103. package/dist/sharedString.js.map +1 -1
  104. package/lib/defaultMap.d.ts +1 -1
  105. package/lib/defaultMap.d.ts.map +1 -1
  106. package/lib/defaultMap.js +5 -6
  107. package/lib/defaultMap.js.map +1 -1
  108. package/lib/defaultMapInterfaces.d.ts +1 -1
  109. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  110. package/lib/defaultMapInterfaces.js.map +1 -1
  111. package/lib/index.d.ts +3 -2
  112. package/lib/index.d.ts.map +1 -1
  113. package/lib/index.js +3 -1
  114. package/lib/index.js.map +1 -1
  115. package/lib/intervalCollection.d.ts +14 -437
  116. package/lib/intervalCollection.d.ts.map +1 -1
  117. package/lib/intervalCollection.js +64 -877
  118. package/lib/intervalCollection.js.map +1 -1
  119. package/lib/intervalIndex/endpointInRangeIndex.d.ts +20 -0
  120. package/lib/intervalIndex/endpointInRangeIndex.d.ts.map +1 -0
  121. package/lib/intervalIndex/endpointInRangeIndex.js +56 -0
  122. package/lib/intervalIndex/endpointInRangeIndex.js.map +1 -0
  123. package/lib/intervalIndex/endpointIndex.d.ts +21 -0
  124. package/lib/intervalIndex/endpointIndex.d.ts.map +1 -0
  125. package/lib/intervalIndex/endpointIndex.js +38 -0
  126. package/lib/intervalIndex/endpointIndex.js.map +1 -0
  127. package/lib/intervalIndex/idIntervalIndex.d.ts +12 -0
  128. package/lib/intervalIndex/idIntervalIndex.d.ts.map +1 -0
  129. package/lib/intervalIndex/idIntervalIndex.js +37 -0
  130. package/lib/intervalIndex/idIntervalIndex.js.map +1 -0
  131. package/lib/intervalIndex/index.d.ts +13 -0
  132. package/lib/intervalIndex/index.d.ts.map +1 -0
  133. package/lib/intervalIndex/index.js +11 -0
  134. package/lib/intervalIndex/index.js.map +1 -0
  135. package/lib/intervalIndex/intervalIndex.d.ts +29 -0
  136. package/lib/intervalIndex/intervalIndex.d.ts.map +1 -0
  137. package/lib/intervalIndex/intervalIndex.js +6 -0
  138. package/lib/intervalIndex/intervalIndex.js.map +1 -0
  139. package/lib/intervalIndex/intervalIndexUtils.d.ts +17 -0
  140. package/lib/intervalIndex/intervalIndexUtils.d.ts.map +1 -0
  141. package/lib/intervalIndex/intervalIndexUtils.js +18 -0
  142. package/lib/intervalIndex/intervalIndexUtils.js.map +1 -0
  143. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +33 -0
  144. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -0
  145. package/lib/intervalIndex/overlappingIntervalsIndex.js +98 -0
  146. package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -0
  147. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts +8 -0
  148. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -0
  149. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +29 -0
  150. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -0
  151. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +33 -0
  152. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -0
  153. package/lib/intervalIndex/sequenceIntervalIndexes.js +6 -0
  154. package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -0
  155. package/lib/intervalIndex/startpointInRangeIndex.d.ts +20 -0
  156. package/lib/intervalIndex/startpointInRangeIndex.d.ts.map +1 -0
  157. package/lib/intervalIndex/startpointInRangeIndex.js +58 -0
  158. package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -0
  159. package/lib/intervalTree.d.ts +2 -56
  160. package/lib/intervalTree.d.ts.map +1 -1
  161. package/lib/intervalTree.js +2 -11
  162. package/lib/intervalTree.js.map +1 -1
  163. package/lib/intervals/index.d.ts +8 -0
  164. package/lib/intervals/index.d.ts.map +1 -0
  165. package/lib/intervals/index.js +8 -0
  166. package/lib/intervals/index.js.map +1 -0
  167. package/lib/intervals/interval.d.ts +88 -0
  168. package/lib/intervals/interval.d.ts.map +1 -0
  169. package/lib/intervals/interval.js +175 -0
  170. package/lib/intervals/interval.js.map +1 -0
  171. package/lib/intervals/intervalUtils.d.ts +200 -0
  172. package/lib/intervals/intervalUtils.d.ts.map +1 -0
  173. package/lib/intervals/intervalUtils.js +74 -0
  174. package/lib/intervals/intervalUtils.js.map +1 -0
  175. package/lib/intervals/sequenceInterval.d.ts +132 -0
  176. package/lib/intervals/sequenceInterval.d.ts.map +1 -0
  177. package/lib/intervals/sequenceInterval.js +305 -0
  178. package/lib/intervals/sequenceInterval.js.map +1 -0
  179. package/lib/packageVersion.d.ts +1 -1
  180. package/lib/packageVersion.js +1 -1
  181. package/lib/packageVersion.js.map +1 -1
  182. package/lib/revertibles.d.ts +1 -1
  183. package/lib/revertibles.d.ts.map +1 -1
  184. package/lib/revertibles.js +69 -36
  185. package/lib/revertibles.js.map +1 -1
  186. package/lib/sequence.d.ts +33 -4
  187. package/lib/sequence.d.ts.map +1 -1
  188. package/lib/sequence.js +86 -41
  189. package/lib/sequence.js.map +1 -1
  190. package/lib/sequenceDeltaEvent.d.ts +8 -3
  191. package/lib/sequenceDeltaEvent.d.ts.map +1 -1
  192. package/lib/sequenceDeltaEvent.js +2 -3
  193. package/lib/sequenceDeltaEvent.js.map +1 -1
  194. package/lib/sharedIntervalCollection.d.ts +2 -1
  195. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  196. package/lib/sharedIntervalCollection.js +1 -1
  197. package/lib/sharedIntervalCollection.js.map +1 -1
  198. package/lib/sharedSequence.d.ts +9 -0
  199. package/lib/sharedSequence.d.ts.map +1 -1
  200. package/lib/sharedSequence.js +8 -5
  201. package/lib/sharedSequence.js.map +1 -1
  202. package/lib/sharedString.d.ts.map +1 -1
  203. package/lib/sharedString.js +9 -29
  204. package/lib/sharedString.js.map +1 -1
  205. package/package.json +31 -34
  206. package/src/defaultMap.ts +2 -1
  207. package/src/defaultMapInterfaces.ts +1 -1
  208. package/src/index.ts +21 -9
  209. package/src/intervalCollection.ts +118 -1403
  210. package/src/intervalIndex/endpointInRangeIndex.ts +104 -0
  211. package/src/intervalIndex/endpointIndex.ts +78 -0
  212. package/src/intervalIndex/idIntervalIndex.ts +58 -0
  213. package/src/intervalIndex/index.ts +16 -0
  214. package/src/intervalIndex/intervalIndex.ts +31 -0
  215. package/src/intervalIndex/intervalIndexUtils.ts +27 -0
  216. package/src/intervalIndex/overlappingIntervalsIndex.ts +162 -0
  217. package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +71 -0
  218. package/src/intervalIndex/sequenceIntervalIndexes.ts +32 -0
  219. package/src/intervalIndex/startpointInRangeIndex.ts +109 -0
  220. package/src/intervalTree.ts +3 -75
  221. package/src/intervals/index.ts +25 -0
  222. package/src/intervals/interval.ts +230 -0
  223. package/src/intervals/intervalUtils.ts +256 -0
  224. package/src/intervals/sequenceInterval.ts +494 -0
  225. package/src/packageVersion.ts +1 -1
  226. package/src/revertibles.ts +81 -16
  227. package/src/sequence.ts +100 -35
  228. package/src/sequenceDeltaEvent.ts +12 -4
  229. package/src/sharedIntervalCollection.ts +2 -3
  230. package/src/sharedSequence.ts +11 -5
  231. package/src/sharedString.ts +8 -25
@@ -5,34 +5,26 @@
5
5
 
6
6
  /* eslint-disable no-bitwise */
7
7
 
8
- import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
9
- import { IEvent } from "@fluidframework/common-definitions";
10
- import { UsageError } from "@fluidframework/container-utils";
8
+ import { TypedEventEmitter } from "@fluid-internal/client-utils";
9
+ import { assert } from "@fluidframework/core-utils";
10
+ import { IEvent } from "@fluidframework/core-interfaces";
11
11
  import {
12
12
  addProperties,
13
13
  Client,
14
- compareReferencePositions,
15
14
  createMap,
16
- ICombiningOp,
15
+ getSlideToSegoff,
17
16
  ISegment,
18
17
  MergeTreeDeltaType,
19
- minReferencePosition,
20
- PropertiesManager,
21
18
  PropertySet,
22
- RedBlackTree,
23
19
  LocalReferencePosition,
24
20
  ReferenceType,
25
21
  refTypeIncludesFlag,
26
22
  reservedRangeLabelsKey,
27
23
  UnassignedSequenceNumber,
28
- maxReferencePosition,
29
- createDetachedLocalReferencePosition,
30
24
  DetachedReferencePosition,
31
- SlidingPreference,
32
- PropertyAction,
33
25
  } from "@fluidframework/merge-tree";
34
26
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
35
- import { LoggingError } from "@fluidframework/telemetry-utils";
27
+ import { LoggingError, UsageError } from "@fluidframework/telemetry-utils";
36
28
  import { v4 as uuid } from "uuid";
37
29
  import {
38
30
  IMapMessageLocalMetadata,
@@ -43,1346 +35,97 @@ import {
43
35
  IValueTypeOperationValue,
44
36
  SequenceOptions,
45
37
  } from "./defaultMapInterfaces";
46
- import { IInterval, IntervalConflictResolver, IntervalTree, IntervalNode } from "./intervalTree";
47
-
48
- const reservedIntervalIdKey = "intervalId";
49
-
50
- /**
51
- * Values are used in persisted formats (ops) and revertibles.
52
- * @alpha
53
- */
54
- export const IntervalOpType = {
55
- ADD: "add",
56
- DELETE: "delete",
57
- CHANGE: "change",
58
- PROPERTY_CHANGED: "propertyChanged",
59
- POSITION_REMOVE: "positionRemove",
60
- } as const;
61
-
62
- export enum IntervalType {
63
- Simple = 0x0,
64
- Nest = 0x1,
65
-
66
- /**
67
- * SlideOnRemove indicates that the ends of the interval will slide if the segment
68
- * they reference is removed and acked.
69
- * See `packages\dds\merge-tree\docs\REFERENCEPOSITIONS.md` for details
70
- * SlideOnRemove is the default interval behavior and does not need to be specified.
71
- */
72
- SlideOnRemove = 0x2, // SlideOnRemove is default behavior - all intervals are SlideOnRemove
73
-
74
- /**
75
- * A temporary interval, used internally
76
- * @internal
77
- */
78
- Transient = 0x4,
79
- }
80
-
81
- /**
82
- * Serialized object representation of an interval.
83
- * This representation is used for ops that create or change intervals.
84
- * @internal
85
- */
86
- export interface ISerializedInterval {
87
- /**
88
- * Sequence number at which `start` and `end` should be interpreted
89
- *
90
- * @remarks - It's unclear that this is necessary to store here.
91
- * This should just be the refSeq on the op that modified the interval, which should be available via other means.
92
- * At the time of writing, it's not plumbed through to the reconnect/rebase code, however, which does need it.
93
- */
94
- sequenceNumber: number;
95
- /** Start position of the interval */
96
- start: number;
97
- /** End position of the interval */
98
- end: number;
99
- /** Interval type to create */
100
- intervalType: IntervalType;
101
- stickiness?: IntervalStickiness;
102
- /** Any properties the interval has */
103
- properties?: PropertySet;
104
- }
105
-
106
- /**
107
- * Represents a change that should be applied to an existing interval.
108
- * Changes can modify any of start/end/properties, with `undefined` signifying no change should be made.
109
- * @internal
110
- */
111
- export type SerializedIntervalDelta = Omit<ISerializedInterval, "start" | "end" | "properties"> &
112
- Partial<Pick<ISerializedInterval, "start" | "end" | "properties">>;
113
-
114
- /**
115
- * A size optimization to avoid redundantly storing keys when serializing intervals
116
- * as JSON for summaries.
117
- *
118
- * Intervals are of the format:
119
- *
120
- * [start, end, sequenceNumber, intervalType, properties, stickiness?]
121
- */
122
- export type CompressedSerializedInterval =
123
- | [number, number, number, IntervalType, PropertySet, IntervalStickiness]
124
- | [number, number, number, IntervalType, PropertySet];
125
-
126
- export interface ISerializedIntervalCollectionV2 {
127
- label: string;
128
- version: 2;
129
- intervals: CompressedSerializedInterval[];
130
- }
131
-
132
- /**
133
- * Decompress an interval after loading a summary from JSON. The exact format
134
- * of this compression is unspecified and subject to change
135
- */
136
- function decompressInterval(
137
- interval: CompressedSerializedInterval,
138
- label?: string,
139
- ): ISerializedInterval {
140
- return {
141
- start: interval[0],
142
- end: interval[1],
143
- sequenceNumber: interval[2],
144
- intervalType: interval[3],
145
- properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
146
- stickiness: interval[5],
147
- };
148
- }
149
-
150
- /**
151
- * Compress an interval prior to serialization as JSON. The exact format of this
152
- * compression is unspecified and subject to change
153
- */
154
- function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
155
- const { start, end, sequenceNumber, intervalType, properties } = interval;
156
-
157
- const base: CompressedSerializedInterval = [
158
- start,
159
- end,
160
- sequenceNumber,
161
- intervalType,
162
- // remove the `referenceRangeLabels` property as it is already stored
163
- // in the `label` field of the summary
164
- { ...properties, [reservedRangeLabelsKey]: undefined },
165
- ];
166
-
167
- if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
168
- base.push(interval.stickiness);
169
- }
170
-
171
- return base;
172
- }
173
-
174
- function startReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
175
- // if any start stickiness, prefer sliding backwards
176
- return (stickiness & IntervalStickiness.START) !== 0
177
- ? SlidingPreference.BACKWARD
178
- : SlidingPreference.FORWARD;
179
- }
180
-
181
- function endReferenceSlidingPreference(stickiness: IntervalStickiness): SlidingPreference {
182
- // if any end stickiness, prefer sliding forwards
183
- return (stickiness & IntervalStickiness.END) !== 0
184
- ? SlidingPreference.FORWARD
185
- : SlidingPreference.BACKWARD;
186
- }
187
-
188
- export interface ISerializableInterval extends IInterval {
189
- /** Serializable bag of properties associated with the interval. */
190
- properties: PropertySet;
191
- /** @internal */
192
- propertyManager: PropertiesManager;
193
- /** @internal */
194
- serialize(): ISerializedInterval;
195
- /** @internal */
196
- addProperties(
197
- props: PropertySet,
198
- collaborating?: boolean,
199
- seq?: number,
200
- ): PropertySet | undefined;
201
- /**
202
- * Gets the id associated with this interval.
203
- * When the interval is used as part of an interval collection, this id can be used to modify or remove the
204
- * interval.
205
- * @remarks - This signature includes `undefined` strictly for backwards-compatibility reasons, as older versions
206
- * of Fluid didn't always write interval ids.
207
- */
208
- getIntervalId(): string | undefined;
209
- }
210
-
211
- /**
212
- * @sealed
213
- */
214
- export interface IIntervalHelpers<TInterval extends ISerializableInterval> {
215
- compareEnds(a: TInterval, b: TInterval): number;
216
- compareStarts?(a: TInterval, b: TInterval): number;
217
- /**
218
- *
219
- * @param label - label of the interval collection this interval is being added to. This parameter is
220
- * irrelevant for transient intervals.
221
- * @param start - numerical start position of the interval
222
- * @param end - numerical end position of the interval
223
- * @param client - client creating the interval
224
- * @param intervalType - Type of interval to create. Default is SlideOnRemove
225
- * @param op - If this create came from a remote client, op that created it. Default is undefined (i.e. local)
226
- * @param fromSnapshot - If this create came from loading a snapshot. Default is false.
227
- */
228
- create(
229
- label: string,
230
- start: number | undefined,
231
- end: number | undefined,
232
- client: Client | undefined,
233
- intervalType: IntervalType,
234
- op?: ISequencedDocumentMessage,
235
- fromSnapshot?: boolean,
236
- stickiness?: IntervalStickiness,
237
- ): TInterval;
238
- }
239
-
240
- /**
241
- * Determines how an interval should expand when segments are inserted adjacent
242
- * to the range it spans
243
- *
244
- * Note that interval stickiness is currently an experimental feature and must
245
- * be explicitly enabled with the `intervalStickinessEnabled` flag
246
- */
247
- export const IntervalStickiness = {
248
- /**
249
- * Interval does not expand to include adjacent segments
250
- */
251
- NONE: 0b00,
252
-
253
- /**
254
- * Interval expands to include segments inserted adjacent to the start
255
- */
256
- START: 0b01,
257
-
258
- /**
259
- * Interval expands to include segments inserted adjacent to the end
260
- *
261
- * This is the default stickiness
262
- */
263
- END: 0b10,
264
-
265
- /**
266
- * Interval expands to include all segments inserted adjacent to it
267
- */
268
- FULL: 0b11,
269
- } as const;
270
-
271
- /**
272
- * Determines how an interval should expand when segments are inserted adjacent
273
- * to the range it spans
274
- *
275
- * Note that interval stickiness is currently an experimental feature and must
276
- * be explicitly enabled with the `intervalStickinessEnabled` flag
277
- */
278
- export type IntervalStickiness = typeof IntervalStickiness[keyof typeof IntervalStickiness];
279
-
280
- /**
281
- * Serializable interval whose endpoints are plain-old numbers.
282
- */
283
- export class Interval implements ISerializableInterval {
284
- /**
285
- * {@inheritDoc ISerializableInterval.properties}
286
- */
287
- public properties: PropertySet;
288
- /** @internal */
289
- public auxProps: PropertySet[] | undefined;
290
- /**
291
- * {@inheritDoc ISerializableInterval.propertyManager}
292
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
293
- */
294
- public propertyManager: PropertiesManager;
295
- constructor(public start: number, public end: number, props?: PropertySet) {
296
- this.propertyManager = new PropertiesManager();
297
- this.properties = {};
298
-
299
- if (props) {
300
- this.addProperties(props);
301
- }
302
- }
303
-
304
- /**
305
- * {@inheritDoc ISerializableInterval.getIntervalId}
306
- */
307
- public getIntervalId(): string {
308
- const id = this.properties?.[reservedIntervalIdKey];
309
- assert(id !== undefined, 0x5e1 /* interval ID should not be undefined */);
310
- return `${id}`;
311
- }
312
-
313
- /**
314
- * @returns an array containing any auxiliary property sets added with `addPropertySet`.
315
- */
316
- public getAdditionalPropertySets(): PropertySet[] {
317
- return this.auxProps ?? [];
318
- }
319
-
320
- /**
321
- * Adds an auxiliary set of properties to this interval.
322
- * These properties can be recovered using `getAdditionalPropertySets`
323
- * @param props - set of properties to add
324
- * @remarks - This gets called as part of the default conflict resolver for `IIntervalCollection<Interval>`
325
- * (i.e. non-sequence-based interval collections). However, the additional properties don't get serialized.
326
- * This functionality seems half-baked.
327
- */
328
- public addPropertySet(props: PropertySet) {
329
- if (this.auxProps === undefined) {
330
- this.auxProps = [];
331
- }
332
- this.auxProps.push(props);
333
- }
334
-
335
- /**
336
- * {@inheritDoc ISerializableInterval.serialize}
337
- * @internal
338
- */
339
- public serialize(): ISerializedInterval {
340
- const serializedInterval: ISerializedInterval = {
341
- end: this.end,
342
- intervalType: 0,
343
- sequenceNumber: 0,
344
- start: this.start,
345
- };
346
- if (this.properties) {
347
- serializedInterval.properties = this.properties;
348
- }
349
- return serializedInterval;
350
- }
351
-
352
- /**
353
- * {@inheritDoc IInterval.clone}
354
- */
355
- public clone() {
356
- return new Interval(this.start, this.end, this.properties);
357
- }
358
-
359
- /**
360
- * {@inheritDoc IInterval.compare}
361
- */
362
- public compare(b: Interval) {
363
- const startResult = this.compareStart(b);
364
- if (startResult === 0) {
365
- const endResult = this.compareEnd(b);
366
- if (endResult === 0) {
367
- const thisId = this.getIntervalId();
368
- if (thisId) {
369
- const bId = b.getIntervalId();
370
- if (bId) {
371
- return thisId > bId ? 1 : thisId < bId ? -1 : 0;
372
- }
373
- return 0;
374
- }
375
- return 0;
376
- } else {
377
- return endResult;
378
- }
379
- } else {
380
- return startResult;
381
- }
382
- }
383
-
384
- /**
385
- * {@inheritDoc IInterval.compareStart}
386
- */
387
- public compareStart(b: Interval) {
388
- return this.start - b.start;
389
- }
390
-
391
- /**
392
- * {@inheritDoc IInterval.compareEnd}
393
- */
394
- public compareEnd(b: Interval) {
395
- return this.end - b.end;
396
- }
397
-
398
- /**
399
- * {@inheritDoc IInterval.overlaps}
400
- */
401
- public overlaps(b: Interval) {
402
- const result = this.start <= b.end && this.end >= b.start;
403
- return result;
404
- }
405
-
406
- /**
407
- * {@inheritDoc IInterval.union}
408
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
409
- */
410
- public union(b: Interval) {
411
- return new Interval(
412
- Math.min(this.start, b.start),
413
- Math.max(this.end, b.end),
414
- this.properties,
415
- );
416
- }
417
-
418
- public getProperties() {
419
- return this.properties;
420
- }
421
-
422
- /**
423
- * {@inheritDoc ISerializableInterval.addProperties}
424
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
425
- */
426
- public addProperties(
427
- newProps: PropertySet,
428
- collaborating: boolean = false,
429
- seq?: number,
430
- op?: ICombiningOp,
431
- ): PropertySet | undefined {
432
- if (newProps) {
433
- this.initializeProperties();
434
- return this.propertyManager.addProperties(
435
- this.properties,
436
- newProps,
437
- op,
438
- seq,
439
- collaborating,
440
- );
441
- }
442
- }
443
-
444
- /**
445
- * {@inheritDoc IInterval.modify}
446
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
447
- */
448
- public modify(label: string, start: number, end: number, op?: ISequencedDocumentMessage) {
449
- const startPos = start ?? this.start;
450
- const endPos = end ?? this.end;
451
- if (this.start === startPos && this.end === endPos) {
452
- // Return undefined to indicate that no change is necessary.
453
- return;
454
- }
455
- const newInterval = new Interval(startPos, endPos);
456
- if (this.properties) {
457
- newInterval.initializeProperties();
458
- this.propertyManager.copyTo(
459
- this.properties,
460
- newInterval.properties,
461
- newInterval.propertyManager,
462
- );
463
- }
464
- return newInterval;
465
- }
466
-
467
- private initializeProperties(): void {
468
- if (!this.propertyManager) {
469
- this.propertyManager = new PropertiesManager();
470
- }
471
- if (!this.properties) {
472
- this.properties = createMap<any>();
473
- }
474
- }
475
- }
476
-
477
- /**
478
- * Interval implementation whose ends are associated with positions in a mutatable sequence.
479
- * As such, when content is inserted into the middle of the interval, the interval expands to
480
- * include that content.
481
- *
482
- * @remarks - The endpoint's position should be treated exclusively to get reasonable behavior--i.e.
483
- * an interval referring to "hello" in "hello world" should have a start position of 0 and an end
484
- * position of 5.
485
- *
486
- * To see why, consider what happens if "llo wor" is removed from the string to make "held".
487
- * The interval's startpoint remains on the "h" (it isn't altered), but the interval's endpoint
488
- * slides forward to the next unremoved position, which is the "l" in "held".
489
- * Users would generally expect the interval to now refer to "he" (as it is the subset of content
490
- * remaining after the removal), hence the "l" should be excluded.
491
- * If the interval endpoint was treated inclusively, the interval would now refer to "hel", which
492
- * is undesirable.
493
- *
494
- * Since the end of an interval is treated exclusively but cannot be greater than or equal to the
495
- * length of the associated sequence, application models which leverage interval collections should
496
- * consider inserting a marker at the end of the sequence to represent the end of the content.
497
- */
498
- export class SequenceInterval implements ISerializableInterval {
499
- /**
500
- * {@inheritDoc ISerializableInterval.properties}
501
- */
502
- public properties: PropertySet;
503
- /**
504
- * {@inheritDoc ISerializableInterval.propertyManager}
505
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
506
- */
507
- public propertyManager: PropertiesManager;
508
-
509
- constructor(
510
- private readonly client: Client,
511
- /**
512
- * Start endpoint of this interval.
513
- * @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
514
- */
515
- public start: LocalReferencePosition,
516
- /**
517
- * End endpoint of this interval.
518
- * @remarks - This endpoint can be resolved into a character position using the SharedString it's a part of.
519
- */
520
- public end: LocalReferencePosition,
521
- public intervalType: IntervalType,
522
- props?: PropertySet,
523
- public readonly stickiness: IntervalStickiness = IntervalStickiness.END,
524
- ) {
525
- this.propertyManager = new PropertiesManager();
526
- this.properties = {};
527
-
528
- if (props) {
529
- this.addProperties(props);
530
- }
531
- }
532
-
533
- private callbacks?: Record<"beforePositionChange" | "afterPositionChange", () => void>;
534
-
535
- /**
536
- * Subscribes to position change events on this interval if there are no current listeners.
537
- * @internal
538
- */
539
- public addPositionChangeListeners(
540
- beforePositionChange: () => void,
541
- afterPositionChange: () => void,
542
- ): void {
543
- if (this.callbacks === undefined) {
544
- this.callbacks = {
545
- beforePositionChange,
546
- afterPositionChange,
547
- };
548
-
549
- const startCbs = (this.start.callbacks ??= {});
550
- const endCbs = (this.end.callbacks ??= {});
551
- startCbs.beforeSlide = endCbs.beforeSlide = beforePositionChange;
552
- startCbs.afterSlide = endCbs.afterSlide = afterPositionChange;
553
- }
554
- }
555
-
556
- /**
557
- * Removes the currently subscribed position change listeners.
558
- * @internal
559
- */
560
- public removePositionChangeListeners(): void {
561
- if (this.callbacks) {
562
- this.callbacks = undefined;
563
- this.start.callbacks = undefined;
564
- this.end.callbacks = undefined;
565
- }
566
- }
567
-
568
- /**
569
- * {@inheritDoc ISerializableInterval.serialize}
570
- * @internal
571
- */
572
- public serialize(): ISerializedInterval {
573
- const startPosition = this.client.localReferencePositionToPosition(this.start);
574
- const endPosition = this.client.localReferencePositionToPosition(this.end);
575
- const serializedInterval: ISerializedInterval = {
576
- end: endPosition,
577
- intervalType: this.intervalType,
578
- sequenceNumber: this.client.getCurrentSeq(),
579
- start: startPosition,
580
- };
581
-
582
- if (this.properties) {
583
- serializedInterval.properties = this.properties;
584
- }
585
- if (this.stickiness !== IntervalStickiness.END) {
586
- serializedInterval.stickiness = this.stickiness;
587
- }
588
-
589
- return serializedInterval;
590
- }
591
-
592
- /**
593
- * {@inheritDoc IInterval.clone}
594
- */
595
- public clone() {
596
- return new SequenceInterval(
597
- this.client,
598
- this.start,
599
- this.end,
600
- this.intervalType,
601
- this.properties,
602
- this.stickiness,
603
- );
604
- }
605
-
606
- /**
607
- * {@inheritDoc IInterval.compare}
608
- */
609
- public compare(b: SequenceInterval) {
610
- const startResult = this.compareStart(b);
611
- if (startResult === 0) {
612
- const endResult = this.compareEnd(b);
613
- if (endResult === 0) {
614
- const thisId = this.getIntervalId();
615
- if (thisId) {
616
- const bId = b.getIntervalId();
617
- if (bId) {
618
- return thisId > bId ? 1 : thisId < bId ? -1 : 0;
619
- }
620
- return 0;
621
- }
622
- return 0;
623
- } else {
624
- return endResult;
625
- }
626
- } else {
627
- return startResult;
628
- }
629
- }
630
-
631
- /**
632
- * {@inheritDoc IInterval.compareStart}
633
- */
634
- public compareStart(b: SequenceInterval) {
635
- return compareReferencePositions(this.start, b.start);
636
- }
637
-
638
- /**
639
- * {@inheritDoc IInterval.compareEnd}
640
- */
641
- public compareEnd(b: SequenceInterval) {
642
- return compareReferencePositions(this.end, b.end);
643
- }
644
-
645
- /**
646
- * {@inheritDoc IInterval.overlaps}
647
- */
648
- public overlaps(b: SequenceInterval) {
649
- const result =
650
- compareReferencePositions(this.start, b.end) <= 0 &&
651
- compareReferencePositions(this.end, b.start) >= 0;
652
- return result;
653
- }
654
-
655
- /**
656
- * {@inheritDoc ISerializableInterval.getIntervalId}
657
- */
658
- public getIntervalId(): string {
659
- const id = this.properties?.[reservedIntervalIdKey];
660
- assert(id !== undefined, 0x5e2 /* interval ID should not be undefined */);
661
- return `${id}`;
662
- }
663
-
664
- /**
665
- * {@inheritDoc IInterval.union}
666
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
667
- */
668
- public union(b: SequenceInterval) {
669
- return new SequenceInterval(
670
- this.client,
671
- minReferencePosition(this.start, b.start),
672
- maxReferencePosition(this.end, b.end),
673
- this.intervalType,
674
- );
675
- }
676
-
677
- /**
678
- * {@inheritDoc ISerializableInterval.addProperties}
679
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
680
- */
681
- public addProperties(
682
- newProps: PropertySet,
683
- collab: boolean = false,
684
- seq?: number,
685
- op?: ICombiningOp,
686
- ): PropertySet | undefined {
687
- this.initializeProperties();
688
- return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab);
689
- }
690
-
691
- /**
692
- * @returns whether this interval overlaps two numerical positions.
693
- */
694
- public overlapsPos(bstart: number, bend: number) {
695
- const startPos = this.client.localReferencePositionToPosition(this.start);
696
- const endPos = this.client.localReferencePositionToPosition(this.end);
697
- return endPos > bstart && startPos < bend;
698
- }
699
-
700
- /**
701
- * {@inheritDoc IInterval.modify}
702
- * @deprecated - This API was never intended to be public and will be marked internal in a future release.
703
- */
704
- public modify(
705
- label: string,
706
- start: number,
707
- end: number,
708
- op?: ISequencedDocumentMessage,
709
- localSeq?: number,
710
- stickiness: IntervalStickiness = IntervalStickiness.END,
711
- ) {
712
- const getRefType = (baseType: ReferenceType): ReferenceType => {
713
- let refType = baseType;
714
- if (op === undefined) {
715
- refType &= ~ReferenceType.SlideOnRemove;
716
- refType |= ReferenceType.StayOnRemove;
717
- }
718
- return refType;
719
- };
720
-
721
- let startRef = this.start;
722
- if (start !== undefined) {
723
- startRef = createPositionReference(
724
- this.client,
725
- start,
726
- getRefType(this.start.refType),
727
- op,
728
- undefined,
729
- localSeq,
730
- startReferenceSlidingPreference(stickiness),
731
- );
732
- if (this.start.properties) {
733
- startRef.addProperties(this.start.properties);
734
- }
735
- }
736
-
737
- let endRef = this.end;
738
- if (end !== undefined) {
739
- endRef = createPositionReference(
740
- this.client,
741
- end,
742
- getRefType(this.end.refType),
743
- op,
744
- undefined,
745
- localSeq,
746
- endReferenceSlidingPreference(stickiness),
747
- );
748
- if (this.end.properties) {
749
- endRef.addProperties(this.end.properties);
750
- }
751
- }
752
-
753
- const newInterval = new SequenceInterval(this.client, startRef, endRef, this.intervalType);
754
- if (this.properties) {
755
- newInterval.initializeProperties();
756
- this.propertyManager.copyTo(
757
- this.properties,
758
- newInterval.properties,
759
- newInterval.propertyManager,
760
- );
761
- }
762
- return newInterval;
763
- }
764
-
765
- private initializeProperties(): void {
766
- if (!this.propertyManager) {
767
- this.propertyManager = new PropertiesManager();
768
- }
769
- if (!this.properties) {
770
- this.properties = createMap<any>();
771
- }
772
- }
773
- }
774
-
775
- function createPositionReferenceFromSegoff(
776
- client: Client,
777
- segoff: { segment: ISegment | undefined; offset: number | undefined },
778
- refType: ReferenceType,
779
- op?: ISequencedDocumentMessage,
780
- localSeq?: number,
781
- fromSnapshot?: boolean,
782
- slidingPreference?: SlidingPreference,
783
- ): LocalReferencePosition {
784
- if (segoff.segment) {
785
- const ref = client.createLocalReferencePosition(
786
- segoff.segment,
787
- segoff.offset,
788
- refType,
789
- undefined,
790
- slidingPreference,
791
- );
792
- return ref;
793
- }
794
-
795
- // Creating references on detached segments is allowed for:
796
- // - Transient segments
797
- // - References coming from a remote client (location may have been concurrently removed)
798
- // - References being rebased to a new sequence number
799
- // (segment they originally referred to may have been removed with no suitable replacement)
800
- if (
801
- !op &&
802
- !localSeq &&
803
- !fromSnapshot &&
804
- !refTypeIncludesFlag(refType, ReferenceType.Transient)
805
- ) {
806
- throw new UsageError("Non-transient references need segment");
807
- }
808
-
809
- return createDetachedLocalReferencePosition(refType);
810
- }
811
-
812
- function createPositionReference(
813
- client: Client,
814
- pos: number,
815
- refType: ReferenceType,
816
- op?: ISequencedDocumentMessage,
817
- fromSnapshot?: boolean,
818
- localSeq?: number,
819
- slidingPreference?: SlidingPreference,
820
- ): LocalReferencePosition {
821
- let segoff;
822
- if (op) {
823
- assert(
824
- (refType & ReferenceType.SlideOnRemove) !== 0,
825
- 0x2f5 /* op create references must be SlideOnRemove */,
826
- );
827
- segoff = client.getContainingSegment(pos, {
828
- referenceSequenceNumber: op.referenceSequenceNumber,
829
- clientId: op.clientId,
830
- });
831
- segoff = client.getSlideToSegment(segoff);
832
- } else {
833
- assert(
834
- (refType & ReferenceType.SlideOnRemove) === 0 || !!fromSnapshot,
835
- 0x2f6 /* SlideOnRemove references must be op created */,
836
- );
837
- segoff = client.getContainingSegment(pos, undefined, localSeq);
838
- }
839
-
840
- return createPositionReferenceFromSegoff(
841
- client,
842
- segoff,
843
- refType,
844
- op,
845
- localSeq,
846
- fromSnapshot,
847
- slidingPreference,
848
- );
849
- }
850
-
851
- export function createSequenceInterval(
852
- label: string,
853
- start: number,
854
- end: number,
855
- client: Client,
856
- intervalType: IntervalType,
857
- op?: ISequencedDocumentMessage,
858
- fromSnapshot?: boolean,
859
- stickiness: IntervalStickiness = IntervalStickiness.END,
860
- ): SequenceInterval {
861
- let beginRefType = ReferenceType.RangeBegin;
862
- let endRefType = ReferenceType.RangeEnd;
863
- if (intervalType === IntervalType.Transient) {
864
- beginRefType = ReferenceType.Transient;
865
- endRefType = ReferenceType.Transient;
866
- } else {
867
- if (intervalType === IntervalType.Nest) {
868
- beginRefType = ReferenceType.NestBegin;
869
- endRefType = ReferenceType.NestEnd;
870
- }
871
- // All non-transient interval references must eventually be SlideOnRemove
872
- // To ensure eventual consistency, they must start as StayOnRemove when
873
- // pending (created locally and creation op is not acked)
874
- if (op || fromSnapshot) {
875
- beginRefType |= ReferenceType.SlideOnRemove;
876
- endRefType |= ReferenceType.SlideOnRemove;
877
- } else {
878
- beginRefType |= ReferenceType.StayOnRemove;
879
- endRefType |= ReferenceType.StayOnRemove;
880
- }
881
- }
882
-
883
- const startLref = createPositionReference(
884
- client,
885
- start,
886
- beginRefType,
887
- op,
888
- fromSnapshot,
889
- undefined,
890
- startReferenceSlidingPreference(stickiness),
891
- );
892
- const endLref = createPositionReference(
893
- client,
894
- end,
895
- endRefType,
896
- op,
897
- fromSnapshot,
898
- undefined,
899
- endReferenceSlidingPreference(stickiness),
900
- );
901
- const rangeProp = {
902
- [reservedRangeLabelsKey]: [label],
903
- };
904
- startLref.addProperties(rangeProp);
905
- endLref.addProperties(rangeProp);
906
-
907
- const ival = new SequenceInterval(
908
- client,
909
- startLref,
910
- endLref,
911
- intervalType,
912
- rangeProp,
913
- stickiness,
914
- );
915
- return ival;
916
- }
917
-
918
- export function createIntervalIndex() {
919
- const helpers: IIntervalHelpers<Interval> = {
920
- compareEnds: compareIntervalEnds,
921
- create: createInterval,
922
- };
923
- const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
924
- return lc;
925
- }
926
-
927
- /**
928
- * Collection of intervals.
929
- *
930
- * Implementers of this interface will typically implement additional APIs to support efficiently querying a collection
931
- * of intervals in some manner, for example:
932
- * - "find all intervals with start endpoint between these two points"
933
- * - "find all intervals which overlap this range"
934
- * etc.
935
- */
936
- export interface IntervalIndex<TInterval extends ISerializableInterval> {
937
- /**
938
- * Adds an interval to the index.
939
- * @remarks - Application code should never need to invoke this method on their index for production scenarios:
940
- * Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
941
- */
942
- add(interval: TInterval): void;
943
-
944
- /**
945
- * Removes an interval from the index.
946
- * @remarks - Application code should never need to invoke this method on their index for production scenarios:
947
- * Fluid handles adding and removing intervals from an index in response to sequence or interval changes.
948
- */
949
- remove(interval: TInterval): void;
950
- }
951
-
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
- class IdIntervalIndex<TInterval extends ISerializableInterval>
1082
- implements IntervalIndex<TInterval>, Iterable<TInterval>
1083
- {
1084
- private readonly intervalIdMap: Map<string, TInterval> = new Map();
1085
-
1086
- public add(interval: TInterval) {
1087
- const id = interval.getIntervalId();
1088
- assert(
1089
- id !== undefined,
1090
- 0x2c0 /* "ID must be created before adding interval to collection" */,
1091
- );
1092
- // Make the ID immutable.
1093
- Object.defineProperty(interval.properties, reservedIntervalIdKey, {
1094
- configurable: false,
1095
- enumerable: true,
1096
- writable: false,
1097
- });
1098
- this.intervalIdMap.set(id, interval);
1099
- }
1100
-
1101
- public remove(interval: TInterval) {
1102
- const id = interval.getIntervalId();
1103
- assert(id !== undefined, 0x311 /* expected id to exist on interval */);
1104
- this.intervalIdMap.delete(id);
1105
- }
1106
-
1107
- public getIntervalById(id: string) {
1108
- return this.intervalIdMap.get(id);
1109
- }
1110
-
1111
- public [Symbol.iterator]() {
1112
- return this.intervalIdMap.values();
1113
- }
1114
- }
1115
-
1116
- class EndpointIndex<TInterval extends ISerializableInterval> implements IntervalIndex<TInterval> {
1117
- private readonly endIntervalTree: RedBlackTree<TInterval, TInterval>;
1118
-
1119
- constructor(
1120
- private readonly client: Client,
1121
- private readonly helpers: IIntervalHelpers<TInterval>,
1122
- ) {
1123
- // eslint-disable-next-line @typescript-eslint/unbound-method
1124
- this.endIntervalTree = new RedBlackTree<TInterval, TInterval>(helpers.compareEnds);
1125
- }
1126
-
1127
- public previousInterval(pos: number) {
1128
- const transientInterval = this.helpers.create(
1129
- "transient",
1130
- pos,
1131
- pos,
1132
- this.client,
1133
- IntervalType.Transient,
1134
- );
1135
- const rbNode = this.endIntervalTree.floor(transientInterval);
1136
- if (rbNode) {
1137
- return rbNode.data;
1138
- }
1139
- }
1140
-
1141
- public nextInterval(pos: number) {
1142
- const transientInterval = this.helpers.create(
1143
- "transient",
1144
- pos,
1145
- pos,
1146
- this.client,
1147
- IntervalType.Transient,
1148
- );
1149
- const rbNode = this.endIntervalTree.ceil(transientInterval);
1150
- if (rbNode) {
1151
- return rbNode.data;
1152
- }
1153
- }
1154
-
1155
- public add(interval: TInterval): void {
1156
- this.endIntervalTree.put(interval, interval);
1157
- }
1158
-
1159
- public remove(interval: TInterval): void {
1160
- this.endIntervalTree.remove(interval);
1161
- }
1162
- }
38
+ import {
39
+ CompressedSerializedInterval,
40
+ IIntervalHelpers,
41
+ Interval,
42
+ IntervalOpType,
43
+ IntervalStickiness,
44
+ IntervalType,
45
+ ISerializableInterval,
46
+ ISerializedInterval,
47
+ SequenceInterval,
48
+ SerializedIntervalDelta,
49
+ createPositionReferenceFromSegoff,
50
+ endReferenceSlidingPreference,
51
+ startReferenceSlidingPreference,
52
+ sequenceIntervalHelpers,
53
+ createInterval,
54
+ } from "./intervals";
55
+ import {
56
+ IEndpointIndex,
57
+ IIdIntervalIndex,
58
+ IOverlappingIntervalsIndex,
59
+ IntervalIndex,
60
+ createEndpointIndex,
61
+ createIdIntervalIndex,
62
+ createOverlappingIntervalsIndex,
63
+ } from "./intervalIndex";
1163
64
 
1164
- /**
1165
- * Collection of intervals.
1166
- *
1167
- * Provide additional APIs to support efficiently querying a collection of intervals whose endpoints fall within a specified range.
1168
- */
1169
- export interface IEndpointInRangeIndex<TInterval extends ISerializableInterval>
1170
- extends IntervalIndex<TInterval> {
1171
- /**
1172
- * @returns an array of all intervals contained in this collection whose endpoints locate in the range [start, end] (includes both ends)
1173
- */
1174
- findIntervalsWithEndpointInRange(start: number, end: number);
1175
- }
65
+ const reservedIntervalIdKey = "intervalId";
1176
66
 
1177
- /**
1178
- * Collection of intervals.
1179
- *
1180
- * Provide additional APIs to support efficiently querying a collection of intervals whose startpoints fall within a specified range.
1181
- */
1182
- export interface IStartpointInRangeIndex<TInterval extends ISerializableInterval>
1183
- extends IntervalIndex<TInterval> {
1184
- /**
1185
- * @returns an array of all intervals contained in this collection whose startpoints locate in the range [start, end] (includes both ends)
1186
- */
1187
- findIntervalsWithStartpointInRange(start: number, end: number);
67
+ export interface ISerializedIntervalCollectionV2 {
68
+ label: string;
69
+ version: 2;
70
+ intervals: CompressedSerializedInterval[];
1188
71
  }
1189
72
 
1190
73
  /**
1191
- * Interface for intervals that have comparison override properties.
74
+ * Decompress an interval after loading a summary from JSON. The exact format
75
+ * of this compression is unspecified and subject to change
1192
76
  */
1193
- const forceCompare = Symbol();
1194
-
1195
- interface HasComparisonOverride {
1196
- [forceCompare]: number;
77
+ function decompressInterval(
78
+ interval: CompressedSerializedInterval,
79
+ label?: string,
80
+ ): ISerializedInterval {
81
+ return {
82
+ start: interval[0],
83
+ end: interval[1],
84
+ sequenceNumber: interval[2],
85
+ intervalType: interval[3],
86
+ properties: { ...interval[4], [reservedRangeLabelsKey]: [label] },
87
+ stickiness: interval[5],
88
+ };
1197
89
  }
1198
90
 
1199
91
  /**
1200
- * Compares two objects based on their comparison override properties.
1201
- * @returns A number indicating the order of the intervals (negative for a is lower than b, 0 for tie, positive for a is greater than b).
92
+ * Compress an interval prior to serialization as JSON. The exact format of this
93
+ * compression is unspecified and subject to change
1202
94
  */
1203
- function compareOverrideables(
1204
- a: Partial<HasComparisonOverride>,
1205
- b: Partial<HasComparisonOverride>,
1206
- ): number {
1207
- const forceCompareA = a[forceCompare] ?? 0;
1208
- const forceCompareB = b[forceCompare] ?? 0;
1209
-
1210
- return forceCompareA - forceCompareB;
1211
- }
1212
-
1213
- class EndpointInRangeIndex<TInterval extends ISerializableInterval>
1214
- implements IEndpointInRangeIndex<TInterval>
1215
- {
1216
- private readonly intervalTree;
1217
-
1218
- constructor(
1219
- private readonly helpers: IIntervalHelpers<TInterval>,
1220
- private readonly client: Client,
1221
- ) {
1222
- this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
1223
- const compareEndsResult = helpers.compareEnds(a, b);
1224
- if (compareEndsResult !== 0) {
1225
- return compareEndsResult;
1226
- }
1227
-
1228
- const overrideablesComparison = compareOverrideables(
1229
- a as Partial<HasComparisonOverride>,
1230
- b as Partial<HasComparisonOverride>,
1231
- );
1232
- if (overrideablesComparison !== 0) {
1233
- return overrideablesComparison;
1234
- }
1235
-
1236
- const aId = a.getIntervalId();
1237
- const bId = b.getIntervalId();
1238
- if (aId !== undefined && bId !== undefined) {
1239
- return aId.localeCompare(bId);
1240
- }
1241
- return 0;
1242
- });
1243
- }
1244
-
1245
- public add(interval: TInterval): void {
1246
- this.intervalTree.put(interval, interval);
1247
- }
1248
-
1249
- public remove(interval: TInterval): void {
1250
- this.intervalTree.remove(interval);
1251
- }
1252
-
1253
- public findIntervalsWithEndpointInRange(start: number, end: number) {
1254
- if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
1255
- return [];
1256
- }
1257
- const results: TInterval[] = [];
1258
- const action: PropertyAction<TInterval, TInterval> = (node) => {
1259
- results.push(node.data);
1260
- return true;
1261
- };
1262
-
1263
- const transientStartInterval = this.helpers.create(
1264
- "transient",
1265
- start,
1266
- start,
1267
- this.client,
1268
- IntervalType.Transient,
1269
- );
1270
-
1271
- const transientEndInterval = this.helpers.create(
1272
- "transient",
1273
- end,
1274
- end,
1275
- this.client,
1276
- IntervalType.Transient,
1277
- );
1278
-
1279
- // Add comparison overrides to the transient intervals
1280
- (transientStartInterval as Partial<HasComparisonOverride>)[forceCompare] = -1;
1281
- (transientEndInterval as Partial<HasComparisonOverride>)[forceCompare] = 1;
1282
-
1283
- this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
1284
- return results;
1285
- }
1286
- }
1287
-
1288
- class StartpointInRangeIndex<TInterval extends ISerializableInterval>
1289
- implements IStartpointInRangeIndex<TInterval>
1290
- {
1291
- private readonly intervalTree;
1292
-
1293
- constructor(
1294
- private readonly helpers: IIntervalHelpers<TInterval>,
1295
- private readonly client: Client,
1296
- ) {
1297
- this.intervalTree = new RedBlackTree<TInterval, TInterval>((a: TInterval, b: TInterval) => {
1298
- assert(
1299
- typeof helpers.compareStarts === "function",
1300
- 0x6d1 /* compareStarts does not exist in the helpers */,
1301
- );
1302
-
1303
- const compareStartsResult = helpers.compareStarts(a, b);
1304
- if (compareStartsResult !== 0) {
1305
- return compareStartsResult;
1306
- }
1307
-
1308
- const overrideablesComparison = compareOverrideables(
1309
- a as Partial<HasComparisonOverride>,
1310
- b as Partial<HasComparisonOverride>,
1311
- );
1312
- if (overrideablesComparison !== 0) {
1313
- return overrideablesComparison;
1314
- }
1315
- const aId = a.getIntervalId();
1316
- const bId = b.getIntervalId();
1317
- if (aId !== undefined && bId !== undefined) {
1318
- return aId.localeCompare(bId);
1319
- }
1320
- return 0;
1321
- });
1322
- }
1323
-
1324
- public add(interval: TInterval): void {
1325
- this.intervalTree.put(interval, interval);
1326
- }
1327
-
1328
- public remove(interval: TInterval): void {
1329
- this.intervalTree.remove(interval);
1330
- }
1331
-
1332
- public findIntervalsWithStartpointInRange(start: number, end: number) {
1333
- if (start <= 0 || start > end || this.intervalTree.isEmpty()) {
1334
- return [];
1335
- }
1336
- const results: TInterval[] = [];
1337
- const action: PropertyAction<TInterval, TInterval> = (node) => {
1338
- results.push(node.data);
1339
- return true;
1340
- };
1341
-
1342
- const transientStartInterval = this.helpers.create(
1343
- "transient",
1344
- start,
1345
- start,
1346
- this.client,
1347
- IntervalType.Transient,
1348
- );
1349
-
1350
- const transientEndInterval = this.helpers.create(
1351
- "transient",
1352
- end,
1353
- end,
1354
- this.client,
1355
- IntervalType.Transient,
1356
- );
95
+ function compressInterval(interval: ISerializedInterval): CompressedSerializedInterval {
96
+ const { start, end, sequenceNumber, intervalType, properties } = interval;
1357
97
 
1358
- // Add comparison overrides to the transient intervals
1359
- (transientStartInterval as Partial<HasComparisonOverride>)[forceCompare] = -1;
1360
- (transientEndInterval as Partial<HasComparisonOverride>)[forceCompare] = 1;
98
+ const base: CompressedSerializedInterval = [
99
+ start,
100
+ end,
101
+ sequenceNumber,
102
+ intervalType,
103
+ // remove the `referenceRangeLabels` property as it is already stored
104
+ // in the `label` field of the summary
105
+ { ...properties, [reservedRangeLabelsKey]: undefined },
106
+ ];
1361
107
 
1362
- this.intervalTree.mapRange(action, results, transientStartInterval, transientEndInterval);
1363
- return results;
108
+ if (interval.stickiness !== undefined && interval.stickiness !== IntervalStickiness.END) {
109
+ base.push(interval.stickiness);
1364
110
  }
1365
- }
1366
111
 
1367
- export function createEndpointInRangeIndex<TInterval extends ISerializableInterval>(
1368
- helpers: IIntervalHelpers<TInterval>,
1369
- client: Client,
1370
- ): IEndpointInRangeIndex<TInterval> {
1371
- return new EndpointInRangeIndex<TInterval>(helpers, client);
112
+ return base;
1372
113
  }
1373
114
 
1374
- export function createStartpointInRangeIndex<TInterval extends ISerializableInterval>(
1375
- helpers: IIntervalHelpers<TInterval>,
1376
- client: Client,
1377
- ): IStartpointInRangeIndex<TInterval> {
1378
- return new StartpointInRangeIndex<TInterval>(helpers, client);
115
+ export function createIntervalIndex() {
116
+ const helpers: IIntervalHelpers<Interval> = {
117
+ compareEnds: (a: Interval, b: Interval) => a.end - b.end,
118
+ create: createInterval,
119
+ };
120
+ const lc = new LocalIntervalCollection<Interval>(undefined as any as Client, "", helpers);
121
+ return lc;
1379
122
  }
1380
123
 
1381
124
  export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1382
125
  private static readonly legacyIdPrefix = "legacy";
1383
- public readonly overlappingIntervalsIndex: OverlappingIntervalsIndex<TInterval>;
1384
- public readonly idIntervalIndex: IdIntervalIndex<TInterval>;
1385
- public readonly endIntervalIndex: EndpointIndex<TInterval>;
126
+ public readonly overlappingIntervalsIndex: IOverlappingIntervalsIndex<TInterval>;
127
+ public readonly idIntervalIndex: IIdIntervalIndex<TInterval>;
128
+ public readonly endIntervalIndex: IEndpointIndex<TInterval>;
1386
129
  private readonly indexes: Set<IntervalIndex<TInterval>>;
1387
130
 
1388
131
  constructor(
@@ -1395,9 +138,9 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1395
138
  previousInterval: TInterval,
1396
139
  ) => void,
1397
140
  ) {
1398
- this.overlappingIntervalsIndex = new OverlappingIntervalsIndex(client, helpers);
1399
- this.idIntervalIndex = new IdIntervalIndex();
1400
- this.endIntervalIndex = new EndpointIndex(client, helpers);
141
+ this.overlappingIntervalsIndex = createOverlappingIntervalsIndex(client, helpers);
142
+ this.idIntervalIndex = createIdIntervalIndex<TInterval>();
143
+ this.endIntervalIndex = createEndpointIndex(client, helpers);
1401
144
  this.indexes = new Set([
1402
145
  this.overlappingIntervalsIndex,
1403
146
  this.idIntervalIndex,
@@ -1493,6 +236,17 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1493
236
  }
1494
237
 
1495
238
  if (props) {
239
+ // This check is intended to prevent scenarios where a random interval is created and then
240
+ // inserted into a collection. The aim is to ensure that the collection is created first
241
+ // then the user can create/add intervals based on the collection
242
+ if (
243
+ props[reservedRangeLabelsKey] !== undefined &&
244
+ props[reservedRangeLabelsKey][0] !== this.label
245
+ ) {
246
+ throw new LoggingError(
247
+ "Adding an interval that belongs to another interval collection is not permitted",
248
+ );
249
+ }
1496
250
  interval.addProperties(props);
1497
251
  }
1498
252
  interval.properties[reservedIntervalIdKey] ??= uuid();
@@ -1602,24 +356,6 @@ export class LocalIntervalCollection<TInterval extends ISerializableInterval> {
1602
356
  }
1603
357
  }
1604
358
 
1605
- export const compareSequenceIntervalEnds = (a: SequenceInterval, b: SequenceInterval): number =>
1606
- compareReferencePositions(a.end, b.end);
1607
-
1608
- export const compareSequenceIntervalStarts = (a: SequenceInterval, b: SequenceInterval): number =>
1609
- compareReferencePositions(a.start, b.start);
1610
-
1611
- export const sequenceIntervalHelpers: IIntervalHelpers<SequenceInterval> = {
1612
- compareEnds: compareSequenceIntervalEnds,
1613
- compareStarts: compareSequenceIntervalStarts,
1614
- create: createSequenceInterval,
1615
- };
1616
-
1617
- export const intervalHelpers: IIntervalHelpers<Interval> = {
1618
- compareEnds: (a: Interval, b: Interval) => a.end - b.end,
1619
- compareStarts: (a: Interval, b: Interval) => a.start - b.start,
1620
- create: createInterval,
1621
- };
1622
-
1623
359
  class SequenceIntervalCollectionFactory
1624
360
  implements IValueFactory<IntervalCollection<SequenceInterval>>
1625
361
  {
@@ -1667,26 +403,6 @@ export class SequenceIntervalCollectionValueType
1667
403
  private static readonly _ops = makeOpsMap<SequenceInterval>();
1668
404
  }
1669
405
 
1670
- const compareIntervalEnds = (a: Interval, b: Interval) => a.end - b.end;
1671
-
1672
- function createInterval(
1673
- label: string,
1674
- start: number,
1675
- end: number,
1676
- client: Client,
1677
- intervalType?: IntervalType,
1678
- op?: ISequencedDocumentMessage,
1679
- fromSnapshot?: boolean,
1680
- ): Interval {
1681
- const rangeProp: PropertySet = {};
1682
-
1683
- if (label && label.length > 0) {
1684
- rangeProp[reservedRangeLabelsKey] = [label];
1685
- }
1686
-
1687
- return new Interval(start, end, rangeProp);
1688
- }
1689
-
1690
406
  class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Interval>> {
1691
407
  public load(
1692
408
  emitter: IValueOpEmitter,
@@ -1694,7 +410,7 @@ class IntervalCollectionFactory implements IValueFactory<IntervalCollection<Inte
1694
410
  options?: Partial<SequenceOptions>,
1695
411
  ): IntervalCollection<Interval> {
1696
412
  const helpers: IIntervalHelpers<Interval> = {
1697
- compareEnds: compareIntervalEnds,
413
+ compareEnds: (a: Interval, b: Interval) => a.end - b.end,
1698
414
  create: createInterval,
1699
415
  };
1700
416
  const collection = new IntervalCollection<Interval>(helpers, false, emitter, raw, options);
@@ -1839,6 +555,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
1839
555
  * endpoints. These references should be used for position information only.
1840
556
  * `local` reflects whether the change originated locally.
1841
557
  * `op` is defined if and only if the server has acked this change.
558
+ * `slide` is true if the change is due to sliding on removal of position
1842
559
  */
1843
560
  (
1844
561
  event: "changeInterval",
@@ -1847,6 +564,7 @@ export interface IIntervalCollectionEvent<TInterval extends ISerializableInterva
1847
564
  previousInterval: TInterval,
1848
565
  local: boolean,
1849
566
  op: ISequencedDocumentMessage | undefined,
567
+ slide: boolean,
1850
568
  ) => void,
1851
569
  );
1852
570
  /**
@@ -1894,7 +612,7 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
1894
612
  * All intervals which are part of this collection will be added to the index, and the index will automatically
1895
613
  * be updated when this collection updates due to local or remote changes.
1896
614
  *
1897
- * @remarks - After attaching an index to an interval collection, applications should typically store this
615
+ * @remarks After attaching an index to an interval collection, applications should typically store this
1898
616
  * index somewhere in their in-memory data model for future reference and querying.
1899
617
  */
1900
618
  attachIndex(index: IntervalIndex<TInterval>): void;
@@ -1903,7 +621,7 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
1903
621
  * All intervals which are part of this collection will be removed from the index, and updates to this collection
1904
622
  * due to local or remote changes will no longer incur updates to the index.
1905
623
  *
1906
- * @returns - Return false if the target index cannot be found in the indexes, otherwise remove all intervals in the index and return true
624
+ * @returns `false` if the target index cannot be found in the indexes, otherwise remove all intervals in the index and return `true`.
1907
625
  */
1908
626
  detachIndex(index: IntervalIndex<TInterval>): boolean;
1909
627
  /**
@@ -1918,8 +636,8 @@ export interface IIntervalCollection<TInterval extends ISerializableInterval>
1918
636
  * @param intervalType - type of the interval. All intervals are SlideOnRemove. Intervals may not be Transient.
1919
637
  * @param props - properties of the interval
1920
638
  * @param stickiness - {@link (IntervalStickiness:type)} to apply to the added interval.
1921
- * @returns - the created interval
1922
- * @remarks - See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
639
+ * @returns The created interval
640
+ * @remarks See documentation on {@link SequenceInterval} for comments on interval endpoint semantics: there are subtleties
1923
641
  * with how the current half-open behavior is represented.
1924
642
  */
1925
643
  add(
@@ -2112,7 +830,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2112
830
  // if segment is undefined, it slid off the string
2113
831
  assert(segment !== undefined, 0x54e /* No segment found */);
2114
832
 
2115
- const segoff = this.client.getSlideToSegment({ segment, offset }) ?? segment;
833
+ const segoff = getSlideToSegoff({ segment, offset }) ?? segment;
2116
834
 
2117
835
  // case happens when rebasing op, but concurrently entire string has been deleted
2118
836
  if (segoff.segment === undefined || segoff.offset === undefined) {
@@ -2176,7 +894,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2176
894
  client,
2177
895
  label,
2178
896
  this.helpers,
2179
- (interval, previousInterval) => this.emitChange(interval, previousInterval, true),
897
+ (interval, previousInterval) => this.emitChange(interval, previousInterval, true, true),
2180
898
  );
2181
899
  if (this.savedSerializedIntervals) {
2182
900
  for (const serializedInterval of this.savedSerializedIntervals) {
@@ -2216,6 +934,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2216
934
  interval: TInterval,
2217
935
  previousInterval: TInterval,
2218
936
  local: boolean,
937
+ slide: boolean,
2219
938
  op?: ISequencedDocumentMessage,
2220
939
  ): void {
2221
940
  // Temporarily make references transient so that positional queries work (non-transient refs
@@ -2228,18 +947,18 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2228
947
  endRefType = previousInterval.end.refType;
2229
948
  previousInterval.start.refType = ReferenceType.Transient;
2230
949
  previousInterval.end.refType = ReferenceType.Transient;
2231
- this.emit("changeInterval", interval, previousInterval, local, op);
950
+ this.emit("changeInterval", interval, previousInterval, local, op, slide);
2232
951
  previousInterval.start.refType = startRefType;
2233
952
  previousInterval.end.refType = endRefType;
2234
953
  } else {
2235
- this.emit("changeInterval", interval, previousInterval, local, op);
954
+ this.emit("changeInterval", interval, previousInterval, local, op, slide);
2236
955
  }
2237
956
  }
2238
957
 
2239
958
  /**
2240
959
  * {@inheritdoc IIntervalCollection.getIntervalById}
2241
960
  */
2242
- public getIntervalById(id: string) {
961
+ public getIntervalById(id: string): TInterval | undefined {
2243
962
  if (!this.localCollection) {
2244
963
  throw new LoggingError("attach must be called before accessing intervals");
2245
964
  }
@@ -2327,7 +1046,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2327
1046
  /**
2328
1047
  * {@inheritdoc IIntervalCollection.removeIntervalById}
2329
1048
  */
2330
- public removeIntervalById(id: string) {
1049
+ public removeIntervalById(id: string): TInterval | undefined {
2331
1050
  if (!this.localCollection) {
2332
1051
  throw new LoggingError("Attach must be called before accessing intervals");
2333
1052
  }
@@ -2351,6 +1070,13 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2351
1070
  if (!props) {
2352
1071
  throw new LoggingError("changeProperties should be called with a property set");
2353
1072
  }
1073
+ // prevent the overwriting of an interval label, it should remain unchanged
1074
+ // once it has been inserted into the collection.
1075
+ if (props[reservedRangeLabelsKey] !== undefined) {
1076
+ throw new LoggingError(
1077
+ "The label property should not be modified once inserted to the collection",
1078
+ );
1079
+ }
2354
1080
 
2355
1081
  const interval = this.getIntervalById(id);
2356
1082
  if (interval) {
@@ -2402,7 +1128,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2402
1128
  this.localSeqToSerializedInterval.set(localSeq, serializedInterval);
2403
1129
  this.emitter.emit("change", undefined, serializedInterval, { localSeq });
2404
1130
  this.addPendingChange(id, serializedInterval);
2405
- this.emitChange(newInterval, interval, true);
1131
+ this.emitChange(newInterval, interval, true, false);
2406
1132
  return newInterval;
2407
1133
  }
2408
1134
  // No interval to change
@@ -2538,7 +1264,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2538
1264
  }
2539
1265
 
2540
1266
  if (newInterval !== interval) {
2541
- this.emitChange(newInterval, interval, local, op);
1267
+ this.emitChange(newInterval, interval, local, false, op);
2542
1268
  }
2543
1269
 
2544
1270
  const changedProperties = Object.keys(newProps).length > 0;
@@ -2548,19 +1274,6 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2548
1274
  }
2549
1275
  }
2550
1276
 
2551
- /**
2552
- * @deprecated - This functionality was useful when adding two intervals at the same start/end positions resulted
2553
- * in a conflict. This is no longer the case (as of PR#6407), as interval collections support multiple intervals
2554
- * at the same location and gives each interval a unique id.
2555
- *
2556
- * As such, the conflict resolver is never invoked and unnecessary. This API will be removed in an upcoming release.
2557
- */
2558
- public addConflictResolver(_: IntervalConflictResolver<TInterval>): void {
2559
- if (!this.localCollection) {
2560
- throw new LoggingError("attachSequence must be called");
2561
- }
2562
- }
2563
-
2564
1277
  /**
2565
1278
  * {@inheritdoc IIntervalCollection.attachDeserializer}
2566
1279
  */
@@ -2654,7 +1367,9 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2654
1367
  return rebased;
2655
1368
  }
2656
1369
 
2657
- private getSlideToSegment(lref: LocalReferencePosition) {
1370
+ private getSlideToSegment(
1371
+ lref: LocalReferencePosition,
1372
+ ): { segment: ISegment | undefined; offset: number | undefined } | undefined {
2658
1373
  if (!this.client) {
2659
1374
  throw new LoggingError("client does not exist");
2660
1375
  }
@@ -2662,7 +1377,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2662
1377
  if (segoff.segment?.localRefs?.has(lref) !== true) {
2663
1378
  return undefined;
2664
1379
  }
2665
- const newSegoff = this.client.getSlideToSegment(segoff);
1380
+ const newSegoff = getSlideToSegoff(segoff);
2666
1381
  const value: { segment: ISegment | undefined; offset: number | undefined } | undefined =
2667
1382
  segoff.segment === newSegoff.segment && segoff.offset === newSegoff.offset
2668
1383
  ? undefined
@@ -2761,7 +1476,7 @@ export class IntervalCollection<TInterval extends ISerializableInterval>
2761
1476
  oldSeg?.localRefs?.addLocalRef(oldInterval.end, oldInterval.end.getOffset());
2762
1477
  }
2763
1478
  this.localCollection.add(interval);
2764
- this.emitChange(interval, oldInterval as TInterval, true, op);
1479
+ this.emitChange(interval, oldInterval as TInterval, true, true, op);
2765
1480
  }
2766
1481
  }
2767
1482