@fluidframework/merge-tree 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/MergeTreeTextHelper.d.ts.map +1 -1
  3. package/dist/MergeTreeTextHelper.js +0 -2
  4. package/dist/MergeTreeTextHelper.js.map +1 -1
  5. package/dist/attributionPolicy.d.ts.map +1 -1
  6. package/dist/attributionPolicy.js +6 -16
  7. package/dist/attributionPolicy.js.map +1 -1
  8. package/dist/client.d.ts +3 -4
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +39 -28
  11. package/dist/client.js.map +1 -1
  12. package/dist/endOfTreeSegment.d.ts +2 -1
  13. package/dist/endOfTreeSegment.d.ts.map +1 -1
  14. package/dist/endOfTreeSegment.js.map +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/mergeTree.d.ts +8 -7
  20. package/dist/mergeTree.d.ts.map +1 -1
  21. package/dist/mergeTree.js +190 -216
  22. package/dist/mergeTree.js.map +1 -1
  23. package/dist/mergeTreeNodeWalk.d.ts.map +1 -1
  24. package/dist/mergeTreeNodeWalk.js +3 -2
  25. package/dist/mergeTreeNodeWalk.js.map +1 -1
  26. package/dist/mergeTreeNodes.d.ts +76 -162
  27. package/dist/mergeTreeNodes.d.ts.map +1 -1
  28. package/dist/mergeTreeNodes.js +100 -112
  29. package/dist/mergeTreeNodes.js.map +1 -1
  30. package/dist/mergeTreeTracking.d.ts.map +1 -1
  31. package/dist/mergeTreeTracking.js +0 -2
  32. package/dist/mergeTreeTracking.js.map +1 -1
  33. package/dist/partialLengths.d.ts +2 -2
  34. package/dist/partialLengths.d.ts.map +1 -1
  35. package/dist/partialLengths.js +28 -26
  36. package/dist/partialLengths.js.map +1 -1
  37. package/dist/perspective.d.ts +3 -2
  38. package/dist/perspective.d.ts.map +1 -1
  39. package/dist/perspective.js +11 -4
  40. package/dist/perspective.js.map +1 -1
  41. package/dist/referencePositions.d.ts.map +1 -1
  42. package/dist/referencePositions.js +4 -1
  43. package/dist/referencePositions.js.map +1 -1
  44. package/dist/revertibles.d.ts.map +1 -1
  45. package/dist/revertibles.js +10 -11
  46. package/dist/revertibles.js.map +1 -1
  47. package/dist/segmentGroupCollection.d.ts +4 -4
  48. package/dist/segmentGroupCollection.d.ts.map +1 -1
  49. package/dist/segmentGroupCollection.js +0 -6
  50. package/dist/segmentGroupCollection.js.map +1 -1
  51. package/dist/segmentInfos.d.ts +257 -0
  52. package/dist/segmentInfos.d.ts.map +1 -0
  53. package/dist/segmentInfos.js +166 -0
  54. package/dist/segmentInfos.js.map +1 -0
  55. package/dist/snapshotLoader.d.ts.map +1 -1
  56. package/dist/snapshotLoader.js +38 -44
  57. package/dist/snapshotLoader.js.map +1 -1
  58. package/dist/snapshotV1.d.ts.map +1 -1
  59. package/dist/snapshotV1.js +9 -12
  60. package/dist/snapshotV1.js.map +1 -1
  61. package/dist/snapshotlegacy.d.ts +2 -2
  62. package/dist/snapshotlegacy.d.ts.map +1 -1
  63. package/dist/snapshotlegacy.js +5 -3
  64. package/dist/snapshotlegacy.js.map +1 -1
  65. package/dist/sortedSegmentSet.d.ts.map +1 -1
  66. package/dist/sortedSegmentSet.js +5 -8
  67. package/dist/sortedSegmentSet.js.map +1 -1
  68. package/dist/test/beastTest.spec.d.ts +0 -2
  69. package/dist/test/beastTest.spec.d.ts.map +1 -1
  70. package/dist/test/beastTest.spec.js +1 -5
  71. package/dist/test/beastTest.spec.js.map +1 -1
  72. package/dist/test/client.annotateMarker.spec.js.map +1 -1
  73. package/dist/test/client.applyMsg.spec.js +15 -12
  74. package/dist/test/client.applyMsg.spec.js.map +1 -1
  75. package/dist/test/client.attributionFarm.spec.js.map +1 -1
  76. package/dist/test/client.getPosition.spec.js +3 -2
  77. package/dist/test/client.getPosition.spec.js.map +1 -1
  78. package/dist/test/client.localReference.spec.js +6 -6
  79. package/dist/test/client.localReference.spec.js.map +1 -1
  80. package/dist/test/client.localReferenceFarm.spec.js.map +1 -1
  81. package/dist/test/client.rollback.spec.js.map +1 -1
  82. package/dist/test/dirname.cjs +0 -1
  83. package/dist/test/dirname.cjs.map +1 -1
  84. package/dist/test/index.d.ts +1 -1
  85. package/dist/test/index.d.ts.map +1 -1
  86. package/dist/test/index.js +2 -4
  87. package/dist/test/index.js.map +1 -1
  88. package/dist/test/mergeTree.annotate.spec.js +3 -0
  89. package/dist/test/mergeTree.annotate.spec.js.map +1 -1
  90. package/dist/test/mergeTree.insertingWalk.spec.js +1 -1
  91. package/dist/test/mergeTree.insertingWalk.spec.js.map +1 -1
  92. package/dist/test/mergeTree.markRangeRemoved.spec.js +2 -0
  93. package/dist/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
  94. package/dist/test/mergeTree.walk.spec.js.map +1 -1
  95. package/dist/test/mergeTreeOperationRunner.d.ts.map +1 -1
  96. package/dist/test/mergeTreeOperationRunner.js +2 -3
  97. package/dist/test/mergeTreeOperationRunner.js.map +1 -1
  98. package/dist/test/obliterate.spec.js.map +1 -1
  99. package/dist/test/propertyManager.spec.js.map +1 -1
  100. package/dist/test/reconnectHelper.d.ts +2 -1
  101. package/dist/test/reconnectHelper.d.ts.map +1 -1
  102. package/dist/test/reconnectHelper.js.map +1 -1
  103. package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  104. package/dist/test/revertibleFarm.spec.js.map +1 -1
  105. package/dist/test/segmentGroupCollection.spec.js +15 -3
  106. package/dist/test/segmentGroupCollection.spec.js.map +1 -1
  107. package/dist/test/snapshot.utils.d.ts +2 -2
  108. package/dist/test/snapshot.utils.d.ts.map +1 -1
  109. package/dist/test/snapshot.utils.js.map +1 -1
  110. package/dist/test/sortedSegmentSet.spec.js +4 -3
  111. package/dist/test/sortedSegmentSet.spec.js.map +1 -1
  112. package/dist/test/testClient.d.ts +8 -6
  113. package/dist/test/testClient.d.ts.map +1 -1
  114. package/dist/test/testClient.js +29 -27
  115. package/dist/test/testClient.js.map +1 -1
  116. package/dist/test/testClientLogger.d.ts.map +1 -1
  117. package/dist/test/testClientLogger.js +6 -4
  118. package/dist/test/testClientLogger.js.map +1 -1
  119. package/dist/test/testUtils.d.ts +2 -2
  120. package/dist/test/testUtils.d.ts.map +1 -1
  121. package/dist/test/testUtils.js +32 -8
  122. package/dist/test/testUtils.js.map +1 -1
  123. package/dist/test/text.d.ts +2 -2
  124. package/dist/test/text.d.ts.map +1 -1
  125. package/dist/test/text.js +12 -6
  126. package/dist/test/text.js.map +1 -1
  127. package/dist/test/tracking.spec.js.map +1 -1
  128. package/dist/test/wordUnitTests.spec.js +1 -1
  129. package/dist/test/wordUnitTests.spec.js.map +1 -1
  130. package/dist/zamboni.d.ts.map +1 -1
  131. package/dist/zamboni.js +8 -7
  132. package/dist/zamboni.js.map +1 -1
  133. package/lib/MergeTreeTextHelper.d.ts.map +1 -1
  134. package/lib/MergeTreeTextHelper.js +0 -2
  135. package/lib/MergeTreeTextHelper.js.map +1 -1
  136. package/lib/attributionPolicy.d.ts.map +1 -1
  137. package/lib/attributionPolicy.js +6 -16
  138. package/lib/attributionPolicy.js.map +1 -1
  139. package/lib/client.d.ts +3 -4
  140. package/lib/client.d.ts.map +1 -1
  141. package/lib/client.js +40 -29
  142. package/lib/client.js.map +1 -1
  143. package/lib/endOfTreeSegment.d.ts +2 -1
  144. package/lib/endOfTreeSegment.d.ts.map +1 -1
  145. package/lib/endOfTreeSegment.js.map +1 -1
  146. package/lib/index.d.ts +2 -1
  147. package/lib/index.d.ts.map +1 -1
  148. package/lib/index.js +1 -1
  149. package/lib/index.js.map +1 -1
  150. package/lib/mergeTree.d.ts +8 -7
  151. package/lib/mergeTree.d.ts.map +1 -1
  152. package/lib/mergeTree.js +177 -205
  153. package/lib/mergeTree.js.map +1 -1
  154. package/lib/mergeTreeNodeWalk.d.ts.map +1 -1
  155. package/lib/mergeTreeNodeWalk.js +3 -2
  156. package/lib/mergeTreeNodeWalk.js.map +1 -1
  157. package/lib/mergeTreeNodes.d.ts +76 -162
  158. package/lib/mergeTreeNodes.d.ts.map +1 -1
  159. package/lib/mergeTreeNodes.js +95 -108
  160. package/lib/mergeTreeNodes.js.map +1 -1
  161. package/lib/mergeTreeTracking.d.ts.map +1 -1
  162. package/lib/mergeTreeTracking.js +0 -2
  163. package/lib/mergeTreeTracking.js.map +1 -1
  164. package/lib/partialLengths.d.ts +2 -2
  165. package/lib/partialLengths.d.ts.map +1 -1
  166. package/lib/partialLengths.js +25 -23
  167. package/lib/partialLengths.js.map +1 -1
  168. package/lib/perspective.d.ts +3 -2
  169. package/lib/perspective.d.ts.map +1 -1
  170. package/lib/perspective.js +11 -4
  171. package/lib/perspective.js.map +1 -1
  172. package/lib/referencePositions.d.ts.map +1 -1
  173. package/lib/referencePositions.js +4 -1
  174. package/lib/referencePositions.js.map +1 -1
  175. package/lib/revertibles.d.ts.map +1 -1
  176. package/lib/revertibles.js +8 -9
  177. package/lib/revertibles.js.map +1 -1
  178. package/lib/segmentGroupCollection.d.ts +4 -4
  179. package/lib/segmentGroupCollection.d.ts.map +1 -1
  180. package/lib/segmentGroupCollection.js +0 -6
  181. package/lib/segmentGroupCollection.js.map +1 -1
  182. package/lib/segmentInfos.d.ts +257 -0
  183. package/lib/segmentInfos.d.ts.map +1 -0
  184. package/lib/segmentInfos.js +145 -0
  185. package/lib/segmentInfos.js.map +1 -0
  186. package/lib/snapshotLoader.d.ts.map +1 -1
  187. package/lib/snapshotLoader.js +38 -44
  188. package/lib/snapshotLoader.js.map +1 -1
  189. package/lib/snapshotV1.d.ts.map +1 -1
  190. package/lib/snapshotV1.js +9 -12
  191. package/lib/snapshotV1.js.map +1 -1
  192. package/lib/snapshotlegacy.d.ts +2 -2
  193. package/lib/snapshotlegacy.d.ts.map +1 -1
  194. package/lib/snapshotlegacy.js +5 -3
  195. package/lib/snapshotlegacy.js.map +1 -1
  196. package/lib/sortedSegmentSet.d.ts.map +1 -1
  197. package/lib/sortedSegmentSet.js +5 -8
  198. package/lib/sortedSegmentSet.js.map +1 -1
  199. package/lib/test/beastTest.spec.d.ts +0 -2
  200. package/lib/test/beastTest.spec.d.ts.map +1 -1
  201. package/lib/test/beastTest.spec.js +0 -3
  202. package/lib/test/beastTest.spec.js.map +1 -1
  203. package/lib/test/client.annotateMarker.spec.js.map +1 -1
  204. package/lib/test/client.applyMsg.spec.js +15 -12
  205. package/lib/test/client.applyMsg.spec.js.map +1 -1
  206. package/lib/test/client.attributionFarm.spec.js.map +1 -1
  207. package/lib/test/client.getPosition.spec.js +3 -2
  208. package/lib/test/client.getPosition.spec.js.map +1 -1
  209. package/lib/test/client.localReference.spec.js +1 -1
  210. package/lib/test/client.localReference.spec.js.map +1 -1
  211. package/lib/test/client.localReferenceFarm.spec.js.map +1 -1
  212. package/lib/test/client.rollback.spec.js +1 -1
  213. package/lib/test/client.rollback.spec.js.map +1 -1
  214. package/lib/test/dirname.cjs +0 -1
  215. package/lib/test/dirname.cjs.map +1 -1
  216. package/lib/test/index.d.ts +1 -1
  217. package/lib/test/index.d.ts.map +1 -1
  218. package/lib/test/index.js +1 -1
  219. package/lib/test/index.js.map +1 -1
  220. package/lib/test/mergeTree.annotate.spec.js +3 -0
  221. package/lib/test/mergeTree.annotate.spec.js.map +1 -1
  222. package/lib/test/mergeTree.insertingWalk.spec.js +2 -2
  223. package/lib/test/mergeTree.insertingWalk.spec.js.map +1 -1
  224. package/lib/test/mergeTree.markRangeRemoved.spec.js +2 -0
  225. package/lib/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
  226. package/lib/test/mergeTree.walk.spec.js.map +1 -1
  227. package/lib/test/mergeTreeOperationRunner.d.ts.map +1 -1
  228. package/lib/test/mergeTreeOperationRunner.js +1 -2
  229. package/lib/test/mergeTreeOperationRunner.js.map +1 -1
  230. package/lib/test/obliterate.spec.js.map +1 -1
  231. package/lib/test/propertyManager.spec.js.map +1 -1
  232. package/lib/test/reconnectHelper.d.ts +2 -1
  233. package/lib/test/reconnectHelper.d.ts.map +1 -1
  234. package/lib/test/reconnectHelper.js.map +1 -1
  235. package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
  236. package/lib/test/revertibleFarm.spec.js.map +1 -1
  237. package/lib/test/segmentGroupCollection.spec.js +15 -3
  238. package/lib/test/segmentGroupCollection.spec.js.map +1 -1
  239. package/lib/test/snapshot.utils.d.ts +2 -2
  240. package/lib/test/snapshot.utils.d.ts.map +1 -1
  241. package/lib/test/snapshot.utils.js.map +1 -1
  242. package/lib/test/sortedSegmentSet.spec.js +4 -3
  243. package/lib/test/sortedSegmentSet.spec.js.map +1 -1
  244. package/lib/test/testClient.d.ts +8 -6
  245. package/lib/test/testClient.d.ts.map +1 -1
  246. package/lib/test/testClient.js +30 -28
  247. package/lib/test/testClient.js.map +1 -1
  248. package/lib/test/testClientLogger.d.ts.map +1 -1
  249. package/lib/test/testClientLogger.js +5 -3
  250. package/lib/test/testClientLogger.js.map +1 -1
  251. package/lib/test/testUtils.d.ts +2 -2
  252. package/lib/test/testUtils.d.ts.map +1 -1
  253. package/lib/test/testUtils.js +9 -8
  254. package/lib/test/testUtils.js.map +1 -1
  255. package/lib/test/text.d.ts +2 -2
  256. package/lib/test/text.d.ts.map +1 -1
  257. package/lib/test/text.js +12 -6
  258. package/lib/test/text.js.map +1 -1
  259. package/lib/test/tracking.spec.js.map +1 -1
  260. package/lib/test/wordUnitTests.spec.js +1 -1
  261. package/lib/test/wordUnitTests.spec.js.map +1 -1
  262. package/lib/zamboni.d.ts.map +1 -1
  263. package/lib/zamboni.js +7 -6
  264. package/lib/zamboni.js.map +1 -1
  265. package/package.json +17 -17
  266. package/src/MergeTreeTextHelper.ts +2 -4
  267. package/src/attributionPolicy.ts +5 -13
  268. package/src/client.ts +55 -44
  269. package/src/endOfTreeSegment.ts +3 -3
  270. package/src/index.ts +4 -6
  271. package/src/mergeTree.ts +245 -282
  272. package/src/mergeTreeNodeWalk.ts +3 -2
  273. package/src/mergeTreeNodes.ts +190 -322
  274. package/src/mergeTreeTracking.ts +0 -3
  275. package/src/partialLengths.ts +42 -27
  276. package/src/perspective.ts +27 -4
  277. package/src/referencePositions.ts +4 -1
  278. package/src/revertibles.ts +19 -13
  279. package/src/segmentGroupCollection.ts +7 -18
  280. package/src/segmentInfos.ts +377 -0
  281. package/src/snapshotLoader.ts +60 -57
  282. package/src/snapshotV1.ts +14 -16
  283. package/src/snapshotlegacy.ts +12 -17
  284. package/src/sortedSegmentSet.ts +6 -8
  285. package/src/zamboni.ts +10 -12
@@ -0,0 +1,377 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert, isObject } from "@fluidframework/core-utils/internal";
7
+
8
+ import { ISegmentInternal, ISegmentPrivate, MergeBlock } from "./mergeTreeNodes.js";
9
+ import type { ReferencePosition } from "./referencePositions.js";
10
+
11
+ export interface StringToType {
12
+ "string": string;
13
+ "number": number;
14
+ "object": object;
15
+ "array": [];
16
+ "boolean": boolean;
17
+ }
18
+
19
+ export function propExists<P extends string>(
20
+ thing: unknown,
21
+ prop: P,
22
+ ): thing is Record<P, unknown> {
23
+ return isObject(thing) && prop in thing;
24
+ }
25
+
26
+ export function hasProp<P extends string, T extends keyof StringToType>(
27
+ thing: unknown,
28
+ prop: P,
29
+ type: T,
30
+ ): thing is Record<P, StringToType[typeof type]> {
31
+ return (
32
+ propExists(thing, prop) &&
33
+ (type === "array" ? Array.isArray(thing[prop]) : typeof thing[prop] === type)
34
+ );
35
+ }
36
+
37
+ export function propInstanceOf<P extends string, T>(
38
+ thing: unknown,
39
+ prop: P,
40
+ type: new (...args: any[]) => T,
41
+ ): thing is Record<P, T> {
42
+ return propExists(thing, prop) && thing[prop] instanceof type;
43
+ }
44
+
45
+ /**
46
+ * Contains insertion information associated to an {@link ISegment}.
47
+ */
48
+ export interface IInsertionInfo {
49
+ /**
50
+ * Short clientId for the client that inserted this segment.
51
+ */
52
+ clientId: number;
53
+ /**
54
+ * Local seq at which this segment was inserted.
55
+ * This is defined if and only if the insertion of the segment is pending ack, i.e. `seq` is UnassignedSequenceNumber.
56
+ * Once the segment is acked, this field is cleared.
57
+ *
58
+ * @privateRemarks
59
+ * See {@link CollaborationWindow.localSeq} for more information on the semantics of localSeq.
60
+ */
61
+ localSeq?: number;
62
+ /**
63
+ * Seq at which this segment was inserted.
64
+ * If undefined, it is assumed the segment was inserted prior to the collab window's minimum sequence number.
65
+ */
66
+ seq: number;
67
+ }
68
+
69
+ /**
70
+ * Converts a segment-like object to an insertion info object if possible.
71
+ *
72
+ * @param segmentLike - The segment-like object to convert.
73
+ * @returns The insertion info object if the conversion is possible, otherwise undefined.
74
+ */
75
+ export const toInsertionInfo = (segmentLike: unknown): IInsertionInfo | undefined =>
76
+ hasProp(segmentLike, "clientId", "number") && hasProp(segmentLike, "seq", "number")
77
+ ? segmentLike
78
+ : undefined;
79
+
80
+ /**
81
+ * A type-guard which determines if the segment has insertion info, and
82
+ * returns true if it does, along with applying strong typing.
83
+ *
84
+ * @param segmentLike - The segment-like object to check.
85
+ * @returns True if the segment has insertion info, otherwise false.
86
+ */
87
+ export const isInserted = (segmentLike: unknown): segmentLike is IInsertionInfo =>
88
+ toInsertionInfo(segmentLike) !== undefined;
89
+
90
+ /**
91
+ * Asserts that the segment has insertion info. Usage of this function should not produce a user facing error.
92
+ *
93
+ * @param segmentLike - The segment-like object to check.
94
+ * @throws Will throw an error if the segment does not have insertion info.
95
+ */
96
+ export const assertInserted: <T extends Partial<IInsertionInfo> | undefined>(
97
+ segmentLike: ISegmentInternal | Partial<IInsertionInfo> | T,
98
+ ) => asserts segmentLike is IInsertionInfo | Exclude<T, Partial<IInsertionInfo>> = (
99
+ segmentLike,
100
+ ) =>
101
+ assert(
102
+ segmentLike === undefined || isInserted(segmentLike),
103
+ 0xaa0 /* must be insertionInfo */,
104
+ );
105
+
106
+ /**
107
+ * Common properties for a node in a merge tree.
108
+ */
109
+ export interface IMergeNodeInfo {
110
+ /**
111
+ * The parent merge block if the node is parented
112
+ */
113
+ parent: MergeBlock;
114
+
115
+ /**
116
+ * The index of this node in its parent's list of children.
117
+ */
118
+ index: number;
119
+
120
+ /**
121
+ * A string that can be used for comparing the location of this node to other `MergeNode`s in the same tree.
122
+ * `a.ordinal < b.ordinal` if and only if `a` comes before `b` in a pre-order traversal of the tree.
123
+ */
124
+ ordinal: string;
125
+ }
126
+
127
+ /**
128
+ * Converts a segment-like object to a merge node info object if possible.
129
+ *
130
+ * @param segmentLike - The segment-like object to convert.
131
+ * @returns The merge node info object if the conversion is possible, otherwise undefined.
132
+ */
133
+ export const toMergeNodeInfo = (nodeLike: unknown): IMergeNodeInfo | undefined =>
134
+ propInstanceOf(nodeLike, "parent", MergeBlock) &&
135
+ hasProp(nodeLike, "ordinal", "string") &&
136
+ hasProp(nodeLike, "index", "number")
137
+ ? nodeLike
138
+ : undefined;
139
+
140
+ /**
141
+ * A type-guard which determines if the segment has merge node info, and
142
+ * returns true if it does, along with applying strong typing.
143
+ *
144
+ * @param nodeLike - The segment-like object to check.
145
+ * @returns True if the segment has merge node info, otherwise false.
146
+ */
147
+ export const isMergeNodeInfo = (nodeLike: unknown): nodeLike is IMergeNodeInfo =>
148
+ toMergeNodeInfo(nodeLike) !== undefined;
149
+
150
+ /**
151
+ * Asserts that the segment has merge node info. Usage of this function should not produce a user facing error.
152
+ *
153
+ * @param segmentLike - The segment-like object to check.
154
+ * @throws Will throw an error if the segment does not have merge node info.
155
+ */
156
+ export const assertMergeNode: <T extends Partial<IMergeNodeInfo> | undefined>(
157
+ nodeLike: ISegmentInternal | ISegmentPrivate | Partial<IMergeNodeInfo> | T,
158
+ ) => asserts nodeLike is IMergeNodeInfo | Exclude<T, Partial<IMergeNodeInfo>> = (
159
+ segmentLike,
160
+ ) =>
161
+ assert(
162
+ segmentLike === undefined || isMergeNodeInfo(segmentLike),
163
+ 0xaa1 /* must be MergeNodeInfo */,
164
+ );
165
+
166
+ /**
167
+ * Removes the merge node info. This is used to remove nodes from the merge-tree.
168
+ * @param segmentLike - The segment-like object to check.
169
+ * @returns This function will change the type of the provided node like to never via an assertion. This
170
+ * ensures no further usage of the removed merge node info is allowed. if continued use is required other
171
+ * type coercion methods should be used to correctly re-type the variable.
172
+ */
173
+ export const removeMergeNodeInfo: (nodeLike: IMergeNodeInfo) => asserts nodeLike is never = (
174
+ nodeLike,
175
+ ) =>
176
+ Object.assign<IMergeNodeInfo, Record<keyof IMergeNodeInfo, undefined>>(nodeLike, {
177
+ parent: undefined,
178
+ index: undefined,
179
+ ordinal: undefined,
180
+ });
181
+
182
+ /**
183
+ * Contains removal information associated to an {@link ISegment}.
184
+ * @legacy
185
+ * @alpha
186
+ * @deprecated - This interface will be removed in 2.20 with no replacement.
187
+ */
188
+ export interface IRemovalInfo {
189
+ /**
190
+ * Local seq at which this segment was removed, if the removal is yet-to-be acked.
191
+ */
192
+ localRemovedSeq?: number;
193
+ /**
194
+ * Seq at which this segment was removed.
195
+ */
196
+ removedSeq: number;
197
+ /**
198
+ * List of client IDs that have removed this segment.
199
+ * The client that actually removed the segment (i.e. whose removal op was sequenced first) is stored as the first
200
+ * client in this list. Other clients in the list have all issued concurrent ops to remove the segment.
201
+ * @remarks When this list has length \> 1, this is referred to as the "overlapping remove" case.
202
+ */
203
+ removedClientIds: number[];
204
+ }
205
+
206
+ /**
207
+ * Converts a segment-like object to a removal info object if possible.
208
+ *
209
+ * @param segmentLike - The segment-like object to convert.
210
+ * @returns The removal info object if the conversion is possible, otherwise undefined.
211
+ */
212
+ export const toRemovalInfo = (segmentLike: unknown): IRemovalInfo | undefined =>
213
+ hasProp(segmentLike, "removedClientIds", "array") &&
214
+ hasProp(segmentLike, "removedSeq", "number")
215
+ ? segmentLike
216
+ : undefined;
217
+
218
+ /**
219
+ * A type-guard which determines if the segment has removal info, and
220
+ * returns true if it does, along with applying strong typing.
221
+ *
222
+ * @param segmentLike - The segment-like object to check.
223
+ * @returns True if the segment has removal info, otherwise false.
224
+ */
225
+ export const isRemoved = (segmentLike: unknown): segmentLike is IRemovalInfo =>
226
+ toRemovalInfo(segmentLike) !== undefined;
227
+
228
+ /**
229
+ * Asserts that the segment has removal info. Usage of this function should not produce a user facing error.
230
+ *
231
+ * @param segmentLike - The segment-like object to check.
232
+ * @throws Will throw an error if the segment does not have removal info.
233
+ */
234
+ export const assertRemoved: <T extends Partial<IRemovalInfo> | undefined>(
235
+ segmentLike: ISegmentInternal | Partial<IRemovalInfo> | T,
236
+ ) => asserts segmentLike is IRemovalInfo | Exclude<T, Partial<IRemovalInfo>> = (segmentLike) =>
237
+ assert(segmentLike === undefined || isRemoved(segmentLike), 0xaa2 /* must be removalInfo */);
238
+
239
+ /**
240
+ * Removes the removal info. This is used in rollback.
241
+ * @param segmentLike - The segment-like object to check.
242
+ * @returns This function will change the type of the provided node like to never via an assertion. This
243
+ * ensures no further usage of the removed removal info is allowed. if continued use is required other
244
+ * type coercion methods should be use to correctly re-type the variable.
245
+ */
246
+ export const removeRemovalInfo: (nodeLike: IRemovalInfo) => asserts nodeLike is never = (
247
+ nodeLike,
248
+ ) =>
249
+ Object.assign<IRemovalInfo, Record<keyof IRemovalInfo, undefined>>(nodeLike, {
250
+ localRemovedSeq: undefined,
251
+ removedClientIds: undefined,
252
+ removedSeq: undefined,
253
+ });
254
+
255
+ /**
256
+ * Tracks information about when and where this segment was moved to.
257
+ *
258
+ * Note that merge-tree does not currently support moving and only supports
259
+ * obliterate. The fields below include "move" in their names to avoid renaming
260
+ * in the future, when moves _are_ supported.
261
+ * @legacy
262
+ * @alpha
263
+ * @deprecated - This interface will be removed in 2.20 with no replacement.
264
+ */
265
+ export interface IMoveInfo {
266
+ /**
267
+ * Local seq at which this segment was moved if the move is yet-to-be
268
+ * acked.
269
+ */
270
+ localMovedSeq?: number;
271
+
272
+ /**
273
+ * The first seq at which this segment was moved.
274
+ */
275
+ movedSeq: number;
276
+
277
+ /**
278
+ * All seqs at which this segment was moved. In the case of overlapping,
279
+ * concurrent moves this array will contain multiple seqs.
280
+ *
281
+ * The seq at `movedSeqs[i]` corresponds to the client id at `movedClientIds[i]`.
282
+ *
283
+ * The first element corresponds to the seq of the first move
284
+ */
285
+ movedSeqs: number[];
286
+
287
+ /**
288
+ * A reference to the inserted destination segment corresponding to this
289
+ * segment's move.
290
+ *
291
+ * If undefined, the move was an obliterate.
292
+ *
293
+ * Currently this field is unused, as we only support obliterate operations
294
+ */
295
+ moveDst?: ReferencePosition;
296
+
297
+ /**
298
+ * List of client IDs that have moved this segment.
299
+ *
300
+ * The client that actually moved the segment (i.e. whose move op was sequenced
301
+ * first) is stored as the first client in this list. Other clients in the
302
+ * list have all issued concurrent ops to move the segment.
303
+ */
304
+ movedClientIds: number[];
305
+
306
+ /**
307
+ * If this segment was inserted into a concurrently moved range and
308
+ * the move op was sequenced before the insertion op. In this case,
309
+ * the segment is visible only to the inserting client
310
+ *
311
+ * `wasMovedOnInsert` only applies for acked obliterates. That is, if
312
+ * a segment inserted by a remote client is moved on insertion by a local
313
+ * and unacked obliterate, we do not consider it as having been moved
314
+ * on insert
315
+ *
316
+ * If a segment is moved on insertion, its length is only ever visible to
317
+ * the client that inserted the segment. This is relevant in partial length
318
+ * calculations
319
+ */
320
+ wasMovedOnInsert: boolean;
321
+ }
322
+ export const toMoveInfo = (segmentLike: unknown): IMoveInfo | undefined =>
323
+ hasProp(segmentLike, "movedClientIds", "array") &&
324
+ hasProp(segmentLike, "movedSeq", "number") &&
325
+ hasProp(segmentLike, "movedSeqs", "array") &&
326
+ hasProp(segmentLike, "wasMovedOnInsert", "boolean")
327
+ ? segmentLike
328
+ : undefined;
329
+
330
+ /**
331
+ * A type-guard which determines if the segment has move info, and
332
+ * returns true if it does, along with applying strong typing.
333
+ *
334
+ * @param segmentLike - The segment-like object to check.
335
+ * @returns True if the segment has move info, otherwise false.
336
+ */
337
+ export const isMoved = (segmentLike: unknown): segmentLike is IMoveInfo =>
338
+ toMoveInfo(segmentLike) !== undefined;
339
+
340
+ /**
341
+ * Asserts that the segment has move info. Usage of this function should not produce a user facing error.
342
+ *
343
+ * @param segmentLike - The segment-like object to check.
344
+ * @throws Will throw an error if the segment does not have move info.
345
+ */
346
+ export const assertMoved: <T extends Partial<IMoveInfo> | undefined>(
347
+ segmentLike: ISegmentInternal | Partial<IMoveInfo> | T,
348
+ ) => asserts segmentLike is IMoveInfo | Exclude<T, Partial<IMoveInfo>> = (segmentLike) =>
349
+ assert(segmentLike === undefined || isMoved(segmentLike), 0xaa3 /* must be moveInfo */);
350
+
351
+ /**
352
+ * A union type representing any segment info.
353
+ */
354
+ export type SegmentInfo = IMergeNodeInfo | IInsertionInfo | IMoveInfo | IRemovalInfo;
355
+
356
+ /**
357
+ * A type representing a segment with additional info.
358
+ */
359
+ export type SegmentWithInfo<
360
+ T extends SegmentInfo,
361
+ S extends ISegmentPrivate = ISegmentPrivate,
362
+ > = S & T;
363
+
364
+ /**
365
+ * Overwrites the segment info on a segment-like object.
366
+ *
367
+ * @param segmentLike - The segment-like object to set the info on.
368
+ * @param info - The segment info to overwrite.
369
+ * @returns The segment-like object with the info set.
370
+ */
371
+ export const overwriteInfo = <
372
+ T extends SegmentInfo,
373
+ S extends ISegmentPrivate = ISegmentPrivate,
374
+ >(
375
+ segmentLike: S,
376
+ info: T,
377
+ ): SegmentWithInfo<T, S> => Object.assign(segmentLike, info);
@@ -20,12 +20,20 @@ import {
20
20
  createChildLogger,
21
21
  } from "@fluidframework/telemetry-utils/internal";
22
22
 
23
- // eslint-disable-next-line import/no-deprecated
24
23
  import { Client } from "./client.js";
25
24
  import { NonCollabClient, UniversalSequenceNumber } from "./constants.js";
26
25
  import { MergeTree } from "./mergeTree.js";
27
- import { ISegmentLeaf } from "./mergeTreeNodes.js";
26
+ import { ISegmentPrivate } from "./mergeTreeNodes.js";
28
27
  import { IJSONSegment } from "./ops.js";
28
+ import {
29
+ // eslint-disable-next-line import/no-deprecated
30
+ IRemovalInfo,
31
+ overwriteInfo,
32
+ type IInsertionInfo,
33
+ // eslint-disable-next-line import/no-deprecated
34
+ type IMoveInfo,
35
+ type SegmentWithInfo,
36
+ } from "./segmentInfos.js";
29
37
  import {
30
38
  IJSONSegmentWithMergeInfo,
31
39
  MergeTreeChunkV1,
@@ -39,7 +47,7 @@ export class SnapshotLoader {
39
47
 
40
48
  constructor(
41
49
  private readonly runtime: IFluidDataStoreRuntime,
42
- // eslint-disable-next-line import/no-deprecated
50
+
43
51
  private readonly client: Client,
44
52
  private readonly mergeTree: MergeTree,
45
53
  logger: ITelemetryLoggerExt,
@@ -96,60 +104,57 @@ export class SnapshotLoader {
96
104
 
97
105
  private readonly specToSegment = (
98
106
  spec: IJSONSegment | IJSONSegmentWithMergeInfo,
99
- ): ISegmentLeaf => {
100
- let seg: ISegmentLeaf;
101
-
107
+ ): SegmentWithInfo<IInsertionInfo> => {
102
108
  if (hasMergeInfo(spec)) {
103
- seg = this.client.specToSegment(spec.json);
104
-
105
- // `specToSegment()` initializes `seg` with the LocalClientId. Overwrite this with
106
- // the `spec` client (if specified). Otherwise overwrite with `NonCollabClient`.
107
- seg.clientId =
108
- spec.client === undefined
109
- ? NonCollabClient
110
- : this.client.getOrAddShortClientId(spec.client);
111
-
112
- seg.seq = spec.seq ?? UniversalSequenceNumber;
109
+ const seg = overwriteInfo<IInsertionInfo>(this.client.specToSegment(spec.json), {
110
+ clientId:
111
+ spec.client === undefined
112
+ ? NonCollabClient
113
+ : this.client.getOrAddShortClientId(spec.client),
114
+ seq: spec.seq ?? UniversalSequenceNumber,
115
+ });
113
116
 
114
117
  if (spec.removedSeq !== undefined) {
115
- seg.removedSeq = spec.removedSeq;
118
+ // this format had a bug where it didn't store all the overlap clients
119
+ // this is for back compat, so we change the singular id to an array
120
+ // this will only cause problems if there is an overlapping delete
121
+ // spanning the snapshot, which should be rare
122
+ const specAsBuggyFormat: IJSONSegmentWithMergeInfo & { removedClient?: string } = spec;
123
+ if (specAsBuggyFormat.removedClient !== undefined) {
124
+ spec.removedClientIds ??= [specAsBuggyFormat.removedClient];
125
+ }
126
+ assert(spec.removedClientIds !== undefined, 0xaa4 /* must have removedClient ids */);
127
+ // eslint-disable-next-line import/no-deprecated
128
+ overwriteInfo<IRemovalInfo>(seg, {
129
+ removedSeq: spec.removedSeq,
130
+ removedClientIds: spec.removedClientIds.map((id) =>
131
+ this.client.getOrAddShortClientId(id),
132
+ ),
133
+ });
116
134
  }
117
135
  if (spec.movedSeq !== undefined) {
118
- seg.movedSeq = spec.movedSeq;
119
- }
120
- if (spec.movedSeqs !== undefined) {
121
- seg.movedSeqs = spec.movedSeqs;
122
- }
123
- // this format had a bug where it didn't store all the overlap clients
124
- // this is for back compat, so we change the singular id to an array
125
- // this will only cause problems if there is an overlapping delete
126
- // spanning the snapshot, which should be rare
127
- const specAsBuggyFormat: IJSONSegmentWithMergeInfo & { removedClient?: string } = spec;
128
- if (specAsBuggyFormat.removedClient !== undefined) {
129
- seg.removedClientIds = [
130
- this.client.getOrAddShortClientId(specAsBuggyFormat.removedClient),
131
- ];
132
- }
133
- if (spec.removedClientIds !== undefined) {
134
- seg.removedClientIds = spec.removedClientIds?.map((sid) =>
135
- this.client.getOrAddShortClientId(sid),
136
- );
137
- }
138
- if (spec.movedClientIds !== undefined) {
139
- seg.movedClientIds = spec.movedClientIds?.map((sid) =>
140
- this.client.getOrAddShortClientId(sid),
136
+ assert(
137
+ spec.movedClientIds !== undefined && spec.movedSeqs !== undefined,
138
+ 0xaa5 /* must have movedIds ids */,
141
139
  );
140
+ // eslint-disable-next-line import/no-deprecated
141
+ overwriteInfo<IMoveInfo>(seg, {
142
+ movedSeq: spec.movedSeq,
143
+ movedSeqs: spec.movedSeqs,
144
+ movedClientIds: spec.movedClientIds.map((id) =>
145
+ this.client.getOrAddShortClientId(id),
146
+ ),
147
+ // BUG? This isn't persisted
148
+ wasMovedOnInsert: false,
149
+ });
142
150
  }
143
- } else {
144
- seg = this.client.specToSegment(spec);
145
- seg.seq = UniversalSequenceNumber;
146
151
 
147
- // `specToSegment()` initializes `seg` with the LocalClientId. We must overwrite this with
148
- // `NonCollabClient`.
149
- seg.clientId = NonCollabClient;
152
+ return seg;
150
153
  }
151
-
152
- return seg;
154
+ return overwriteInfo(this.client.specToSegment(spec), {
155
+ seq: UniversalSequenceNumber,
156
+ clientId: NonCollabClient,
157
+ });
153
158
  };
154
159
 
155
160
  private loadHeader(header: string): MergeTreeChunkV1 {
@@ -207,7 +212,7 @@ export class SnapshotLoader {
207
212
  }
208
213
 
209
214
  let chunksWithAttribution = chunk1.attribution === undefined ? 0 : 1;
210
- const segs: ISegmentLeaf[] = [];
215
+ const segs: SegmentWithInfo<IInsertionInfo>[] = [];
211
216
  let lengthSofar = chunk1.length;
212
217
  for (
213
218
  let chunkIndex = 1;
@@ -244,7 +249,7 @@ export class SnapshotLoader {
244
249
 
245
250
  // Helper to insert segments at the end of the MergeTree.
246
251
  const mergeTree = this.mergeTree;
247
- const append = (segments: ISegmentLeaf[], cli: number, seq: number): void => {
252
+ const append = (segments: ISegmentPrivate[], cli: number, seq: number): void => {
248
253
  mergeTree.insertSegments(
249
254
  mergeTree.root.cachedLength ?? 0,
250
255
  segments,
@@ -256,7 +261,7 @@ export class SnapshotLoader {
256
261
  };
257
262
 
258
263
  // Helpers to batch-insert segments that are below the min seq
259
- const batch: ISegmentLeaf[] = [];
264
+ const batch: SegmentWithInfo<IInsertionInfo>[] = [];
260
265
  const flushBatch = (): void => {
261
266
  if (batch.length > 0) {
262
267
  append(batch, NonCollabClient, UniversalSequenceNumber);
@@ -264,23 +269,21 @@ export class SnapshotLoader {
264
269
  };
265
270
 
266
271
  for (const seg of segs) {
267
- const cli = seg.clientId;
268
- const seq = seg.seq;
269
-
272
+ const { clientId, seq } = seg;
270
273
  // If the segment can be batch inserted, add it to the 'batch' array. Otherwise, flush
271
274
  // any batched segments and then insert the current segment individually.
272
- if (cli === NonCollabClient && seq === UniversalSequenceNumber) {
275
+ if (clientId === NonCollabClient && seq === UniversalSequenceNumber) {
273
276
  batch.push(seg);
274
277
  } else {
275
278
  flushBatch();
276
- append([seg], cli, seq!);
279
+ append([seg], clientId, seq);
277
280
  }
278
281
  }
279
282
 
280
283
  flushBatch();
281
284
  }
282
285
 
283
- private extractAttribution(segments: ISegmentLeaf[], chunk: MergeTreeChunkV1): void {
286
+ private extractAttribution(segments: ISegmentPrivate[], chunk: MergeTreeChunkV1): void {
284
287
  if (chunk.attribution) {
285
288
  const { attributionPolicy } = this.mergeTree;
286
289
  if (attributionPolicy === undefined) {
package/src/snapshotV1.ts CHANGED
@@ -22,9 +22,10 @@ import { IAttributionCollection } from "./attributionCollection.js";
22
22
  import { UnassignedSequenceNumber } from "./constants.js";
23
23
  import { MergeTree } from "./mergeTree.js";
24
24
  import { walkAllChildSegments } from "./mergeTreeNodeWalk.js";
25
- import { ISegmentLeaf } from "./mergeTreeNodes.js";
25
+ import { ISegmentPrivate } from "./mergeTreeNodes.js";
26
26
  import type { IJSONSegment } from "./ops.js";
27
27
  import { PropertySet, matchProperties } from "./properties.js";
28
+ import { assertInserted, isMoved, isRemoved } from "./segmentInfos.js";
28
29
  import {
29
30
  IJSONSegmentWithMergeInfo,
30
31
  JsonSegmentSpecs,
@@ -208,7 +209,7 @@ export class SnapshotV1 {
208
209
  };
209
210
 
210
211
  // Helper to serialize the given `segment` and add it to the snapshot (if a segment is provided).
211
- const pushSeg = (segment?: ISegmentLeaf): void => {
212
+ const pushSeg = (segment?: ISegmentPrivate): void => {
212
213
  if (segment) {
213
214
  if (segment.properties !== undefined && Object.keys(segment.properties).length === 0) {
214
215
  segment.properties = undefined;
@@ -221,8 +222,9 @@ export class SnapshotV1 {
221
222
  }
222
223
  };
223
224
 
224
- let prev: ISegmentLeaf | undefined;
225
- const extractSegment = (segment: ISegmentLeaf): boolean => {
225
+ let prev: ISegmentPrivate | undefined;
226
+ const extractSegment = (segment: ISegmentPrivate): boolean => {
227
+ assertInserted(segment);
226
228
  // Elide segments that do not need to be included in the snapshot. A segment may be elided if
227
229
  // either condition is true:
228
230
  // a) The segment has not yet been ACKed. We do not need to snapshot unACKed segments because
@@ -231,10 +233,8 @@ export class SnapshotV1 {
231
233
  // segment, and therefore we can discard it.
232
234
  if (
233
235
  segment.seq === UnassignedSequenceNumber ||
234
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
235
- segment.removedSeq! <= minSeq ||
236
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
237
- segment.movedSeq! <= minSeq
236
+ (isRemoved(segment) && segment.removedSeq <= minSeq) ||
237
+ (isMoved(segment) && segment.movedSeq <= minSeq)
238
238
  ) {
239
239
  if (segment.seq !== UnassignedSequenceNumber) {
240
240
  originalSegments += 1;
@@ -248,11 +248,10 @@ export class SnapshotV1 {
248
248
  // (seq, client, etc.) This information is only needed if the segment is above the MSN (and doesn't
249
249
  // have a pending remove.)
250
250
  if (
251
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
252
- segment.seq! <= minSeq && // Segment is below the MSN, and...
253
- (segment.removedSeq === undefined || // .. Segment has not been removed, or...
251
+ segment.seq <= minSeq && // Segment is below the MSN, and...
252
+ (!isRemoved(segment) || // .. Segment has not been removed, or...
254
253
  segment.removedSeq === UnassignedSequenceNumber) && // .. Removal op to be delivered on reconnect
255
- (segment.movedSeq === undefined || segment.movedSeq === UnassignedSequenceNumber)
254
+ (!isMoved(segment) || segment.movedSeq === UnassignedSequenceNumber)
256
255
  ) {
257
256
  // This segment is below the MSN, which means that future ops will not reference it. Attempt to
258
257
  // coalesce the new segment with the previous (if any).
@@ -286,14 +285,13 @@ export class SnapshotV1 {
286
285
  json: segment.toJSONObject() as IJSONSegment,
287
286
  };
288
287
  // If the segment insertion is above the MSN, record the insertion merge info.
289
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
290
- if (segment.seq! > minSeq) {
288
+ if (segment.seq > minSeq) {
291
289
  raw.seq = segment.seq;
292
290
  raw.client = this.getLongClientId(segment.clientId);
293
291
  }
294
292
  // We have already dispensed with removed segments below the MSN and removed segments with unassigned
295
293
  // sequence numbers. Any remaining removal info should be preserved.
296
- if (segment.removedSeq !== undefined) {
294
+ if (isRemoved(segment)) {
297
295
  assert(
298
296
  segment.removedSeq !== UnassignedSequenceNumber && segment.removedSeq > minSeq,
299
297
  0x065 /* "On removal info preservation, segment has invalid removed sequence number!" */,
@@ -311,7 +309,7 @@ export class SnapshotV1 {
311
309
  );
312
310
  }
313
311
 
314
- if (segment.movedSeq !== undefined) {
312
+ if (isMoved(segment)) {
315
313
  assert(
316
314
  segment.movedSeq !== UnassignedSequenceNumber && segment.movedSeq > minSeq,
317
315
  0x873 /* On move info preservation, segment has invalid moved sequence number! */,