@fluidframework/tree 2.83.0 → 2.90.0-378676

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 (781) hide show
  1. package/api-report/tree.alpha.api.md +1 -0
  2. package/dist/core/change-family/changeFamily.d.ts +4 -1
  3. package/dist/core/change-family/changeFamily.d.ts.map +1 -1
  4. package/dist/core/change-family/changeFamily.js.map +1 -1
  5. package/dist/core/change-family/index.d.ts +1 -1
  6. package/dist/core/change-family/index.d.ts.map +1 -1
  7. package/dist/core/change-family/index.js.map +1 -1
  8. package/dist/core/index.d.ts +3 -3
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +6 -4
  11. package/dist/core/index.js.map +1 -1
  12. package/dist/core/rebase/changeRebaser.d.ts +6 -1
  13. package/dist/core/rebase/changeRebaser.d.ts.map +1 -1
  14. package/dist/core/rebase/changeRebaser.js.map +1 -1
  15. package/dist/core/rebase/index.d.ts +1 -1
  16. package/dist/core/rebase/index.d.ts.map +1 -1
  17. package/dist/core/rebase/index.js +2 -1
  18. package/dist/core/rebase/index.js.map +1 -1
  19. package/dist/core/rebase/types.d.ts +1 -0
  20. package/dist/core/rebase/types.d.ts.map +1 -1
  21. package/dist/core/rebase/types.js +5 -1
  22. package/dist/core/rebase/types.js.map +1 -1
  23. package/dist/core/rebase/utils.d.ts.map +1 -1
  24. package/dist/core/rebase/utils.js +25 -7
  25. package/dist/core/rebase/utils.js.map +1 -1
  26. package/dist/core/tree/delta.d.ts +5 -0
  27. package/dist/core/tree/delta.d.ts.map +1 -1
  28. package/dist/core/tree/delta.js.map +1 -1
  29. package/dist/core/tree/detachedFieldIndex.d.ts +13 -1
  30. package/dist/core/tree/detachedFieldIndex.d.ts.map +1 -1
  31. package/dist/core/tree/detachedFieldIndex.js +14 -1
  32. package/dist/core/tree/detachedFieldIndex.js.map +1 -1
  33. package/dist/core/tree/detachedFieldIndexTypes.d.ts +4 -0
  34. package/dist/core/tree/detachedFieldIndexTypes.d.ts.map +1 -1
  35. package/dist/core/tree/detachedFieldIndexTypes.js.map +1 -1
  36. package/dist/core/tree/index.d.ts +2 -2
  37. package/dist/core/tree/index.d.ts.map +1 -1
  38. package/dist/core/tree/index.js +4 -3
  39. package/dist/core/tree/index.js.map +1 -1
  40. package/dist/core/tree/pathTree.d.ts +11 -3
  41. package/dist/core/tree/pathTree.d.ts.map +1 -1
  42. package/dist/core/tree/pathTree.js +14 -2
  43. package/dist/core/tree/pathTree.js.map +1 -1
  44. package/dist/core/tree/visitDelta.d.ts.map +1 -1
  45. package/dist/core/tree/visitDelta.js +17 -13
  46. package/dist/core/tree/visitDelta.js.map +1 -1
  47. package/dist/feature-libraries/changeAtomIdBTree.d.ts +3 -2
  48. package/dist/feature-libraries/changeAtomIdBTree.d.ts.map +1 -1
  49. package/dist/feature-libraries/changeAtomIdBTree.js +15 -2
  50. package/dist/feature-libraries/changeAtomIdBTree.js.map +1 -1
  51. package/dist/feature-libraries/default-schema/defaultEditBuilder.d.ts +92 -44
  52. package/dist/feature-libraries/default-schema/defaultEditBuilder.d.ts.map +1 -1
  53. package/dist/feature-libraries/default-schema/defaultEditBuilder.js +220 -70
  54. package/dist/feature-libraries/default-schema/defaultEditBuilder.js.map +1 -1
  55. package/dist/feature-libraries/default-schema/defaultFieldKinds.d.ts.map +1 -1
  56. package/dist/feature-libraries/default-schema/defaultFieldKinds.js +12 -2
  57. package/dist/feature-libraries/default-schema/defaultFieldKinds.js.map +1 -1
  58. package/dist/feature-libraries/default-schema/index.d.ts +2 -1
  59. package/dist/feature-libraries/default-schema/index.d.ts.map +1 -1
  60. package/dist/feature-libraries/default-schema/index.js +4 -2
  61. package/dist/feature-libraries/default-schema/index.js.map +1 -1
  62. package/dist/feature-libraries/default-schema/locationBasedEditBuilder.d.ts +40 -0
  63. package/dist/feature-libraries/default-schema/locationBasedEditBuilder.d.ts.map +1 -0
  64. package/dist/feature-libraries/default-schema/locationBasedEditBuilder.js +153 -0
  65. package/dist/feature-libraries/default-schema/locationBasedEditBuilder.js.map +1 -0
  66. package/dist/feature-libraries/default-schema/mappedEditBuilder.d.ts +7 -6
  67. package/dist/feature-libraries/default-schema/mappedEditBuilder.d.ts.map +1 -1
  68. package/dist/feature-libraries/default-schema/mappedEditBuilder.js +15 -0
  69. package/dist/feature-libraries/default-schema/mappedEditBuilder.js.map +1 -1
  70. package/dist/feature-libraries/deltaUtils.d.ts +1 -0
  71. package/dist/feature-libraries/deltaUtils.d.ts.map +1 -1
  72. package/dist/feature-libraries/deltaUtils.js +6 -1
  73. package/dist/feature-libraries/deltaUtils.js.map +1 -1
  74. package/dist/feature-libraries/flex-tree/context.d.ts +9 -0
  75. package/dist/feature-libraries/flex-tree/context.d.ts.map +1 -1
  76. package/dist/feature-libraries/flex-tree/context.js +6 -0
  77. package/dist/feature-libraries/flex-tree/context.js.map +1 -1
  78. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +6 -6
  79. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  80. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  81. package/dist/feature-libraries/flex-tree/lazyField.d.ts +8 -7
  82. package/dist/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  83. package/dist/feature-libraries/flex-tree/lazyField.js +39 -8
  84. package/dist/feature-libraries/flex-tree/lazyField.js.map +1 -1
  85. package/dist/feature-libraries/index.d.ts +3 -3
  86. package/dist/feature-libraries/index.d.ts.map +1 -1
  87. package/dist/feature-libraries/index.js +8 -4
  88. package/dist/feature-libraries/index.js.map +1 -1
  89. package/dist/feature-libraries/mitigatedChangeFamily.d.ts.map +1 -1
  90. package/dist/feature-libraries/mitigatedChangeFamily.js +2 -2
  91. package/dist/feature-libraries/mitigatedChangeFamily.js.map +1 -1
  92. package/dist/feature-libraries/modular-schema/crossFieldQueries.d.ts +100 -24
  93. package/dist/feature-libraries/modular-schema/crossFieldQueries.d.ts.map +1 -1
  94. package/dist/feature-libraries/modular-schema/crossFieldQueries.js +8 -12
  95. package/dist/feature-libraries/modular-schema/crossFieldQueries.js.map +1 -1
  96. package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts +85 -51
  97. package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
  98. package/dist/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
  99. package/dist/feature-libraries/modular-schema/genericFieldKind.d.ts.map +1 -1
  100. package/dist/feature-libraries/modular-schema/genericFieldKind.js +4 -9
  101. package/dist/feature-libraries/modular-schema/genericFieldKind.js.map +1 -1
  102. package/dist/feature-libraries/modular-schema/index.d.ts +3 -3
  103. package/dist/feature-libraries/modular-schema/index.d.ts.map +1 -1
  104. package/dist/feature-libraries/modular-schema/index.js +2 -3
  105. package/dist/feature-libraries/modular-schema/index.js.map +1 -1
  106. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts +18 -22
  107. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
  108. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js +276 -158
  109. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
  110. package/dist/feature-libraries/modular-schema/modularChangeCodecV2.d.ts.map +1 -1
  111. package/dist/feature-libraries/modular-schema/modularChangeCodecV2.js +1 -1
  112. package/dist/feature-libraries/modular-schema/modularChangeCodecV2.js.map +1 -1
  113. package/dist/feature-libraries/modular-schema/modularChangeCodecV3.d.ts +15 -0
  114. package/dist/feature-libraries/modular-schema/modularChangeCodecV3.d.ts.map +1 -0
  115. package/dist/feature-libraries/modular-schema/modularChangeCodecV3.js +296 -0
  116. package/dist/feature-libraries/modular-schema/modularChangeCodecV3.js.map +1 -0
  117. package/dist/feature-libraries/modular-schema/modularChangeCodecs.d.ts +1 -0
  118. package/dist/feature-libraries/modular-schema/modularChangeCodecs.d.ts.map +1 -1
  119. package/dist/feature-libraries/modular-schema/modularChangeCodecs.js +8 -0
  120. package/dist/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
  121. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts +56 -22
  122. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  123. package/dist/feature-libraries/modular-schema/modularChangeFamily.js +1390 -462
  124. package/dist/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  125. package/dist/feature-libraries/modular-schema/modularChangeFormatV1.d.ts.map +1 -1
  126. package/dist/feature-libraries/modular-schema/modularChangeFormatV1.js.map +1 -1
  127. package/dist/feature-libraries/modular-schema/modularChangeFormatV2.d.ts +1 -2
  128. package/dist/feature-libraries/modular-schema/modularChangeFormatV2.d.ts.map +1 -1
  129. package/dist/feature-libraries/modular-schema/modularChangeFormatV2.js +3 -3
  130. package/dist/feature-libraries/modular-schema/modularChangeFormatV2.js.map +1 -1
  131. package/dist/feature-libraries/modular-schema/modularChangeFormatV3.d.ts +74 -0
  132. package/dist/feature-libraries/modular-schema/modularChangeFormatV3.d.ts.map +1 -0
  133. package/dist/feature-libraries/modular-schema/modularChangeFormatV3.js +35 -0
  134. package/dist/feature-libraries/modular-schema/modularChangeFormatV3.js.map +1 -0
  135. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts +49 -10
  136. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  137. package/dist/feature-libraries/modular-schema/modularChangeTypes.js +3 -3
  138. package/dist/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  139. package/dist/feature-libraries/optional-field/optionalField.d.ts +13 -32
  140. package/dist/feature-libraries/optional-field/optionalField.d.ts.map +1 -1
  141. package/dist/feature-libraries/optional-field/optionalField.js +257 -446
  142. package/dist/feature-libraries/optional-field/optionalField.js.map +1 -1
  143. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV3.d.ts +23 -0
  144. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV3.d.ts.map +1 -0
  145. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV3.js +31 -0
  146. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV3.js.map +1 -0
  147. package/dist/feature-libraries/optional-field/optionalFieldChangeTypes.d.ts +31 -31
  148. package/dist/feature-libraries/optional-field/optionalFieldChangeTypes.d.ts.map +1 -1
  149. package/dist/feature-libraries/optional-field/optionalFieldChangeTypes.js.map +1 -1
  150. package/dist/feature-libraries/optional-field/optionalFieldCodecV2.d.ts +1 -1
  151. package/dist/feature-libraries/optional-field/optionalFieldCodecV2.d.ts.map +1 -1
  152. package/dist/feature-libraries/optional-field/optionalFieldCodecV2.js +57 -28
  153. package/dist/feature-libraries/optional-field/optionalFieldCodecV2.js.map +1 -1
  154. package/dist/feature-libraries/optional-field/optionalFieldCodecV3.d.ts +12 -0
  155. package/dist/feature-libraries/optional-field/optionalFieldCodecV3.d.ts.map +1 -0
  156. package/dist/feature-libraries/optional-field/optionalFieldCodecV3.js +57 -0
  157. package/dist/feature-libraries/optional-field/optionalFieldCodecV3.js.map +1 -0
  158. package/dist/feature-libraries/optional-field/optionalFieldCodecs.d.ts.map +1 -1
  159. package/dist/feature-libraries/optional-field/optionalFieldCodecs.js +5 -1
  160. package/dist/feature-libraries/optional-field/optionalFieldCodecs.js.map +1 -1
  161. package/dist/feature-libraries/optional-field/requiredField.d.ts +3 -2
  162. package/dist/feature-libraries/optional-field/requiredField.d.ts.map +1 -1
  163. package/dist/feature-libraries/optional-field/requiredField.js +6 -1
  164. package/dist/feature-libraries/optional-field/requiredField.js.map +1 -1
  165. package/dist/feature-libraries/sequence-field/compose.d.ts +6 -7
  166. package/dist/feature-libraries/sequence-field/compose.d.ts.map +1 -1
  167. package/dist/feature-libraries/sequence-field/compose.js +81 -259
  168. package/dist/feature-libraries/sequence-field/compose.js.map +1 -1
  169. package/dist/feature-libraries/sequence-field/helperTypes.d.ts +14 -10
  170. package/dist/feature-libraries/sequence-field/helperTypes.d.ts.map +1 -1
  171. package/dist/feature-libraries/sequence-field/helperTypes.js.map +1 -1
  172. package/dist/feature-libraries/sequence-field/invert.d.ts +3 -3
  173. package/dist/feature-libraries/sequence-field/invert.d.ts.map +1 -1
  174. package/dist/feature-libraries/sequence-field/invert.js +65 -167
  175. package/dist/feature-libraries/sequence-field/invert.js.map +1 -1
  176. package/dist/feature-libraries/sequence-field/markQueue.d.ts +2 -2
  177. package/dist/feature-libraries/sequence-field/markQueue.d.ts.map +1 -1
  178. package/dist/feature-libraries/sequence-field/markQueue.js.map +1 -1
  179. package/dist/feature-libraries/sequence-field/moveEffectTable.d.ts +4 -56
  180. package/dist/feature-libraries/sequence-field/moveEffectTable.d.ts.map +1 -1
  181. package/dist/feature-libraries/sequence-field/moveEffectTable.js +7 -90
  182. package/dist/feature-libraries/sequence-field/moveEffectTable.js.map +1 -1
  183. package/dist/feature-libraries/sequence-field/rebase.d.ts +3 -3
  184. package/dist/feature-libraries/sequence-field/rebase.d.ts.map +1 -1
  185. package/dist/feature-libraries/sequence-field/rebase.js +109 -116
  186. package/dist/feature-libraries/sequence-field/rebase.js.map +1 -1
  187. package/dist/feature-libraries/sequence-field/replaceRevisions.d.ts.map +1 -1
  188. package/dist/feature-libraries/sequence-field/replaceRevisions.js +19 -32
  189. package/dist/feature-libraries/sequence-field/replaceRevisions.js.map +1 -1
  190. package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.d.ts.map +1 -1
  191. package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.js +1 -2
  192. package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.js.map +1 -1
  193. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV2.d.ts +22 -4
  194. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV2.d.ts.map +1 -1
  195. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV2.js +388 -187
  196. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV2.js.map +1 -1
  197. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV3.d.ts.map +1 -1
  198. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV3.js +20 -62
  199. package/dist/feature-libraries/sequence-field/sequenceFieldCodecV3.js.map +1 -1
  200. package/dist/feature-libraries/sequence-field/sequenceFieldEditor.d.ts +2 -2
  201. package/dist/feature-libraries/sequence-field/sequenceFieldEditor.d.ts.map +1 -1
  202. package/dist/feature-libraries/sequence-field/sequenceFieldEditor.js +13 -13
  203. package/dist/feature-libraries/sequence-field/sequenceFieldEditor.js.map +1 -1
  204. package/dist/feature-libraries/sequence-field/sequenceFieldToDelta.d.ts +3 -2
  205. package/dist/feature-libraries/sequence-field/sequenceFieldToDelta.d.ts.map +1 -1
  206. package/dist/feature-libraries/sequence-field/sequenceFieldToDelta.js +16 -111
  207. package/dist/feature-libraries/sequence-field/sequenceFieldToDelta.js.map +1 -1
  208. package/dist/feature-libraries/sequence-field/types.d.ts +37 -74
  209. package/dist/feature-libraries/sequence-field/types.d.ts.map +1 -1
  210. package/dist/feature-libraries/sequence-field/types.js.map +1 -1
  211. package/dist/feature-libraries/sequence-field/utils.d.ts +20 -25
  212. package/dist/feature-libraries/sequence-field/utils.d.ts.map +1 -1
  213. package/dist/feature-libraries/sequence-field/utils.js +159 -320
  214. package/dist/feature-libraries/sequence-field/utils.js.map +1 -1
  215. package/dist/packageVersion.d.ts +1 -1
  216. package/dist/packageVersion.d.ts.map +1 -1
  217. package/dist/packageVersion.js +1 -1
  218. package/dist/packageVersion.js.map +1 -1
  219. package/dist/shared-tree/independentView.d.ts.map +1 -1
  220. package/dist/shared-tree/independentView.js +1 -1
  221. package/dist/shared-tree/independentView.js.map +1 -1
  222. package/dist/shared-tree/index.d.ts +1 -1
  223. package/dist/shared-tree/index.d.ts.map +1 -1
  224. package/dist/shared-tree/index.js.map +1 -1
  225. package/dist/shared-tree/schematizeTree.d.ts +4 -4
  226. package/dist/shared-tree/schematizeTree.d.ts.map +1 -1
  227. package/dist/shared-tree/schematizeTree.js +2 -1
  228. package/dist/shared-tree/schematizeTree.js.map +1 -1
  229. package/dist/shared-tree/schematizingTreeView.d.ts +1 -5
  230. package/dist/shared-tree/schematizingTreeView.d.ts.map +1 -1
  231. package/dist/shared-tree/schematizingTreeView.js +21 -35
  232. package/dist/shared-tree/schematizingTreeView.js.map +1 -1
  233. package/dist/shared-tree/sharedTree.d.ts +9 -3
  234. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  235. package/dist/shared-tree/sharedTree.js +4 -1
  236. package/dist/shared-tree/sharedTree.js.map +1 -1
  237. package/dist/shared-tree/sharedTreeChangeCodecs.d.ts +1 -0
  238. package/dist/shared-tree/sharedTreeChangeCodecs.d.ts.map +1 -1
  239. package/dist/shared-tree/sharedTreeChangeCodecs.js +8 -0
  240. package/dist/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
  241. package/dist/shared-tree/sharedTreeChangeFamily.d.ts +4 -4
  242. package/dist/shared-tree/sharedTreeChangeFamily.d.ts.map +1 -1
  243. package/dist/shared-tree/sharedTreeChangeFamily.js +2 -2
  244. package/dist/shared-tree/sharedTreeChangeFamily.js.map +1 -1
  245. package/dist/shared-tree/sharedTreeEditBuilder.d.ts +16 -6
  246. package/dist/shared-tree/sharedTreeEditBuilder.d.ts.map +1 -1
  247. package/dist/shared-tree/sharedTreeEditBuilder.js +14 -7
  248. package/dist/shared-tree/sharedTreeEditBuilder.js.map +1 -1
  249. package/dist/shared-tree/treeCheckout.d.ts +13 -11
  250. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  251. package/dist/shared-tree/treeCheckout.js +56 -6
  252. package/dist/shared-tree/treeCheckout.js.map +1 -1
  253. package/dist/shared-tree-core/branch.d.ts +3 -2
  254. package/dist/shared-tree-core/branch.d.ts.map +1 -1
  255. package/dist/shared-tree-core/branch.js +4 -3
  256. package/dist/shared-tree-core/branch.js.map +1 -1
  257. package/dist/shared-tree-core/editManager.d.ts +2 -2
  258. package/dist/shared-tree-core/editManager.d.ts.map +1 -1
  259. package/dist/shared-tree-core/editManager.js +9 -9
  260. package/dist/shared-tree-core/editManager.js.map +1 -1
  261. package/dist/shared-tree-core/editManagerCodecs.d.ts +4 -0
  262. package/dist/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
  263. package/dist/shared-tree-core/editManagerCodecs.js +10 -2
  264. package/dist/shared-tree-core/editManagerCodecs.js.map +1 -1
  265. package/dist/shared-tree-core/editManagerFormatCommons.d.ts +1 -0
  266. package/dist/shared-tree-core/editManagerFormatCommons.d.ts.map +1 -1
  267. package/dist/shared-tree-core/editManagerFormatCommons.js +6 -0
  268. package/dist/shared-tree-core/editManagerFormatCommons.js.map +1 -1
  269. package/dist/shared-tree-core/editManagerFormatV1toV4.d.ts +2 -2
  270. package/dist/shared-tree-core/editManagerFormatV1toV4.d.ts.map +1 -1
  271. package/dist/shared-tree-core/editManagerFormatV1toV4.js +1 -0
  272. package/dist/shared-tree-core/editManagerFormatV1toV4.js.map +1 -1
  273. package/dist/shared-tree-core/index.d.ts +2 -2
  274. package/dist/shared-tree-core/index.d.ts.map +1 -1
  275. package/dist/shared-tree-core/index.js +3 -1
  276. package/dist/shared-tree-core/index.js.map +1 -1
  277. package/dist/shared-tree-core/messageCodecV1ToV4.d.ts +1 -1
  278. package/dist/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -1
  279. package/dist/shared-tree-core/messageCodecV1ToV4.js.map +1 -1
  280. package/dist/shared-tree-core/messageCodecs.d.ts +4 -0
  281. package/dist/shared-tree-core/messageCodecs.d.ts.map +1 -1
  282. package/dist/shared-tree-core/messageCodecs.js +10 -2
  283. package/dist/shared-tree-core/messageCodecs.js.map +1 -1
  284. package/dist/shared-tree-core/messageFormat.d.ts +1 -0
  285. package/dist/shared-tree-core/messageFormat.d.ts.map +1 -1
  286. package/dist/shared-tree-core/messageFormat.js +6 -0
  287. package/dist/shared-tree-core/messageFormat.js.map +1 -1
  288. package/dist/shared-tree-core/messageFormatV1ToV4.d.ts +2 -2
  289. package/dist/shared-tree-core/messageFormatV1ToV4.d.ts.map +1 -1
  290. package/dist/shared-tree-core/messageFormatV1ToV4.js +1 -0
  291. package/dist/shared-tree-core/messageFormatV1ToV4.js.map +1 -1
  292. package/dist/shared-tree-core/sharedTreeCore.d.ts +1 -0
  293. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  294. package/dist/shared-tree-core/sharedTreeCore.js +1 -1
  295. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  296. package/dist/simple-tree/core/unhydratedFlexTree.d.ts +16 -12
  297. package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  298. package/dist/simple-tree/core/unhydratedFlexTree.js +59 -8
  299. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  300. package/dist/simple-tree/fieldSchema.d.ts +4 -4
  301. package/dist/simple-tree/fieldSchema.d.ts.map +1 -1
  302. package/dist/simple-tree/fieldSchema.js.map +1 -1
  303. package/dist/simple-tree/index.d.ts +2 -2
  304. package/dist/simple-tree/index.d.ts.map +1 -1
  305. package/dist/simple-tree/index.js +1 -1
  306. package/dist/simple-tree/index.js.map +1 -1
  307. package/dist/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
  308. package/dist/simple-tree/node-kinds/array/arrayNode.js +5 -3
  309. package/dist/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  310. package/dist/simple-tree/node-kinds/common.d.ts.map +1 -1
  311. package/dist/simple-tree/node-kinds/common.js +1 -1
  312. package/dist/simple-tree/node-kinds/common.js.map +1 -1
  313. package/dist/simple-tree/node-kinds/map/mapNode.js +2 -2
  314. package/dist/simple-tree/node-kinds/map/mapNode.js.map +1 -1
  315. package/dist/simple-tree/node-kinds/object/objectNode.d.ts.map +1 -1
  316. package/dist/simple-tree/node-kinds/object/objectNode.js +19 -19
  317. package/dist/simple-tree/node-kinds/object/objectNode.js.map +1 -1
  318. package/dist/simple-tree/node-kinds/record/recordNode.d.ts.map +1 -1
  319. package/dist/simple-tree/node-kinds/record/recordNode.js +4 -2
  320. package/dist/simple-tree/node-kinds/record/recordNode.js.map +1 -1
  321. package/dist/simple-tree/prepareForInsertion.d.ts +54 -47
  322. package/dist/simple-tree/prepareForInsertion.d.ts.map +1 -1
  323. package/dist/simple-tree/prepareForInsertion.js +183 -125
  324. package/dist/simple-tree/prepareForInsertion.js.map +1 -1
  325. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.d.ts +8 -3
  326. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.d.ts.map +1 -1
  327. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.js +27 -13
  328. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.js.map +1 -1
  329. package/dist/treeFactory.d.ts.map +1 -1
  330. package/dist/treeFactory.js +12 -2
  331. package/dist/treeFactory.js.map +1 -1
  332. package/dist/util/index.d.ts +1 -1
  333. package/dist/util/index.d.ts.map +1 -1
  334. package/dist/util/index.js +2 -1
  335. package/dist/util/index.js.map +1 -1
  336. package/dist/util/rangeMap.d.ts +23 -11
  337. package/dist/util/rangeMap.d.ts.map +1 -1
  338. package/dist/util/rangeMap.js +43 -10
  339. package/dist/util/rangeMap.js.map +1 -1
  340. package/lib/core/change-family/changeFamily.d.ts +4 -1
  341. package/lib/core/change-family/changeFamily.d.ts.map +1 -1
  342. package/lib/core/change-family/changeFamily.js.map +1 -1
  343. package/lib/core/change-family/index.d.ts +1 -1
  344. package/lib/core/change-family/index.d.ts.map +1 -1
  345. package/lib/core/change-family/index.js.map +1 -1
  346. package/lib/core/index.d.ts +3 -3
  347. package/lib/core/index.d.ts.map +1 -1
  348. package/lib/core/index.js +2 -2
  349. package/lib/core/index.js.map +1 -1
  350. package/lib/core/rebase/changeRebaser.d.ts +6 -1
  351. package/lib/core/rebase/changeRebaser.d.ts.map +1 -1
  352. package/lib/core/rebase/changeRebaser.js.map +1 -1
  353. package/lib/core/rebase/index.d.ts +1 -1
  354. package/lib/core/rebase/index.d.ts.map +1 -1
  355. package/lib/core/rebase/index.js +1 -1
  356. package/lib/core/rebase/index.js.map +1 -1
  357. package/lib/core/rebase/types.d.ts +1 -0
  358. package/lib/core/rebase/types.d.ts.map +1 -1
  359. package/lib/core/rebase/types.js +3 -0
  360. package/lib/core/rebase/types.js.map +1 -1
  361. package/lib/core/rebase/utils.d.ts.map +1 -1
  362. package/lib/core/rebase/utils.js +25 -7
  363. package/lib/core/rebase/utils.js.map +1 -1
  364. package/lib/core/tree/delta.d.ts +5 -0
  365. package/lib/core/tree/delta.d.ts.map +1 -1
  366. package/lib/core/tree/delta.js.map +1 -1
  367. package/lib/core/tree/detachedFieldIndex.d.ts +13 -1
  368. package/lib/core/tree/detachedFieldIndex.d.ts.map +1 -1
  369. package/lib/core/tree/detachedFieldIndex.js +15 -2
  370. package/lib/core/tree/detachedFieldIndex.js.map +1 -1
  371. package/lib/core/tree/detachedFieldIndexTypes.d.ts +4 -0
  372. package/lib/core/tree/detachedFieldIndexTypes.d.ts.map +1 -1
  373. package/lib/core/tree/detachedFieldIndexTypes.js.map +1 -1
  374. package/lib/core/tree/index.d.ts +2 -2
  375. package/lib/core/tree/index.d.ts.map +1 -1
  376. package/lib/core/tree/index.js +1 -1
  377. package/lib/core/tree/index.js.map +1 -1
  378. package/lib/core/tree/pathTree.d.ts +11 -3
  379. package/lib/core/tree/pathTree.d.ts.map +1 -1
  380. package/lib/core/tree/pathTree.js +12 -1
  381. package/lib/core/tree/pathTree.js.map +1 -1
  382. package/lib/core/tree/visitDelta.d.ts.map +1 -1
  383. package/lib/core/tree/visitDelta.js +17 -13
  384. package/lib/core/tree/visitDelta.js.map +1 -1
  385. package/lib/feature-libraries/changeAtomIdBTree.d.ts +3 -2
  386. package/lib/feature-libraries/changeAtomIdBTree.d.ts.map +1 -1
  387. package/lib/feature-libraries/changeAtomIdBTree.js +15 -3
  388. package/lib/feature-libraries/changeAtomIdBTree.js.map +1 -1
  389. package/lib/feature-libraries/default-schema/defaultEditBuilder.d.ts +92 -44
  390. package/lib/feature-libraries/default-schema/defaultEditBuilder.d.ts.map +1 -1
  391. package/lib/feature-libraries/default-schema/defaultEditBuilder.js +217 -69
  392. package/lib/feature-libraries/default-schema/defaultEditBuilder.js.map +1 -1
  393. package/lib/feature-libraries/default-schema/defaultFieldKinds.d.ts.map +1 -1
  394. package/lib/feature-libraries/default-schema/defaultFieldKinds.js +12 -2
  395. package/lib/feature-libraries/default-schema/defaultFieldKinds.js.map +1 -1
  396. package/lib/feature-libraries/default-schema/index.d.ts +2 -1
  397. package/lib/feature-libraries/default-schema/index.d.ts.map +1 -1
  398. package/lib/feature-libraries/default-schema/index.js +2 -1
  399. package/lib/feature-libraries/default-schema/index.js.map +1 -1
  400. package/lib/feature-libraries/default-schema/locationBasedEditBuilder.d.ts +40 -0
  401. package/lib/feature-libraries/default-schema/locationBasedEditBuilder.d.ts.map +1 -0
  402. package/lib/feature-libraries/default-schema/locationBasedEditBuilder.js +149 -0
  403. package/lib/feature-libraries/default-schema/locationBasedEditBuilder.js.map +1 -0
  404. package/lib/feature-libraries/default-schema/mappedEditBuilder.d.ts +7 -6
  405. package/lib/feature-libraries/default-schema/mappedEditBuilder.d.ts.map +1 -1
  406. package/lib/feature-libraries/default-schema/mappedEditBuilder.js +15 -0
  407. package/lib/feature-libraries/default-schema/mappedEditBuilder.js.map +1 -1
  408. package/lib/feature-libraries/deltaUtils.d.ts +1 -0
  409. package/lib/feature-libraries/deltaUtils.d.ts.map +1 -1
  410. package/lib/feature-libraries/deltaUtils.js +5 -1
  411. package/lib/feature-libraries/deltaUtils.js.map +1 -1
  412. package/lib/feature-libraries/flex-tree/context.d.ts +9 -0
  413. package/lib/feature-libraries/flex-tree/context.d.ts.map +1 -1
  414. package/lib/feature-libraries/flex-tree/context.js +6 -0
  415. package/lib/feature-libraries/flex-tree/context.js.map +1 -1
  416. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +6 -6
  417. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  418. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  419. package/lib/feature-libraries/flex-tree/lazyField.d.ts +8 -7
  420. package/lib/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  421. package/lib/feature-libraries/flex-tree/lazyField.js +40 -9
  422. package/lib/feature-libraries/flex-tree/lazyField.js.map +1 -1
  423. package/lib/feature-libraries/index.d.ts +3 -3
  424. package/lib/feature-libraries/index.d.ts.map +1 -1
  425. package/lib/feature-libraries/index.js +3 -3
  426. package/lib/feature-libraries/index.js.map +1 -1
  427. package/lib/feature-libraries/mitigatedChangeFamily.d.ts.map +1 -1
  428. package/lib/feature-libraries/mitigatedChangeFamily.js +2 -2
  429. package/lib/feature-libraries/mitigatedChangeFamily.js.map +1 -1
  430. package/lib/feature-libraries/modular-schema/crossFieldQueries.d.ts +100 -24
  431. package/lib/feature-libraries/modular-schema/crossFieldQueries.d.ts.map +1 -1
  432. package/lib/feature-libraries/modular-schema/crossFieldQueries.js +7 -10
  433. package/lib/feature-libraries/modular-schema/crossFieldQueries.js.map +1 -1
  434. package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts +85 -51
  435. package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
  436. package/lib/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
  437. package/lib/feature-libraries/modular-schema/genericFieldKind.d.ts.map +1 -1
  438. package/lib/feature-libraries/modular-schema/genericFieldKind.js +4 -9
  439. package/lib/feature-libraries/modular-schema/genericFieldKind.js.map +1 -1
  440. package/lib/feature-libraries/modular-schema/index.d.ts +3 -3
  441. package/lib/feature-libraries/modular-schema/index.d.ts.map +1 -1
  442. package/lib/feature-libraries/modular-schema/index.js +1 -1
  443. package/lib/feature-libraries/modular-schema/index.js.map +1 -1
  444. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts +18 -22
  445. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
  446. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js +267 -144
  447. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
  448. package/lib/feature-libraries/modular-schema/modularChangeCodecV2.d.ts.map +1 -1
  449. package/lib/feature-libraries/modular-schema/modularChangeCodecV2.js +1 -1
  450. package/lib/feature-libraries/modular-schema/modularChangeCodecV2.js.map +1 -1
  451. package/lib/feature-libraries/modular-schema/modularChangeCodecV3.d.ts +15 -0
  452. package/lib/feature-libraries/modular-schema/modularChangeCodecV3.d.ts.map +1 -0
  453. package/lib/feature-libraries/modular-schema/modularChangeCodecV3.js +292 -0
  454. package/lib/feature-libraries/modular-schema/modularChangeCodecV3.js.map +1 -0
  455. package/lib/feature-libraries/modular-schema/modularChangeCodecs.d.ts +1 -0
  456. package/lib/feature-libraries/modular-schema/modularChangeCodecs.d.ts.map +1 -1
  457. package/lib/feature-libraries/modular-schema/modularChangeCodecs.js +8 -0
  458. package/lib/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
  459. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts +56 -22
  460. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  461. package/lib/feature-libraries/modular-schema/modularChangeFamily.js +1387 -468
  462. package/lib/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  463. package/lib/feature-libraries/modular-schema/modularChangeFormatV1.d.ts.map +1 -1
  464. package/lib/feature-libraries/modular-schema/modularChangeFormatV1.js.map +1 -1
  465. package/lib/feature-libraries/modular-schema/modularChangeFormatV2.d.ts +1 -2
  466. package/lib/feature-libraries/modular-schema/modularChangeFormatV2.d.ts.map +1 -1
  467. package/lib/feature-libraries/modular-schema/modularChangeFormatV2.js +1 -1
  468. package/lib/feature-libraries/modular-schema/modularChangeFormatV2.js.map +1 -1
  469. package/lib/feature-libraries/modular-schema/modularChangeFormatV3.d.ts +74 -0
  470. package/lib/feature-libraries/modular-schema/modularChangeFormatV3.d.ts.map +1 -0
  471. package/lib/feature-libraries/modular-schema/modularChangeFormatV3.js +32 -0
  472. package/lib/feature-libraries/modular-schema/modularChangeFormatV3.js.map +1 -0
  473. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts +49 -10
  474. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  475. package/lib/feature-libraries/modular-schema/modularChangeTypes.js +1 -1
  476. package/lib/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  477. package/lib/feature-libraries/optional-field/optionalField.d.ts +13 -32
  478. package/lib/feature-libraries/optional-field/optionalField.d.ts.map +1 -1
  479. package/lib/feature-libraries/optional-field/optionalField.js +254 -442
  480. package/lib/feature-libraries/optional-field/optionalField.js.map +1 -1
  481. package/lib/feature-libraries/optional-field/optionalFieldChangeFormatV3.d.ts +23 -0
  482. package/lib/feature-libraries/optional-field/optionalFieldChangeFormatV3.d.ts.map +1 -0
  483. package/lib/feature-libraries/optional-field/optionalFieldChangeFormatV3.js +27 -0
  484. package/lib/feature-libraries/optional-field/optionalFieldChangeFormatV3.js.map +1 -0
  485. package/lib/feature-libraries/optional-field/optionalFieldChangeTypes.d.ts +31 -31
  486. package/lib/feature-libraries/optional-field/optionalFieldChangeTypes.d.ts.map +1 -1
  487. package/lib/feature-libraries/optional-field/optionalFieldChangeTypes.js.map +1 -1
  488. package/lib/feature-libraries/optional-field/optionalFieldCodecV2.d.ts +1 -1
  489. package/lib/feature-libraries/optional-field/optionalFieldCodecV2.d.ts.map +1 -1
  490. package/lib/feature-libraries/optional-field/optionalFieldCodecV2.js +55 -26
  491. package/lib/feature-libraries/optional-field/optionalFieldCodecV2.js.map +1 -1
  492. package/lib/feature-libraries/optional-field/optionalFieldCodecV3.d.ts +12 -0
  493. package/lib/feature-libraries/optional-field/optionalFieldCodecV3.d.ts.map +1 -0
  494. package/lib/feature-libraries/optional-field/optionalFieldCodecV3.js +53 -0
  495. package/lib/feature-libraries/optional-field/optionalFieldCodecV3.js.map +1 -0
  496. package/lib/feature-libraries/optional-field/optionalFieldCodecs.d.ts.map +1 -1
  497. package/lib/feature-libraries/optional-field/optionalFieldCodecs.js +5 -1
  498. package/lib/feature-libraries/optional-field/optionalFieldCodecs.js.map +1 -1
  499. package/lib/feature-libraries/optional-field/requiredField.d.ts +3 -2
  500. package/lib/feature-libraries/optional-field/requiredField.d.ts.map +1 -1
  501. package/lib/feature-libraries/optional-field/requiredField.js +5 -1
  502. package/lib/feature-libraries/optional-field/requiredField.js.map +1 -1
  503. package/lib/feature-libraries/sequence-field/compose.d.ts +6 -7
  504. package/lib/feature-libraries/sequence-field/compose.d.ts.map +1 -1
  505. package/lib/feature-libraries/sequence-field/compose.js +83 -261
  506. package/lib/feature-libraries/sequence-field/compose.js.map +1 -1
  507. package/lib/feature-libraries/sequence-field/helperTypes.d.ts +14 -10
  508. package/lib/feature-libraries/sequence-field/helperTypes.d.ts.map +1 -1
  509. package/lib/feature-libraries/sequence-field/helperTypes.js.map +1 -1
  510. package/lib/feature-libraries/sequence-field/invert.d.ts +3 -3
  511. package/lib/feature-libraries/sequence-field/invert.d.ts.map +1 -1
  512. package/lib/feature-libraries/sequence-field/invert.js +67 -169
  513. package/lib/feature-libraries/sequence-field/invert.js.map +1 -1
  514. package/lib/feature-libraries/sequence-field/markQueue.d.ts +2 -2
  515. package/lib/feature-libraries/sequence-field/markQueue.d.ts.map +1 -1
  516. package/lib/feature-libraries/sequence-field/markQueue.js.map +1 -1
  517. package/lib/feature-libraries/sequence-field/moveEffectTable.d.ts +4 -56
  518. package/lib/feature-libraries/sequence-field/moveEffectTable.d.ts.map +1 -1
  519. package/lib/feature-libraries/sequence-field/moveEffectTable.js +6 -84
  520. package/lib/feature-libraries/sequence-field/moveEffectTable.js.map +1 -1
  521. package/lib/feature-libraries/sequence-field/rebase.d.ts +3 -3
  522. package/lib/feature-libraries/sequence-field/rebase.d.ts.map +1 -1
  523. package/lib/feature-libraries/sequence-field/rebase.js +111 -118
  524. package/lib/feature-libraries/sequence-field/rebase.js.map +1 -1
  525. package/lib/feature-libraries/sequence-field/replaceRevisions.d.ts.map +1 -1
  526. package/lib/feature-libraries/sequence-field/replaceRevisions.js +19 -32
  527. package/lib/feature-libraries/sequence-field/replaceRevisions.js.map +1 -1
  528. package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.d.ts.map +1 -1
  529. package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.js +2 -3
  530. package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.js.map +1 -1
  531. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV2.d.ts +22 -4
  532. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV2.d.ts.map +1 -1
  533. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV2.js +379 -182
  534. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV2.js.map +1 -1
  535. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV3.d.ts.map +1 -1
  536. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV3.js +21 -63
  537. package/lib/feature-libraries/sequence-field/sequenceFieldCodecV3.js.map +1 -1
  538. package/lib/feature-libraries/sequence-field/sequenceFieldEditor.d.ts +2 -2
  539. package/lib/feature-libraries/sequence-field/sequenceFieldEditor.d.ts.map +1 -1
  540. package/lib/feature-libraries/sequence-field/sequenceFieldEditor.js +13 -13
  541. package/lib/feature-libraries/sequence-field/sequenceFieldEditor.js.map +1 -1
  542. package/lib/feature-libraries/sequence-field/sequenceFieldToDelta.d.ts +3 -2
  543. package/lib/feature-libraries/sequence-field/sequenceFieldToDelta.d.ts.map +1 -1
  544. package/lib/feature-libraries/sequence-field/sequenceFieldToDelta.js +16 -111
  545. package/lib/feature-libraries/sequence-field/sequenceFieldToDelta.js.map +1 -1
  546. package/lib/feature-libraries/sequence-field/types.d.ts +37 -74
  547. package/lib/feature-libraries/sequence-field/types.d.ts.map +1 -1
  548. package/lib/feature-libraries/sequence-field/types.js.map +1 -1
  549. package/lib/feature-libraries/sequence-field/utils.d.ts +20 -25
  550. package/lib/feature-libraries/sequence-field/utils.d.ts.map +1 -1
  551. package/lib/feature-libraries/sequence-field/utils.js +155 -313
  552. package/lib/feature-libraries/sequence-field/utils.js.map +1 -1
  553. package/lib/packageVersion.d.ts +1 -1
  554. package/lib/packageVersion.d.ts.map +1 -1
  555. package/lib/packageVersion.js +1 -1
  556. package/lib/packageVersion.js.map +1 -1
  557. package/lib/shared-tree/independentView.d.ts.map +1 -1
  558. package/lib/shared-tree/independentView.js +1 -1
  559. package/lib/shared-tree/independentView.js.map +1 -1
  560. package/lib/shared-tree/index.d.ts +1 -1
  561. package/lib/shared-tree/index.d.ts.map +1 -1
  562. package/lib/shared-tree/index.js.map +1 -1
  563. package/lib/shared-tree/schematizeTree.d.ts +4 -4
  564. package/lib/shared-tree/schematizeTree.d.ts.map +1 -1
  565. package/lib/shared-tree/schematizeTree.js +3 -2
  566. package/lib/shared-tree/schematizeTree.js.map +1 -1
  567. package/lib/shared-tree/schematizingTreeView.d.ts +1 -5
  568. package/lib/shared-tree/schematizingTreeView.d.ts.map +1 -1
  569. package/lib/shared-tree/schematizingTreeView.js +24 -38
  570. package/lib/shared-tree/schematizingTreeView.js.map +1 -1
  571. package/lib/shared-tree/sharedTree.d.ts +9 -3
  572. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  573. package/lib/shared-tree/sharedTree.js +4 -1
  574. package/lib/shared-tree/sharedTree.js.map +1 -1
  575. package/lib/shared-tree/sharedTreeChangeCodecs.d.ts +1 -0
  576. package/lib/shared-tree/sharedTreeChangeCodecs.d.ts.map +1 -1
  577. package/lib/shared-tree/sharedTreeChangeCodecs.js +8 -0
  578. package/lib/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
  579. package/lib/shared-tree/sharedTreeChangeFamily.d.ts +4 -4
  580. package/lib/shared-tree/sharedTreeChangeFamily.d.ts.map +1 -1
  581. package/lib/shared-tree/sharedTreeChangeFamily.js +3 -3
  582. package/lib/shared-tree/sharedTreeChangeFamily.js.map +1 -1
  583. package/lib/shared-tree/sharedTreeEditBuilder.d.ts +16 -6
  584. package/lib/shared-tree/sharedTreeEditBuilder.d.ts.map +1 -1
  585. package/lib/shared-tree/sharedTreeEditBuilder.js +12 -6
  586. package/lib/shared-tree/sharedTreeEditBuilder.js.map +1 -1
  587. package/lib/shared-tree/treeCheckout.d.ts +13 -11
  588. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  589. package/lib/shared-tree/treeCheckout.js +59 -9
  590. package/lib/shared-tree/treeCheckout.js.map +1 -1
  591. package/lib/shared-tree-core/branch.d.ts +3 -2
  592. package/lib/shared-tree-core/branch.d.ts.map +1 -1
  593. package/lib/shared-tree-core/branch.js +4 -3
  594. package/lib/shared-tree-core/branch.js.map +1 -1
  595. package/lib/shared-tree-core/editManager.d.ts +2 -2
  596. package/lib/shared-tree-core/editManager.d.ts.map +1 -1
  597. package/lib/shared-tree-core/editManager.js +9 -9
  598. package/lib/shared-tree-core/editManager.js.map +1 -1
  599. package/lib/shared-tree-core/editManagerCodecs.d.ts +4 -0
  600. package/lib/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
  601. package/lib/shared-tree-core/editManagerCodecs.js +8 -1
  602. package/lib/shared-tree-core/editManagerCodecs.js.map +1 -1
  603. package/lib/shared-tree-core/editManagerFormatCommons.d.ts +1 -0
  604. package/lib/shared-tree-core/editManagerFormatCommons.d.ts.map +1 -1
  605. package/lib/shared-tree-core/editManagerFormatCommons.js +6 -0
  606. package/lib/shared-tree-core/editManagerFormatCommons.js.map +1 -1
  607. package/lib/shared-tree-core/editManagerFormatV1toV4.d.ts +2 -2
  608. package/lib/shared-tree-core/editManagerFormatV1toV4.d.ts.map +1 -1
  609. package/lib/shared-tree-core/editManagerFormatV1toV4.js +1 -0
  610. package/lib/shared-tree-core/editManagerFormatV1toV4.js.map +1 -1
  611. package/lib/shared-tree-core/index.d.ts +2 -2
  612. package/lib/shared-tree-core/index.d.ts.map +1 -1
  613. package/lib/shared-tree-core/index.js +2 -2
  614. package/lib/shared-tree-core/index.js.map +1 -1
  615. package/lib/shared-tree-core/messageCodecV1ToV4.d.ts +1 -1
  616. package/lib/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -1
  617. package/lib/shared-tree-core/messageCodecV1ToV4.js.map +1 -1
  618. package/lib/shared-tree-core/messageCodecs.d.ts +4 -0
  619. package/lib/shared-tree-core/messageCodecs.d.ts.map +1 -1
  620. package/lib/shared-tree-core/messageCodecs.js +8 -1
  621. package/lib/shared-tree-core/messageCodecs.js.map +1 -1
  622. package/lib/shared-tree-core/messageFormat.d.ts +1 -0
  623. package/lib/shared-tree-core/messageFormat.d.ts.map +1 -1
  624. package/lib/shared-tree-core/messageFormat.js +6 -0
  625. package/lib/shared-tree-core/messageFormat.js.map +1 -1
  626. package/lib/shared-tree-core/messageFormatV1ToV4.d.ts +2 -2
  627. package/lib/shared-tree-core/messageFormatV1ToV4.d.ts.map +1 -1
  628. package/lib/shared-tree-core/messageFormatV1ToV4.js +1 -0
  629. package/lib/shared-tree-core/messageFormatV1ToV4.js.map +1 -1
  630. package/lib/shared-tree-core/sharedTreeCore.d.ts +1 -0
  631. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  632. package/lib/shared-tree-core/sharedTreeCore.js +1 -1
  633. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  634. package/lib/simple-tree/core/unhydratedFlexTree.d.ts +16 -12
  635. package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  636. package/lib/simple-tree/core/unhydratedFlexTree.js +58 -8
  637. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  638. package/lib/simple-tree/fieldSchema.d.ts +4 -4
  639. package/lib/simple-tree/fieldSchema.d.ts.map +1 -1
  640. package/lib/simple-tree/fieldSchema.js.map +1 -1
  641. package/lib/simple-tree/index.d.ts +2 -2
  642. package/lib/simple-tree/index.d.ts.map +1 -1
  643. package/lib/simple-tree/index.js +1 -1
  644. package/lib/simple-tree/index.js.map +1 -1
  645. package/lib/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
  646. package/lib/simple-tree/node-kinds/array/arrayNode.js +6 -4
  647. package/lib/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  648. package/lib/simple-tree/node-kinds/common.d.ts.map +1 -1
  649. package/lib/simple-tree/node-kinds/common.js +2 -2
  650. package/lib/simple-tree/node-kinds/common.js.map +1 -1
  651. package/lib/simple-tree/node-kinds/map/mapNode.js +2 -2
  652. package/lib/simple-tree/node-kinds/map/mapNode.js.map +1 -1
  653. package/lib/simple-tree/node-kinds/object/objectNode.d.ts.map +1 -1
  654. package/lib/simple-tree/node-kinds/object/objectNode.js +20 -20
  655. package/lib/simple-tree/node-kinds/object/objectNode.js.map +1 -1
  656. package/lib/simple-tree/node-kinds/record/recordNode.d.ts.map +1 -1
  657. package/lib/simple-tree/node-kinds/record/recordNode.js +4 -2
  658. package/lib/simple-tree/node-kinds/record/recordNode.js.map +1 -1
  659. package/lib/simple-tree/prepareForInsertion.d.ts +54 -47
  660. package/lib/simple-tree/prepareForInsertion.d.ts.map +1 -1
  661. package/lib/simple-tree/prepareForInsertion.js +184 -125
  662. package/lib/simple-tree/prepareForInsertion.js.map +1 -1
  663. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.d.ts +8 -3
  664. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.d.ts.map +1 -1
  665. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.js +23 -10
  666. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.js.map +1 -1
  667. package/lib/treeFactory.d.ts.map +1 -1
  668. package/lib/treeFactory.js +13 -3
  669. package/lib/treeFactory.js.map +1 -1
  670. package/lib/util/index.d.ts +1 -1
  671. package/lib/util/index.d.ts.map +1 -1
  672. package/lib/util/index.js +1 -1
  673. package/lib/util/index.js.map +1 -1
  674. package/lib/util/rangeMap.d.ts +23 -11
  675. package/lib/util/rangeMap.d.ts.map +1 -1
  676. package/lib/util/rangeMap.js +41 -9
  677. package/lib/util/rangeMap.js.map +1 -1
  678. package/package.json +21 -21
  679. package/src/core/change-family/changeFamily.ts +5 -0
  680. package/src/core/change-family/index.ts +1 -0
  681. package/src/core/index.ts +4 -1
  682. package/src/core/rebase/changeRebaser.ts +6 -1
  683. package/src/core/rebase/index.ts +1 -0
  684. package/src/core/rebase/types.ts +4 -0
  685. package/src/core/rebase/utils.ts +31 -7
  686. package/src/core/tree/delta.ts +6 -0
  687. package/src/core/tree/detachedFieldIndex.ts +29 -1
  688. package/src/core/tree/detachedFieldIndexTypes.ts +5 -0
  689. package/src/core/tree/index.ts +13 -12
  690. package/src/core/tree/pathTree.ts +16 -4
  691. package/src/core/tree/visitDelta.ts +31 -11
  692. package/src/feature-libraries/changeAtomIdBTree.ts +28 -3
  693. package/src/feature-libraries/default-schema/defaultEditBuilder.ts +369 -127
  694. package/src/feature-libraries/default-schema/defaultFieldKinds.ts +13 -4
  695. package/src/feature-libraries/default-schema/index.ts +16 -5
  696. package/src/feature-libraries/default-schema/locationBasedEditBuilder.ts +231 -0
  697. package/src/feature-libraries/default-schema/mappedEditBuilder.ts +35 -9
  698. package/src/feature-libraries/deltaUtils.ts +6 -1
  699. package/src/feature-libraries/flex-tree/context.ts +17 -0
  700. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +7 -8
  701. package/src/feature-libraries/flex-tree/lazyField.ts +65 -24
  702. package/src/feature-libraries/index.ts +22 -9
  703. package/src/feature-libraries/mitigatedChangeFamily.ts +3 -1
  704. package/src/feature-libraries/modular-schema/crossFieldQueries.ts +144 -47
  705. package/src/feature-libraries/modular-schema/fieldChangeHandler.ts +113 -58
  706. package/src/feature-libraries/modular-schema/genericFieldKind.ts +7 -18
  707. package/src/feature-libraries/modular-schema/index.ts +16 -16
  708. package/src/feature-libraries/modular-schema/modularChangeCodecV1.ts +623 -348
  709. package/src/feature-libraries/modular-schema/modularChangeCodecV2.ts +1 -0
  710. package/src/feature-libraries/modular-schema/modularChangeCodecV3.ts +649 -0
  711. package/src/feature-libraries/modular-schema/modularChangeCodecs.ts +14 -0
  712. package/src/feature-libraries/modular-schema/modularChangeFamily.ts +2694 -748
  713. package/src/feature-libraries/modular-schema/modularChangeFormatV1.ts +1 -0
  714. package/src/feature-libraries/modular-schema/modularChangeFormatV2.ts +1 -1
  715. package/src/feature-libraries/modular-schema/modularChangeFormatV3.ts +67 -0
  716. package/src/feature-libraries/modular-schema/modularChangeTypes.ts +62 -10
  717. package/src/feature-libraries/optional-field/optionalField.ts +359 -568
  718. package/src/feature-libraries/optional-field/optionalFieldChangeFormatV3.ts +45 -0
  719. package/src/feature-libraries/optional-field/optionalFieldChangeTypes.ts +31 -35
  720. package/src/feature-libraries/optional-field/optionalFieldCodecV2.ts +89 -35
  721. package/src/feature-libraries/optional-field/optionalFieldCodecV3.ts +94 -0
  722. package/src/feature-libraries/optional-field/optionalFieldCodecs.ts +5 -1
  723. package/src/feature-libraries/optional-field/requiredField.ts +15 -2
  724. package/src/feature-libraries/sequence-field/compose.ts +137 -522
  725. package/src/feature-libraries/sequence-field/helperTypes.ts +34 -19
  726. package/src/feature-libraries/sequence-field/invert.ts +102 -228
  727. package/src/feature-libraries/sequence-field/markQueue.ts +2 -2
  728. package/src/feature-libraries/sequence-field/moveEffectTable.ts +8 -195
  729. package/src/feature-libraries/sequence-field/rebase.ts +171 -207
  730. package/src/feature-libraries/sequence-field/replaceRevisions.ts +26 -52
  731. package/src/feature-libraries/sequence-field/sequenceFieldChangeHandler.ts +8 -3
  732. package/src/feature-libraries/sequence-field/sequenceFieldCodecV2.ts +677 -229
  733. package/src/feature-libraries/sequence-field/sequenceFieldCodecV3.ts +56 -70
  734. package/src/feature-libraries/sequence-field/sequenceFieldEditor.ts +28 -30
  735. package/src/feature-libraries/sequence-field/sequenceFieldToDelta.ts +21 -131
  736. package/src/feature-libraries/sequence-field/types.ts +40 -79
  737. package/src/feature-libraries/sequence-field/utils.ts +211 -370
  738. package/src/packageVersion.ts +1 -1
  739. package/src/shared-tree/independentView.ts +12 -6
  740. package/src/shared-tree/index.ts +3 -2
  741. package/src/shared-tree/schematizeTree.ts +21 -8
  742. package/src/shared-tree/schematizingTreeView.ts +38 -68
  743. package/src/shared-tree/sharedTree.ts +30 -15
  744. package/src/shared-tree/sharedTreeChangeCodecs.ts +8 -0
  745. package/src/shared-tree/sharedTreeChangeFamily.ts +7 -4
  746. package/src/shared-tree/sharedTreeEditBuilder.ts +43 -8
  747. package/src/shared-tree/treeCheckout.ts +93 -17
  748. package/src/shared-tree-core/branch.ts +8 -2
  749. package/src/shared-tree-core/editManager.ts +16 -2
  750. package/src/shared-tree-core/editManagerCodecs.ts +11 -1
  751. package/src/shared-tree-core/editManagerFormatCommons.ts +6 -0
  752. package/src/shared-tree-core/editManagerFormatV1toV4.ts +3 -1
  753. package/src/shared-tree-core/index.ts +2 -0
  754. package/src/shared-tree-core/messageCodecV1ToV4.ts +2 -1
  755. package/src/shared-tree-core/messageCodecs.ts +11 -1
  756. package/src/shared-tree-core/messageFormat.ts +6 -0
  757. package/src/shared-tree-core/messageFormatV1ToV4.ts +3 -1
  758. package/src/shared-tree-core/sharedTreeCore.ts +4 -1
  759. package/src/simple-tree/core/unhydratedFlexTree.ts +82 -35
  760. package/src/simple-tree/fieldSchema.ts +6 -4
  761. package/src/simple-tree/index.ts +2 -1
  762. package/src/simple-tree/node-kinds/array/arrayNode.ts +7 -5
  763. package/src/simple-tree/node-kinds/common.ts +2 -5
  764. package/src/simple-tree/node-kinds/map/mapNode.ts +4 -4
  765. package/src/simple-tree/node-kinds/object/objectNode.ts +26 -26
  766. package/src/simple-tree/node-kinds/record/recordNode.ts +10 -9
  767. package/src/simple-tree/prepareForInsertion.ts +342 -200
  768. package/src/simple-tree/unhydratedFlexTreeFromInsertable.ts +35 -15
  769. package/src/treeFactory.ts +16 -4
  770. package/src/util/index.ts +3 -0
  771. package/src/util/rangeMap.ts +68 -26
  772. package/dist/feature-libraries/sequence-field/relevantRemovedRoots.d.ts +0 -9
  773. package/dist/feature-libraries/sequence-field/relevantRemovedRoots.d.ts.map +0 -1
  774. package/dist/feature-libraries/sequence-field/relevantRemovedRoots.js +0 -50
  775. package/dist/feature-libraries/sequence-field/relevantRemovedRoots.js.map +0 -1
  776. package/docs/main/sequence-field/move-composition.md +0 -46
  777. package/lib/feature-libraries/sequence-field/relevantRemovedRoots.d.ts +0 -9
  778. package/lib/feature-libraries/sequence-field/relevantRemovedRoots.d.ts.map +0 -1
  779. package/lib/feature-libraries/sequence-field/relevantRemovedRoots.js +0 -46
  780. package/lib/feature-libraries/sequence-field/relevantRemovedRoots.js.map +0 -1
  781. package/src/feature-libraries/sequence-field/relevantRemovedRoots.ts +0 -57
@@ -7,13 +7,13 @@ import { UsageError } from "@fluidframework/telemetry-utils/internal";
7
7
  import { BTree } from "@tylerbu/sorted-btree-es6";
8
8
  import { lt } from "semver-ts";
9
9
  import { FluidClientVersion, } from "../../codec/index.js";
10
- import { EditBuilder, makeDetachedNodeId, revisionMetadataSourceFromInfo, areEqualChangeAtomIds, areEqualChangeAtomIdOpts, tagChange, makeAnonChange, newChangeAtomIdRangeMap, mapTaggedChange, comparePartialRevisions, comparePartialChangesetLocalIds, } from "../../core/index.js";
11
- import { brand, idAllocatorFromMaxId, idAllocatorFromState, getOrCreate, mergeTupleBTrees, RangeMap, balancedReduce, newTupleBTree, compareStrings, createTupleComparator, } from "../../util/index.js";
12
- import { getFromChangeAtomIdMap, newChangeAtomIdBTree, setInChangeAtomIdMap, } from "../changeAtomIdBTree.js";
13
- import { CrossFieldTarget, getFirstFromCrossFieldMap, setInCrossFieldMap, } from "./crossFieldQueries.js";
10
+ import { EditBuilder, makeDetachedNodeId, revisionMetadataSourceFromInfo, areEqualChangeAtomIds, areEqualChangeAtomIdOpts, tagChange, makeAnonChange, mapTaggedChange, newChangeAtomIdRangeMap, newChangeAtomIdTransform, offsetChangeAtomId, isDetachedUpPathRoot, subtractChangeAtomIds, makeChangeAtomId, comparePartialRevisions, comparePartialChangesetLocalIds, } from "../../core/index.js";
11
+ import { brand, idAllocatorFromMaxId, idAllocatorFromState, getOrCreate, mergeTupleBTrees, RangeMap, balancedReduce, compareStrings, createTupleComparator, newTupleBTree, } from "../../util/index.js";
12
+ import { getFromChangeAtomIdMap, rangeQueryChangeAtomIdMap, newChangeAtomIdBTree, setInChangeAtomIdMap, } from "../changeAtomIdBTree.js";
13
+ import { NodeMoveType, setInCrossFieldMap, } from "./crossFieldQueries.js";
14
14
  import { NodeAttachState, } from "./fieldChangeHandler.js";
15
15
  import { convertGenericChange, genericFieldKind } from "./genericFieldKind.js";
16
- import { newCrossFieldKeyTable, } from "./modularChangeTypes.js";
16
+ import { newCrossFieldRangeTable, } from "./modularChangeTypes.js";
17
17
  /**
18
18
  * Implementation of ChangeFamily which delegates work in a given field to the appropriate FieldKind
19
19
  * as determined by the schema.
@@ -69,21 +69,23 @@ export class ModularChangeFamily {
69
69
  return convertedChange;
70
70
  }
71
71
  compose(changes) {
72
- const { revInfos, maxId } = getRevInfoFromTaggedChanges(changes);
72
+ const { maxId } = getRevInfoFromTaggedChanges(changes);
73
73
  const idState = { maxId };
74
74
  const pairwiseDelegate = (left, right) => {
75
- return this.composePair(left, right, revInfos, idState);
75
+ return this.composePair(left, right, idState);
76
76
  };
77
77
  const innerChanges = changes.map((change) => change.change);
78
78
  return balancedReduce(innerChanges, pairwiseDelegate, makeModularChangeset);
79
79
  }
80
- composePair(change1, change2, revInfos, idState) {
81
- const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys } = this.composeAllFields(change1, change2, revInfos, idState);
80
+ composePair(change1, change2, idState) {
81
+ const revInfos = composeRevInfos(change1.revisions, change2.revisions);
82
+ const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys, rootNodes } = this.composeAllFields(change1, change2, revInfos, idState);
82
83
  const { allBuilds, allDestroys, allRefreshers } = composeBuildsDestroysAndRefreshers(change1, change2);
83
84
  // The composed changeset has a "no change" constraint if either change has one
84
85
  const noChangeConstraint = change1.noChangeConstraint ?? change2.noChangeConstraint;
85
86
  const noChangeConstraintOnRevert = change1.noChangeConstraintOnRevert ?? change2.noChangeConstraintOnRevert;
86
- return makeModularChangeset({
87
+ const composed = makeModularChangeset({
88
+ rebaseVersion: Math.max(change1.rebaseVersion, change2.rebaseVersion),
87
89
  fieldChanges,
88
90
  nodeChanges,
89
91
  nodeToParent,
@@ -91,12 +93,16 @@ export class ModularChangeFamily {
91
93
  crossFieldKeys,
92
94
  maxId: idState.maxId,
93
95
  revisions: revInfos,
94
- noChangeConstraint,
95
- noChangeConstraintOnRevert,
96
+ rootNodes,
96
97
  builds: allBuilds,
97
98
  destroys: allDestroys,
98
99
  refreshers: allRefreshers,
100
+ noChangeConstraint,
101
+ noChangeConstraintOnRevert,
99
102
  });
103
+ // XXX: This is an expensive assert which should be disabled before merging.
104
+ validateChangeset(composed, this.fieldKinds);
105
+ return composed;
100
106
  }
101
107
  composeAllFields(potentiallyConflictedChange1, potentiallyConflictedChange2, revInfos, idState) {
102
108
  // Our current cell ordering scheme in sequences depends on being able to rebase over a change with conflicts.
@@ -117,34 +123,50 @@ export class ModularChangeFamily {
117
123
  const composedNodeChanges = brand(mergeTupleBTrees(change1.nodeChanges, change2.nodeChanges));
118
124
  const composedNodeToParent = brand(mergeTupleBTrees(change1.nodeToParent, change2.nodeToParent));
119
125
  const composedNodeAliases = brand(mergeTupleBTrees(change1.nodeAliases, change2.nodeAliases));
120
- const crossFieldTable = newComposeTable(change1, change2, composedNodeToParent);
126
+ const pendingCompositions = {
127
+ nodeIdsToCompose: [],
128
+ affectedBaseFields: newFieldIdKeyBTree(),
129
+ };
130
+ const movedCrossFieldKeys = newCrossFieldRangeTable();
131
+ const removedCrossFieldKeys = newCrossFieldRangeTable();
132
+ const composedRoots = composeRootTables(change1, change2, composedNodeToParent, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions);
133
+ const crossFieldTable = newComposeTable(change1, change2, composedRoots, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions);
121
134
  const composedFields = this.composeFieldMaps(change1.fieldChanges, change2.fieldChanges, undefined, genId, crossFieldTable, revisionMetadata);
122
135
  this.composeInvalidatedElements(crossFieldTable, composedFields, composedNodeChanges, composedNodeToParent, composedNodeAliases, genId, revisionMetadata);
123
- // Currently no field kinds require making changes to cross-field keys during composition, so we can just merge the two tables.
124
- const composedCrossFieldKeys = RangeMap.union(change1.crossFieldKeys, change2.crossFieldKeys);
136
+ for (const entry of crossFieldTable.renamesToDelete.entries()) {
137
+ deleteNodeRenameFrom(crossFieldTable.composedRootNodes, entry.start, entry.length);
138
+ }
139
+ for (const [nodeId, location] of crossFieldTable.movedNodeToParent.entries()) {
140
+ // Moved nodes are from change2.
141
+ // If there is a corresponding node in change1, then composedNodeToParent will already have the correct entry,
142
+ // because the location of the node is the same in change1 and the composed change
143
+ // (since they have the same input context).
144
+ if (crossFieldTable.newToBaseNodeId.get(nodeId) === undefined) {
145
+ composedNodeToParent.set(nodeId, location);
146
+ }
147
+ }
125
148
  return {
126
149
  fieldChanges: composedFields,
127
150
  nodeChanges: composedNodeChanges,
128
151
  nodeToParent: composedNodeToParent,
129
152
  nodeAliases: composedNodeAliases,
130
- crossFieldKeys: composedCrossFieldKeys,
153
+ crossFieldKeys: composeCrossFieldKeyTables(change1.crossFieldKeys, change2.crossFieldKeys, crossFieldTable.movedCrossFieldKeys, crossFieldTable.removedCrossFieldKeys),
154
+ rootNodes: composedRoots,
131
155
  };
132
156
  }
133
157
  composeInvalidatedField(fieldChange, crossFieldTable, genId, revisionMetadata) {
134
158
  const context = crossFieldTable.fieldToContext.get(fieldChange);
135
159
  assert(context !== undefined, 0x8cc /* Should have context for every invalidated field */);
136
- const { fieldId, change1: fieldChange1, change2: fieldChange2, composedChange } = context;
160
+ const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
161
+ crossFieldTable.pendingCompositions.affectedBaseFields.delete(fieldIdKeyFromFieldId(context.fieldId));
137
162
  const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
138
163
  const composeNodes = (child1, child2) => {
139
- if (child1 !== undefined &&
140
- child2 !== undefined &&
141
- getFromChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2) === undefined) {
142
- setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
143
- crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
164
+ if (child1 !== undefined && child2 !== undefined) {
165
+ addNodesToCompose(crossFieldTable, child1, child2);
144
166
  }
145
167
  return child1 ?? child2 ?? fail(0xb22 /* Should not compose two undefined nodes */);
146
168
  };
147
- const amendedChange = rebaser.compose(fieldChange1, fieldChange2, composeNodes, genId, new ComposeManager(crossFieldTable, fieldChange, fieldId, false), revisionMetadata);
169
+ const amendedChange = rebaser.compose(fieldChange1, fieldChange2, composeNodes, genId, new ComposeNodeManagerI(crossFieldTable, context.fieldId, false), revisionMetadata);
148
170
  composedChange.change = brand(amendedChange);
149
171
  }
150
172
  /**
@@ -153,32 +175,23 @@ export class ModularChangeFamily {
153
175
  * - discovering that two node changesets refer to the same node (`nodeIdsToCompose`)
154
176
  * - a previously composed field being invalidated by a cross field effect (`invalidatedFields`)
155
177
  * - a field which was copied directly from an input changeset being invalidated by a cross field effect
156
- * (`affectedBaseFields` and `affectedNewFields`)
178
+ * (`affectedBaseFields`)
157
179
  *
158
180
  * Updating an element may invalidate further elements. This function runs until there is no more invalidation.
159
181
  */
160
182
  composeInvalidatedElements(table, composedFields, composedNodes, composedNodeToParent, nodeAliases, genId, metadata) {
161
183
  const pending = table.pendingCompositions;
162
- while (table.invalidatedFields.size > 0 ||
163
- pending.nodeIdsToCompose.length > 0 ||
164
- pending.affectedBaseFields.length > 0 ||
165
- pending.affectedNewFields.length > 0) {
166
- // Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
167
- for (const [id1, id2] of pending.nodeIdsToCompose) {
168
- this.composeNodesById(table.baseChange.nodeChanges, table.newChange.nodeChanges, composedNodes, composedNodeToParent, nodeAliases, id1, id2, genId, table, metadata);
169
- }
170
- pending.nodeIdsToCompose.length = 0;
171
- this.composeAffectedFields(table, table.baseChange, true, pending.affectedBaseFields, composedFields, composedNodes, genId, metadata);
172
- this.composeAffectedFields(table, table.newChange, false, pending.affectedNewFields, composedFields, composedNodes, genId, metadata);
173
- this.processInvalidatedCompositions(table, genId, metadata);
184
+ while (pending.nodeIdsToCompose.length > 0 || pending.affectedBaseFields.length > 0) {
185
+ this.processPendingNodeCompositions(table, composedNodes, composedNodeToParent, nodeAliases, genId, metadata);
186
+ this.composeAffectedFields(table, table.baseChange, pending.affectedBaseFields, composedFields, composedNodes, genId, metadata);
174
187
  }
175
188
  }
176
- processInvalidatedCompositions(table, genId, metadata) {
177
- const fieldsToUpdate = table.invalidatedFields;
178
- table.invalidatedFields = new Set();
179
- for (const fieldChange of fieldsToUpdate) {
180
- this.composeInvalidatedField(fieldChange, table, genId, metadata);
189
+ processPendingNodeCompositions(table, composedNodes, composedNodeToParent, nodeAliases, genId, metadata) {
190
+ // Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
191
+ for (const [id1, id2] of table.pendingCompositions.nodeIdsToCompose) {
192
+ this.composeNodesById(table.baseChange, table.newChange, composedNodes, composedNodeToParent, nodeAliases, id1, id2, genId, table, metadata);
181
193
  }
194
+ table.pendingCompositions.nodeIdsToCompose.length = 0;
182
195
  }
183
196
  /**
184
197
  * Ensures that each field in `affectedFields` has been updated in the composition output.
@@ -191,39 +204,34 @@ export class ModularChangeFamily {
191
204
  * If not, they are assumed to be part of the new changeset.
192
205
  * @param affectedFields - The set of fields to process.
193
206
  */
194
- composeAffectedFields(table, change, areBaseFields, affectedFields, composedFields, composedNodes, genId, metadata) {
195
- for (const fieldIdKey of affectedFields.keys()) {
196
- const fieldId = normalizeFieldId(fieldIdFromFieldIdKey(fieldIdKey), change.nodeAliases);
197
- const fieldChange = fieldChangeFromId(change.fieldChanges, change.nodeChanges, fieldId);
207
+ composeAffectedFields(table, change, affectedFields, composedFields, composedNodes, genId, metadata) {
208
+ const fieldsToProcess = affectedFields.clone();
209
+ affectedFields.clear();
210
+ for (const fieldIdKey of fieldsToProcess.keys()) {
211
+ const fieldId = fieldIdFromFieldIdKey(fieldIdKey);
212
+ const fieldChange = fieldChangeFromId(change, fieldId);
198
213
  if (table.fieldToContext.has(fieldChange) ||
199
214
  table.newFieldToBaseField.has(fieldChange)) {
200
- // This function handles fields which were not part of the intersection of the two changesets but which need to be updated anyway.
201
- // If we've already processed this field then either it is up to date
202
- // or there is pending inval which will be handled in processInvalidatedCompositions.
203
- continue;
204
- }
205
- const emptyChange = this.createEmptyFieldChange(fieldChange.fieldKind);
206
- const [change1, change2] = areBaseFields
207
- ? [fieldChange, emptyChange]
208
- : [emptyChange, fieldChange];
209
- const composedField = this.composeFieldChanges(fieldId, change1, change2, genId, table, metadata);
210
- if (fieldId.nodeId === undefined) {
211
- composedFields.set(fieldId.field, composedField);
212
- continue;
215
+ this.composeInvalidatedField(fieldChange, table, genId, metadata);
213
216
  }
214
- const nodeId = getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId;
215
- let nodeChangeset = nodeChangeFromId(composedNodes, nodeId);
216
- if (!table.composedNodes.has(nodeChangeset)) {
217
- nodeChangeset = cloneNodeChangeset(nodeChangeset);
218
- setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
219
- }
220
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy
221
- if (nodeChangeset.fieldChanges === undefined) {
222
- nodeChangeset.fieldChanges = new Map();
217
+ else {
218
+ this.composeFieldWithNoNewChange(table, fieldChange, fieldId, composedFields, composedNodes, genId, metadata);
223
219
  }
224
- nodeChangeset.fieldChanges.set(fieldId.field, composedField);
225
220
  }
226
- affectedFields.clear();
221
+ }
222
+ composeFieldWithNoNewChange(table, baseFieldChange, fieldId, composedFields, composedNodes, genId, metadata) {
223
+ const emptyChange = this.createEmptyFieldChange(baseFieldChange.fieldKind);
224
+ const composedField = this.composeFieldChanges(fieldId, baseFieldChange, emptyChange, genId, table, metadata);
225
+ if (fieldId.nodeId === undefined) {
226
+ composedFields.set(fieldId.field, composedField);
227
+ return;
228
+ }
229
+ const nodeId = normalizeNodeId(getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId, table.baseChange.nodeAliases);
230
+ // We clone the node changeset before mutating it, as it may be from one of the input changesets.
231
+ const nodeChangeset = cloneNodeChangeset(nodeChangeFromId(composedNodes, table.baseChange.nodeAliases, nodeId));
232
+ setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
233
+ nodeChangeset.fieldChanges ??= new Map();
234
+ nodeChangeset.fieldChanges.set(fieldId.field, composedField);
227
235
  }
228
236
  composeFieldMaps(change1, change2, parentId, genId, crossFieldTable, revisionMetadata) {
229
237
  const composedFields = new Map();
@@ -233,6 +241,14 @@ export class ModularChangeFamily {
233
241
  for (const [field, fieldChange1] of change1) {
234
242
  const fieldId = { nodeId: parentId, field };
235
243
  const fieldChange2 = change2.get(field);
244
+ const cachedComposedFieldChange = crossFieldTable.fieldToContext.get(fieldChange1)?.composedChange;
245
+ if (fieldChange2 === undefined && cachedComposedFieldChange !== undefined) {
246
+ // This can happen if the field was previous processed in `composeFieldWithNoNewChange`.
247
+ // If `change2` does not have a change for this field, then without this check we would
248
+ // lose the composed field change and instead simply have `change1`'s change.
249
+ composedFields.set(field, cachedComposedFieldChange);
250
+ continue;
251
+ }
236
252
  const composedField = fieldChange2 === undefined
237
253
  ? fieldChange1
238
254
  : this.composeFieldChanges(fieldId, fieldChange1, fieldChange2, genId, crossFieldTable, revisionMetadata);
@@ -252,17 +268,16 @@ export class ModularChangeFamily {
252
268
  * will be added to `crossFieldTable.pendingCompositions.nodeIdsToCompose`.
253
269
  *
254
270
  * Any fields which had cross-field information sent to them as part of this field composition
255
- * will be added to either `affectedBaseFields` or `affectedNewFields` in `crossFieldTable.pendingCompositions`.
271
+ * will be added to `affectedBaseFields` in `crossFieldTable.pendingCompositions`.
256
272
  *
257
273
  * Any composed `FieldChange` which is invalidated by new cross-field information will be added to `crossFieldTable.invalidatedFields`.
258
274
  */
259
275
  composeFieldChanges(fieldId, change1, change2, idAllocator, crossFieldTable, revisionMetadata) {
260
276
  const { fieldKind, changeHandler, change1: change1Normalized, change2: change2Normalized, } = this.normalizeFieldChanges(change1, change2);
261
- const manager = new ComposeManager(crossFieldTable, change1, fieldId);
277
+ const manager = new ComposeNodeManagerI(crossFieldTable, fieldId);
262
278
  const composedChange = changeHandler.rebaser.compose(change1Normalized, change2Normalized, (child1, child2) => {
263
279
  if (child1 !== undefined && child2 !== undefined) {
264
- setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
265
- crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
280
+ addNodesToCompose(crossFieldTable, child1, child2);
266
281
  }
267
282
  return child1 ?? child2 ?? fail(0xb23 /* Should not compose two undefined nodes */);
268
283
  }, idAllocator, manager, revisionMetadata);
@@ -279,19 +294,18 @@ export class ModularChangeFamily {
279
294
  crossFieldTable.newFieldToBaseField.set(change2, change1);
280
295
  return composedField;
281
296
  }
282
- composeNodesById(nodeChanges1, nodeChanges2, composedNodes, composedNodeToParent, nodeAliases, id1, id2, idAllocator, crossFieldTable, revisionMetadata) {
283
- const nodeChangeset1 = nodeChangeFromId(nodeChanges1, id1);
284
- const nodeChangeset2 = nodeChangeFromId(nodeChanges2, id2);
297
+ composeNodesById(change1, change2, composedNodes, composedNodeToParent, composedAliases, id1, id2, idAllocator, crossFieldTable, revisionMetadata) {
298
+ const nodeChangeset1 = nodeChangeFromId(change1.nodeChanges, change1.nodeAliases, id1);
299
+ const nodeChangeset2 = nodeChangeFromId(change2.nodeChanges, change2.nodeAliases, id2);
285
300
  const composedNodeChangeset = this.composeNodeChanges(id1, nodeChangeset1, nodeChangeset2, idAllocator, crossFieldTable, revisionMetadata);
286
301
  setInChangeAtomIdMap(composedNodes, id1, composedNodeChangeset);
287
302
  if (!areEqualChangeAtomIds(id1, id2)) {
288
303
  composedNodes.delete([id2.revision, id2.localId]);
289
304
  composedNodeToParent.delete([id2.revision, id2.localId]);
290
- setInChangeAtomIdMap(nodeAliases, id2, id1);
305
+ setInChangeAtomIdMap(composedAliases, id2, id1);
291
306
  // We need to delete id1 to avoid forming a cycle in case id1 already had an alias.
292
- nodeAliases.delete([id1.revision, id1.localId]);
307
+ composedAliases.delete([id1.revision, id1.localId]);
293
308
  }
294
- crossFieldTable.composedNodes.add(composedNodeChangeset);
295
309
  }
296
310
  composeNodeChanges(nodeId, change1, change2, genId, crossFieldTable, revisionMetadata) {
297
311
  // WARNING: this composition logic assumes that we never make compositions of the following form:
@@ -337,16 +351,24 @@ export class ModularChangeFamily {
337
351
  const noChangeConstraintOnRevert = change.change.noChangeConstraint;
338
352
  if (hasConflicts(change.change)) {
339
353
  return makeModularChangeset({
354
+ rebaseVersion: change.change.rebaseVersion,
340
355
  maxId: change.change.maxId,
341
356
  revisions: revInfos,
342
357
  destroys,
343
358
  });
344
359
  }
345
360
  const genId = idAllocatorFromMaxId(change.change.maxId ?? -1);
361
+ const invertedNodeToParent = brand(change.change.nodeToParent.clone());
346
362
  const crossFieldTable = {
347
- ...newCrossFieldTable(),
363
+ change: change.change,
364
+ isRollback,
365
+ entries: newChangeAtomIdRangeMap(),
348
366
  originalFieldToContext: new Map(),
349
- invertedNodeToParent: brand(change.change.nodeToParent.clone()),
367
+ invertRevision: revisionForInvert,
368
+ invertedNodeToParent,
369
+ invalidatedFields: new Set(),
370
+ invertedRoots: invertRootTable(change.change, invertedNodeToParent, isRollback),
371
+ attachToDetachId: newChangeAtomIdTransform(),
350
372
  };
351
373
  const { revInfos: oldRevInfos } = getRevInfoFromTaggedChanges([change]);
352
374
  const revisionMetadata = revisionMetadataSourceFromInfo(oldRevInfos);
@@ -362,16 +384,19 @@ export class ModularChangeFamily {
362
384
  const originalFieldChange = fieldChange.change;
363
385
  const context = crossFieldTable.originalFieldToContext.get(fieldChange);
364
386
  assert(context !== undefined, 0x851 /* Should have context for every invalidated field */);
365
- const { invertedField, fieldId } = context;
366
- const amendedChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.invert(originalFieldChange, isRollback, genId, revisionForInvert, new InvertManager(crossFieldTable, fieldChange, fieldId), revisionMetadata);
387
+ const { invertedField } = context;
388
+ const amendedChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.invert(originalFieldChange, isRollback, genId, revisionForInvert, new InvertNodeManagerI(crossFieldTable, context.fieldId), revisionMetadata);
367
389
  invertedField.change = brand(amendedChange);
368
390
  }
369
391
  }
370
392
  const crossFieldKeys = this.makeCrossFieldKeyTable(invertedFields, invertedNodes);
393
+ this.processInvertRenames(crossFieldTable);
371
394
  return makeModularChangeset({
395
+ rebaseVersion: change.change.rebaseVersion,
372
396
  fieldChanges: invertedFields,
373
397
  nodeChanges: invertedNodes,
374
398
  nodeToParent: crossFieldTable.invertedNodeToParent,
399
+ rootNodes: crossFieldTable.invertedRoots,
375
400
  nodeAliases: change.change.nodeAliases,
376
401
  crossFieldKeys,
377
402
  maxId: genId.getMaxId(),
@@ -387,7 +412,7 @@ export class ModularChangeFamily {
387
412
  const invertedFields = new Map();
388
413
  for (const [field, fieldChange] of changes) {
389
414
  const fieldId = { nodeId: parentId, field };
390
- const manager = new InvertManager(crossFieldTable, fieldChange, fieldId);
415
+ const manager = new InvertNodeManagerI(crossFieldTable, fieldId);
391
416
  const invertedChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.invert(fieldChange.change, isRollback, genId, revisionForInvert, manager, revisionMetadata);
392
417
  const invertedFieldChange = {
393
418
  ...fieldChange,
@@ -419,6 +444,12 @@ export class ModularChangeFamily {
419
444
  }
420
445
  return inverse;
421
446
  }
447
+ processInvertRenames(table) {
448
+ for (const { start: newAttachId, value: originalDetachId, length, } of table.attachToDetachId.entries()) {
449
+ // Note that the detach location is already set in `invertDetach`.
450
+ addNodeRename(table.invertedRoots, originalDetachId, newAttachId, length, undefined);
451
+ }
452
+ }
422
453
  rebase(taggedChange, potentiallyConflictedOver, revisionMetadata) {
423
454
  // Our current cell ordering scheme in sequences depends on being able to rebase over a change with conflicts.
424
455
  // This means that we must rebase over a muted version of the conflicted changeset.
@@ -429,17 +460,26 @@ export class ModularChangeFamily {
429
460
  const maxId = Math.max(change.maxId ?? -1, over.change.maxId ?? -1);
430
461
  const idState = { maxId };
431
462
  const genId = idAllocatorFromState(idState);
463
+ const affectedBaseFields = newFieldIdKeyBTree();
464
+ const nodesToRebase = [];
465
+ const rebasedNodeToParent = brand(change.nodeToParent.clone());
466
+ const rebaseVersion = Math.max(change.rebaseVersion, over.change.rebaseVersion);
467
+ const rebasedRootNodes = rebaseRoots(change, over.change, affectedBaseFields, nodesToRebase, rebasedNodeToParent, rebaseVersion);
432
468
  const crossFieldTable = {
433
- ...newCrossFieldTable(),
469
+ rebaseVersion,
470
+ entries: newDetachedEntryMap(),
434
471
  newChange: change,
435
472
  baseChange: over.change,
436
473
  baseFieldToContext: new Map(),
474
+ baseRoots: over.change.rootNodes,
475
+ rebasedRootNodes,
437
476
  baseToRebasedNodeId: newChangeAtomIdBTree(),
438
477
  rebasedFields: new Set(),
439
- rebasedNodeToParent: brand(change.nodeToParent.clone()),
440
- rebasedCrossFieldKeys: change.crossFieldKeys.clone(),
478
+ rebasedNodeToParent,
479
+ rebasedDetachLocations: newChangeAtomIdRangeMap(),
480
+ movedDetaches: newChangeAtomIdRangeMap(),
441
481
  nodeIdPairs: [],
442
- affectedBaseFields: newFieldIdKeyBTree(),
482
+ affectedBaseFields,
443
483
  fieldsWithUnattachedChild: new Set(),
444
484
  };
445
485
  const getBaseRevisions = () => revisionInfoFromTaggedChange(over).map((info) => info.revision);
@@ -449,8 +489,9 @@ export class ModularChangeFamily {
449
489
  getBaseRevisions,
450
490
  };
451
491
  const rebasedNodes = brand(change.nodeChanges.clone());
452
- const rebasedFields = this.rebaseIntersectingFields(crossFieldTable, rebasedNodes, genId, rebaseMetadata);
453
- this.rebaseInvalidatedElements(rebasedFields, rebasedNodes, crossFieldTable, rebaseMetadata, genId);
492
+ const rebasedFields = this.rebaseIntersectingFields(nodesToRebase, crossFieldTable, rebasedNodes, genId, rebaseMetadata);
493
+ this.rebaseInvalidatedFields(rebasedFields, rebasedNodes, crossFieldTable, rebaseMetadata, genId);
494
+ fixupRebasedDetachLocations(crossFieldTable);
454
495
  const constraintState = newConstraintState(change.constraintViolationCount ?? 0);
455
496
  const revertConstraintState = newConstraintState(change.constraintViolationCountOnRevert ?? 0);
456
497
  let noChangeConstraint = change.noChangeConstraint;
@@ -458,13 +499,17 @@ export class ModularChangeFamily {
458
499
  noChangeConstraint = { violated: true };
459
500
  constraintState.violationCount += 1;
460
501
  }
461
- this.updateConstraintsForFields(rebasedFields, NodeAttachState.Attached, NodeAttachState.Attached, constraintState, revertConstraintState, rebasedNodes);
502
+ this.updateConstraints(rebasedFields, rebasedNodes, rebasedRootNodes, constraintState, revertConstraintState);
503
+ const fieldsWithRootMoves = getFieldsWithRootMoves(crossFieldTable.rebasedRootNodes, change.nodeAliases);
504
+ const fieldToRootChanges = getFieldToRootChanges(crossFieldTable.rebasedRootNodes, change.nodeAliases);
462
505
  const rebased = makeModularChangeset({
463
- fieldChanges: this.pruneFieldMap(rebasedFields, rebasedNodes),
506
+ fieldChanges: this.pruneFieldMap(rebasedFields, undefined, rebasedNodes, crossFieldTable.rebasedNodeToParent, change.nodeAliases, crossFieldTable.rebasedRootNodes, fieldsWithRootMoves, fieldToRootChanges),
464
507
  nodeChanges: rebasedNodes,
465
508
  nodeToParent: crossFieldTable.rebasedNodeToParent,
509
+ rootNodes: this.pruneRoots(crossFieldTable.rebasedRootNodes, rebasedNodes, crossFieldTable.rebasedNodeToParent, change.nodeAliases, fieldsWithRootMoves, fieldToRootChanges),
510
+ // TODO: Do we need to include aliases for node changesets added during rebasing?
466
511
  nodeAliases: change.nodeAliases,
467
- crossFieldKeys: crossFieldTable.rebasedCrossFieldKeys,
512
+ crossFieldKeys: rebaseCrossFieldKeys(change.crossFieldKeys, crossFieldTable.movedDetaches, crossFieldTable.rebasedDetachLocations),
468
513
  maxId: idState.maxId,
469
514
  revisions: change.revisions,
470
515
  constraintViolationCount: constraintState.violationCount,
@@ -474,15 +519,22 @@ export class ModularChangeFamily {
474
519
  builds: change.builds,
475
520
  destroys: change.destroys,
476
521
  refreshers: change.refreshers,
522
+ rebaseVersion,
477
523
  });
524
+ // XXX: This is an expensive assert which should be disabled before merging.
525
+ validateChangeset(rebased, this.fieldKinds);
478
526
  return rebased;
479
527
  }
480
528
  // This performs a first pass on all fields which have both new and base changes.
481
529
  // TODO: Can we also handle additional passes in this method?
482
- rebaseIntersectingFields(crossFieldTable, rebasedNodes, genId, metadata) {
530
+ rebaseIntersectingFields(rootChanges, crossFieldTable, rebasedNodes, genId, metadata) {
483
531
  const change = crossFieldTable.newChange;
484
532
  const baseChange = crossFieldTable.baseChange;
485
533
  const rebasedFields = this.rebaseFieldMap(change.fieldChanges, baseChange.fieldChanges, undefined, genId, crossFieldTable, metadata);
534
+ for (const [newChildChange, baseChildChange] of rootChanges) {
535
+ const rebasedNode = this.rebaseNodeChange(newChildChange, baseChildChange, genId, crossFieldTable, metadata);
536
+ setInChangeAtomIdMap(rebasedNodes, newChildChange, rebasedNode);
537
+ }
486
538
  // This loop processes all fields which have both base and new changes.
487
539
  // Note that the call to `rebaseNodeChange` can add entries to `crossFieldTable.nodeIdPairs`.
488
540
  for (const [newId, baseId, _attachState] of crossFieldTable.nodeIdPairs) {
@@ -491,83 +543,85 @@ export class ModularChangeFamily {
491
543
  }
492
544
  return rebasedFields;
493
545
  }
494
- // This processes fields which have no new changes but have been invalidated by another field.
495
- rebaseFieldsWithoutNewChanges(rebasedFields, rebasedNodes, crossFieldTable, genId, metadata) {
496
- const baseChange = crossFieldTable.baseChange;
497
- for (const [revision, localId, fieldKey] of crossFieldTable.affectedBaseFields.keys()) {
498
- const baseNodeId = localId === undefined
499
- ? undefined
500
- : normalizeNodeId({ revision, localId }, baseChange.nodeAliases);
501
- const baseFieldChange = fieldMapFromNodeId(baseChange.fieldChanges, baseChange.nodeChanges, baseNodeId).get(fieldKey);
502
- assert(baseFieldChange !== undefined, 0x9c2 /* Cross field key registered for empty field */);
503
- if (crossFieldTable.baseFieldToContext.has(baseFieldChange)) {
504
- // This field has already been processed because there were changes to rebase.
505
- continue;
546
+ rebaseFieldWithoutNewChanges(baseFieldChange, baseFieldId, crossFieldTable, rebasedFields, rebasedNodes, genId, metadata,
547
+ /**
548
+ * The ID of a node in `baseFieldChange` which should be included in the rebased field change.
549
+ */
550
+ baseNodeId) {
551
+ // This field has no changes in the new changeset, otherwise it would have been added to
552
+ // `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
553
+ const rebaseChild = (child, baseChild, stateChange) => {
554
+ assert(child === undefined, 0x9c3 /* There should be no new changes in this field */);
555
+ if (baseChild === undefined || baseNodeId === undefined) {
556
+ return undefined;
557
+ }
558
+ return areEqualChangeAtomIds(normalizeNodeId(baseChild, crossFieldTable.baseChange.nodeAliases), baseNodeId)
559
+ ? baseNodeId
560
+ : undefined;
561
+ };
562
+ const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
563
+ const fieldChange = {
564
+ ...baseFieldChange,
565
+ change: brand(handler.createEmpty()),
566
+ };
567
+ const rebasedNodeId = baseFieldId.nodeId === undefined
568
+ ? undefined
569
+ : rebasedNodeIdFromBaseNodeId(crossFieldTable, baseFieldId.nodeId);
570
+ const fieldId = { nodeId: rebasedNodeId, field: baseFieldId.field };
571
+ const rebasedField = handler.rebaser.rebase(fieldChange.change, baseFieldChange.change, rebaseChild, genId, new RebaseNodeManagerI(crossFieldTable, fieldId), metadata, crossFieldTable.rebaseVersion);
572
+ const rebasedFieldChange = {
573
+ ...baseFieldChange,
574
+ change: brand(rebasedField),
575
+ };
576
+ const context = {
577
+ newChange: fieldChange,
578
+ baseChange: baseFieldChange,
579
+ rebasedChange: rebasedFieldChange,
580
+ fieldId,
581
+ baseNodeIds: newChangeAtomIdBTree(),
582
+ };
583
+ if (baseNodeId !== undefined) {
584
+ setInChangeAtomIdMap(context.baseNodeIds, baseNodeId, true);
585
+ }
586
+ crossFieldTable.baseFieldToContext.set(baseFieldChange, context);
587
+ crossFieldTable.rebasedFields.add(rebasedFieldChange);
588
+ this.attachRebasedField(rebasedFields, rebasedNodes, crossFieldTable, rebasedFieldChange, fieldId, genId, metadata);
589
+ }
590
+ rebaseInvalidatedFields(rebasedFields, rebasedNodes, crossFieldTable, rebaseMetadata, genId) {
591
+ while (crossFieldTable.affectedBaseFields.size > 0) {
592
+ const baseFields = crossFieldTable.affectedBaseFields.clone();
593
+ crossFieldTable.affectedBaseFields.clear();
594
+ for (const baseFieldIdKey of baseFields.keys()) {
595
+ const baseFieldId = normalizeFieldId(fieldIdFromFieldIdKey(baseFieldIdKey), crossFieldTable.baseChange.nodeAliases);
596
+ const baseField = fieldChangeFromId(crossFieldTable.baseChange, baseFieldId);
597
+ assert(baseField !== undefined, 0x9c2 /* Cross field key registered for empty field */);
598
+ const context = crossFieldTable.baseFieldToContext.get(baseField);
599
+ if (context === undefined) {
600
+ this.rebaseFieldWithoutNewChanges(baseField, baseFieldId, crossFieldTable, rebasedFields, rebasedNodes, genId, rebaseMetadata);
601
+ }
602
+ else {
603
+ this.rebaseInvalidatedField(baseField, crossFieldTable, context, rebaseMetadata, genId);
604
+ }
506
605
  }
507
- // This field has no changes in the new changeset, otherwise it would have been added to
508
- // `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
509
- const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
510
- const fieldChange = {
511
- ...baseFieldChange,
512
- change: brand(handler.createEmpty()),
513
- };
514
- const rebasedNodeId = baseNodeId === undefined
515
- ? undefined
516
- : rebasedNodeIdFromBaseNodeId(crossFieldTable, baseNodeId);
517
- const fieldId = { nodeId: rebasedNodeId, field: fieldKey };
518
- const rebasedField = handler.rebaser.rebase(fieldChange.change, baseFieldChange.change, noNewChangesRebaseChild, genId, new RebaseManager(crossFieldTable, baseFieldChange, fieldId), metadata);
519
- const rebasedFieldChange = {
520
- ...baseFieldChange,
521
- change: brand(rebasedField),
522
- };
523
- // TODO: Deduplicate
524
- crossFieldTable.baseFieldToContext.set(baseFieldChange, {
525
- newChange: fieldChange,
526
- baseChange: baseFieldChange,
527
- rebasedChange: rebasedFieldChange,
528
- fieldId,
529
- baseNodeIds: [],
530
- });
531
- crossFieldTable.rebasedFields.add(rebasedFieldChange);
532
- this.attachRebasedField(rebasedFields, rebasedNodes, crossFieldTable, rebasedFieldChange, fieldId, genId, metadata);
533
- }
534
- }
535
- rebaseInvalidatedElements(rebasedFields, rebasedNodes, table, metadata, idAllocator) {
536
- this.rebaseFieldsWithoutNewChanges(rebasedFields, rebasedNodes, table, idAllocator, metadata);
537
- this.rebaseFieldsWithUnattachedChild(table, metadata, idAllocator);
538
- this.rebaseInvalidatedFields(table, metadata, idAllocator);
539
- }
540
- rebaseInvalidatedFields(crossFieldTable, rebaseMetadata, genId) {
541
- const fieldsToUpdate = crossFieldTable.invalidatedFields;
542
- crossFieldTable.invalidatedFields = new Set();
543
- for (const field of fieldsToUpdate) {
544
- this.rebaseInvalidatedField(field, crossFieldTable, rebaseMetadata, genId);
545
- }
546
- }
547
- rebaseFieldsWithUnattachedChild(table, metadata, idAllocator) {
548
- for (const field of table.fieldsWithUnattachedChild) {
549
- table.invalidatedFields.delete(field);
550
- this.rebaseInvalidatedField(field, table, metadata, idAllocator, true);
551
606
  }
552
607
  }
553
- rebaseInvalidatedField(baseField, crossFieldTable, rebaseMetadata, genId, allowInval = false) {
554
- const context = crossFieldTable.baseFieldToContext.get(baseField);
555
- assert(context !== undefined, 0x852 /* Every field should have a context */);
608
+ rebaseInvalidatedField(baseField, crossFieldTable, context, rebaseMetadata, genId) {
556
609
  const { changeHandler, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(context.newChange, context.baseChange);
557
610
  const rebaseChild = (curr, base) => {
558
611
  if (curr !== undefined) {
559
612
  return curr;
560
613
  }
561
- if (base !== undefined) {
562
- for (const id of context.baseNodeIds) {
563
- if (areEqualChangeAtomIds(base, id)) {
564
- return base;
565
- }
566
- }
614
+ if (base !== undefined && getFromChangeAtomIdMap(context.baseNodeIds, base) === true) {
615
+ return base;
567
616
  }
568
617
  return undefined;
569
618
  };
570
- context.rebasedChange.change = brand(changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, new RebaseManager(crossFieldTable, baseField, context.fieldId, allowInval), rebaseMetadata));
619
+ let allowInval = false;
620
+ if (crossFieldTable.fieldsWithUnattachedChild.has(baseField)) {
621
+ crossFieldTable.fieldsWithUnattachedChild.delete(baseField);
622
+ allowInval = true;
623
+ }
624
+ context.rebasedChange.change = brand(changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, new RebaseNodeManagerI(crossFieldTable, context.fieldId, allowInval), rebaseMetadata, crossFieldTable.rebaseVersion));
571
625
  }
572
626
  attachRebasedField(rebasedFields, rebasedNodes, table, rebasedField, { nodeId, field: fieldKey }, idAllocator, metadata) {
573
627
  if (nodeId === undefined) {
@@ -576,12 +630,14 @@ export class ModularChangeFamily {
576
630
  }
577
631
  const rebasedNode = getFromChangeAtomIdMap(rebasedNodes, nodeId);
578
632
  if (rebasedNode !== undefined) {
579
- if (rebasedNode.fieldChanges === undefined) {
580
- rebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
633
+ const updatedRebasedNode = cloneNodeChangeset(rebasedNode);
634
+ setInChangeAtomIdMap(rebasedNodes, nodeId, updatedRebasedNode);
635
+ if (updatedRebasedNode.fieldChanges === undefined) {
636
+ updatedRebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
581
637
  return;
582
638
  }
583
- assert(!rebasedNode.fieldChanges.has(fieldKey), 0x9c4 /* Expected an empty field */);
584
- rebasedNode.fieldChanges.set(fieldKey, rebasedField);
639
+ assert(!updatedRebasedNode.fieldChanges.has(fieldKey), 0x9c4 /* Expected an empty field */);
640
+ updatedRebasedNode.fieldChanges.set(fieldKey, rebasedField);
585
641
  return;
586
642
  }
587
643
  const newNode = {
@@ -589,39 +645,51 @@ export class ModularChangeFamily {
589
645
  };
590
646
  setInChangeAtomIdMap(rebasedNodes, nodeId, newNode);
591
647
  setInChangeAtomIdMap(table.baseToRebasedNodeId, nodeId, nodeId);
592
- const parentFieldId = getParentFieldId(table.baseChange, nodeId);
593
- this.attachRebasedNode(rebasedFields, rebasedNodes, table, nodeId, parentFieldId, idAllocator, metadata);
594
- }
595
- attachRebasedNode(rebasedFields, rebasedNodes, table, baseNodeId, parentFieldIdBase, idAllocator, metadata) {
596
- const baseFieldChange = fieldChangeFromId(table.baseChange.fieldChanges, table.baseChange.nodeChanges, parentFieldIdBase);
648
+ const parentBase = getNodeParent(table.baseChange, nodeId);
649
+ this.attachRebasedNode(rebasedFields, rebasedNodes, table, nodeId, parentBase, idAllocator, metadata);
650
+ }
651
+ attachRebasedNode(rebasedFields, rebasedNodes, table, baseNodeId, parentBase, idAllocator, metadata) {
652
+ if (parentBase.root !== undefined) {
653
+ const renamedRoot = firstAttachIdFromDetachId(table.baseChange.rootNodes, parentBase.root, 1).value;
654
+ const attachField = table.baseChange.crossFieldKeys.getFirst({ ...renamedRoot, target: NodeMoveType.Attach }, 1).value;
655
+ if (attachField === undefined) {
656
+ const baseDetachLocation = table.baseChange.rootNodes.detachLocations.getFirst(parentBase.root, 1).value;
657
+ assignRootChange(table.rebasedRootNodes, table.rebasedNodeToParent, renamedRoot, baseNodeId, baseDetachLocation, table.rebaseVersion);
658
+ // We need to make sure the rebased changeset includes the detach location,
659
+ // so we add that field to `affectedBaseFields` unless it's already been processed.
660
+ if (baseDetachLocation !== undefined &&
661
+ !table.baseFieldToContext.has(fieldChangeFromId(table.baseChange, baseDetachLocation))) {
662
+ table.affectedBaseFields.set(fieldIdKeyFromFieldId(baseDetachLocation), true);
663
+ }
664
+ }
665
+ else {
666
+ // The base change inserts this node into `attachField`, so the rebased change should represent this node there.
667
+ const normalizedAttachField = normalizeFieldId(attachField, table.baseChange.nodeAliases);
668
+ const entry = table.entries.getFirst(renamedRoot, 1).value ?? {};
669
+ table.entries.set(renamedRoot, 1, { ...entry, nodeChange: baseNodeId });
670
+ table.affectedBaseFields.set(fieldIdKeyFromFieldId(normalizedAttachField), true);
671
+ this.attachRebasedNode(rebasedFields, rebasedNodes, table, baseNodeId, { field: normalizedAttachField }, idAllocator, metadata);
672
+ }
673
+ return;
674
+ }
675
+ const parentFieldIdBase = parentBase.field;
676
+ const baseFieldChange = fieldChangeFromId(table.baseChange, parentFieldIdBase);
597
677
  const rebasedFieldId = rebasedFieldIdFromBaseId(table, parentFieldIdBase);
598
- setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, rebasedFieldId);
678
+ setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, { field: rebasedFieldId });
599
679
  const context = table.baseFieldToContext.get(baseFieldChange);
600
680
  if (context !== undefined) {
601
681
  // We've already processed this field.
602
- // The new child node will be attached in rebaseFieldsWithUnattachedChild.
603
- context.baseNodeIds.push(baseNodeId);
604
- table.fieldsWithUnattachedChild.add(baseFieldChange);
682
+ // The new child node will be attached in the next pass.
683
+ // Note that adding to `fieldsWithUnattachedChild` allows that field to generate new invalidations,
684
+ // so to avoid invalidation cycles we make sure we only add to it once per new unattached child.
685
+ // This is done by checking whether `context.baseNodeIds` already contained `baseNodeId`.
686
+ if (setInChangeAtomIdMap(context.baseNodeIds, baseNodeId, true)) {
687
+ table.fieldsWithUnattachedChild.add(baseFieldChange);
688
+ table.affectedBaseFields.set(fieldIdKeyFromFieldId(parentFieldIdBase), true);
689
+ }
605
690
  return;
606
691
  }
607
- const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
608
- const fieldChange = {
609
- ...baseFieldChange,
610
- change: brand(handler.createEmpty()),
611
- };
612
- const rebasedChangeset = handler.rebaser.rebase(handler.createEmpty(), baseFieldChange.change, (_idNew, idBase) => idBase !== undefined && areEqualChangeAtomIds(idBase, baseNodeId)
613
- ? baseNodeId
614
- : undefined, idAllocator, new RebaseManager(table, baseFieldChange, rebasedFieldId), metadata);
615
- const rebasedField = { ...baseFieldChange, change: brand(rebasedChangeset) };
616
- table.rebasedFields.add(rebasedField);
617
- table.baseFieldToContext.set(baseFieldChange, {
618
- newChange: fieldChange,
619
- baseChange: baseFieldChange,
620
- rebasedChange: rebasedField,
621
- fieldId: rebasedFieldId,
622
- baseNodeIds: [],
623
- });
624
- this.attachRebasedField(rebasedFields, rebasedNodes, table, rebasedField, rebasedFieldId, idAllocator, metadata);
692
+ this.rebaseFieldWithoutNewChanges(baseFieldChange, parentFieldIdBase, table, rebasedFields, rebasedNodes, idAllocator, metadata, baseNodeId);
625
693
  }
626
694
  rebaseFieldMap(change, over, parentId, genId, crossFieldTable, revisionMetadata) {
627
695
  const rebasedFields = new Map();
@@ -639,8 +707,8 @@ export class ModularChangeFamily {
639
707
  continue;
640
708
  }
641
709
  const { fieldKind, changeHandler, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(fieldChange, baseChange);
642
- const manager = new RebaseManager(crossFieldTable, baseChange, fieldId);
643
- const rebasedField = changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, manager, revisionMetadata);
710
+ const manager = new RebaseNodeManagerI(crossFieldTable, fieldId);
711
+ const rebasedField = changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, manager, revisionMetadata, crossFieldTable.rebaseVersion);
644
712
  const rebasedFieldChange = {
645
713
  fieldKind,
646
714
  change: brand(rebasedField),
@@ -651,15 +719,15 @@ export class ModularChangeFamily {
651
719
  newChange: fieldChange,
652
720
  rebasedChange: rebasedFieldChange,
653
721
  fieldId,
654
- baseNodeIds: [],
722
+ baseNodeIds: newChangeAtomIdBTree(),
655
723
  });
656
724
  crossFieldTable.rebasedFields.add(rebasedFieldChange);
657
725
  }
658
726
  return rebasedFields;
659
727
  }
660
728
  rebaseNodeChange(newId, baseId, genId, crossFieldTable, revisionMetadata) {
661
- const change = nodeChangeFromId(crossFieldTable.newChange.nodeChanges, newId);
662
- const over = nodeChangeFromId(crossFieldTable.baseChange.nodeChanges, baseId);
729
+ const change = nodeChangeFromId(crossFieldTable.newChange.nodeChanges, crossFieldTable.newChange.nodeAliases, newId);
730
+ const over = nodeChangeFromId(crossFieldTable.baseChange.nodeChanges, crossFieldTable.baseChange.nodeAliases, baseId);
663
731
  const baseMap = over?.fieldChanges ?? new Map();
664
732
  const fieldChanges = change.fieldChanges !== undefined && over.fieldChanges !== undefined
665
733
  ? this.rebaseFieldMap(change?.fieldChanges ?? new Map(), baseMap, newId, genId, crossFieldTable, revisionMetadata)
@@ -677,28 +745,37 @@ export class ModularChangeFamily {
677
745
  setInChangeAtomIdMap(crossFieldTable.baseToRebasedNodeId, baseId, newId);
678
746
  return rebasedChange;
679
747
  }
748
+ updateConstraints(rebasedFields, rebasedNodes, rebasedRoots, constraintState, revertConstraintState) {
749
+ this.updateConstraintsForFields(rebasedFields, NodeAttachState.Attached, NodeAttachState.Attached, constraintState, revertConstraintState, rebasedNodes);
750
+ for (const [_detachId, nodeId] of rebasedRoots.nodeChanges.entries()) {
751
+ // XXX: This is incorrect if the rebased changeset attaches the node.
752
+ // Efficiently computing whether the changeset attaches the node would require maintaining a mapping from node ID to attach ID.
753
+ const detachedInOutput = true;
754
+ this.updateConstraintsForNode(nodeId, NodeAttachState.Detached, detachedInOutput ? NodeAttachState.Detached : NodeAttachState.Attached, rebasedNodes, constraintState, revertConstraintState);
755
+ }
756
+ }
680
757
  updateConstraintsForFields(fields, parentInputAttachState, parentOutputAttachState, constraintState, revertConstraintState, nodes) {
681
758
  for (const field of fields.values()) {
682
759
  const handler = getChangeHandler(this.fieldKinds, field.fieldKind);
683
- for (const [nodeId, inputIndex, outputIndex] of handler.getNestedChanges(field.change)) {
684
- const isInputDetached = inputIndex === undefined;
685
- const inputAttachState = parentInputAttachState === NodeAttachState.Detached || isInputDetached
686
- ? NodeAttachState.Detached
687
- : NodeAttachState.Attached;
688
- const isOutputDetached = outputIndex === undefined;
760
+ for (const [nodeId] of handler.getNestedChanges(field.change)) {
761
+ // XXX: This is incorrect if the rebased changeset detaches this node.
762
+ // Efficiently computing whether the changeset detaches the node would require maintaining a mapping from node ID to detach ID.
763
+ const isOutputDetached = false;
689
764
  const outputAttachState = parentOutputAttachState === NodeAttachState.Detached || isOutputDetached
690
765
  ? NodeAttachState.Detached
691
766
  : NodeAttachState.Attached;
692
- this.updateConstraintsForNode(nodeId, inputAttachState, outputAttachState, nodes, constraintState, revertConstraintState);
767
+ this.updateConstraintsForNode(nodeId, parentInputAttachState, outputAttachState, nodes, constraintState, revertConstraintState);
693
768
  }
694
769
  }
695
770
  }
696
771
  updateConstraintsForNode(nodeId, inputAttachState, outputAttachState, nodes, constraintState, revertConstraintState) {
697
- const node = nodes.get([nodeId.revision, nodeId.localId]) ?? fail(0xb24 /* Unknown node ID */);
772
+ const node = getFromChangeAtomIdMap(nodes, nodeId) ?? fail(0xb24 /* Unknown node ID */);
773
+ const updatedNode = { ...node };
774
+ setInChangeAtomIdMap(nodes, nodeId, updatedNode);
698
775
  if (node.nodeExistsConstraint !== undefined) {
699
776
  const isNowViolated = inputAttachState === NodeAttachState.Detached;
700
777
  if (node.nodeExistsConstraint.violated !== isNowViolated) {
701
- node.nodeExistsConstraint = {
778
+ updatedNode.nodeExistsConstraint = {
702
779
  ...node.nodeExistsConstraint,
703
780
  violated: isNowViolated,
704
781
  };
@@ -708,7 +785,7 @@ export class ModularChangeFamily {
708
785
  if (node.nodeExistsConstraintOnRevert !== undefined) {
709
786
  const isNowViolated = outputAttachState === NodeAttachState.Detached;
710
787
  if (node.nodeExistsConstraintOnRevert.violated !== isNowViolated) {
711
- node.nodeExistsConstraintOnRevert = {
788
+ updatedNode.nodeExistsConstraintOnRevert = {
712
789
  ...node.nodeExistsConstraintOnRevert,
713
790
  violated: isNowViolated,
714
791
  };
@@ -719,35 +796,73 @@ export class ModularChangeFamily {
719
796
  this.updateConstraintsForFields(node.fieldChanges, inputAttachState, outputAttachState, constraintState, revertConstraintState, nodes);
720
797
  }
721
798
  }
722
- pruneFieldMap(changeset, nodeMap) {
799
+ pruneFieldMap(changeset, parentId, nodeMap, nodeToParent, aliases, roots, fieldsWithRootMoves, fieldsToRootChanges) {
723
800
  if (changeset === undefined) {
724
801
  return undefined;
725
802
  }
726
803
  const prunedChangeset = new Map();
727
804
  for (const [field, fieldChange] of changeset) {
728
805
  const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
729
- const prunedFieldChangeset = handler.rebaser.prune(fieldChange.change, (nodeId) => this.pruneNodeChange(nodeId, nodeMap));
730
- if (!handler.isEmpty(prunedFieldChangeset)) {
806
+ const prunedFieldChangeset = handler.rebaser.prune(fieldChange.change, (nodeId) => this.pruneNodeChange(nodeId, nodeMap, nodeToParent, aliases, roots, fieldsWithRootMoves, fieldsToRootChanges));
807
+ const fieldId = { nodeId: parentId, field };
808
+ const fieldIdKey = fieldIdKeyFromFieldId(fieldId);
809
+ const rootsWithChanges = fieldsToRootChanges.get(fieldIdKey) ?? [];
810
+ let hasRootWithNodeChange = false;
811
+ for (const rootId of rootsWithChanges) {
812
+ const nodeId = getFromChangeAtomIdMap(roots.nodeChanges, rootId) ?? fail("No root change found");
813
+ const isRootChangeEmpty = this.pruneNodeChange(nodeId, nodeMap, nodeToParent, aliases, roots, fieldsWithRootMoves, fieldsToRootChanges) === undefined;
814
+ if (isRootChangeEmpty) {
815
+ roots.nodeChanges.delete([rootId.revision, rootId.localId]);
816
+ tryRemoveDetachLocation(roots, rootId, 1);
817
+ }
818
+ else {
819
+ hasRootWithNodeChange = true;
820
+ }
821
+ }
822
+ const hasRootChanges = hasRootWithNodeChange || fieldsWithRootMoves.get(fieldIdKey) === true;
823
+ if (!handler.isEmpty(prunedFieldChangeset) || hasRootChanges) {
731
824
  prunedChangeset.set(field, { ...fieldChange, change: brand(prunedFieldChangeset) });
732
825
  }
733
826
  }
734
827
  return prunedChangeset.size > 0 ? prunedChangeset : undefined;
735
828
  }
736
- pruneNodeChange(nodeId, nodeMap) {
737
- const changeset = nodeChangeFromId(nodeMap, nodeId);
829
+ pruneRoots(roots, nodeMap, nodeToParent, aliases, fieldsWithRootMoves, fieldsToRootChanges) {
830
+ const pruned = { ...roots, nodeChanges: newChangeAtomIdBTree() };
831
+ for (const [rootIdKey, nodeId] of roots.nodeChanges.entries()) {
832
+ const rootId = { revision: rootIdKey[0], localId: rootIdKey[1] };
833
+ const hasDetachLocation = roots.detachLocations.getFirst(rootId, 1).value !== undefined;
834
+ // If the root has a detach location it should be pruned by recursion when pruning the field it was detached from.
835
+ const prunedId = hasDetachLocation
836
+ ? nodeId
837
+ : this.pruneNodeChange(nodeId, nodeMap, nodeToParent, aliases, roots, fieldsWithRootMoves, fieldsToRootChanges);
838
+ if (prunedId !== undefined) {
839
+ pruned.nodeChanges.set(rootIdKey, prunedId);
840
+ }
841
+ tryRemoveDetachLocation(pruned, rootId, 1);
842
+ }
843
+ return pruned;
844
+ }
845
+ pruneNodeChange(nodeId, nodes, nodeToParent, aliases, roots, fieldsWithRootMoves, fieldsToRootChanges) {
846
+ const changeset = nodeChangeFromId(nodes, aliases, nodeId);
738
847
  const prunedFields = changeset.fieldChanges === undefined
739
848
  ? undefined
740
- : this.pruneFieldMap(changeset.fieldChanges, nodeMap);
849
+ : this.pruneFieldMap(changeset.fieldChanges, nodeId, nodes, nodeToParent, aliases, roots, fieldsWithRootMoves, fieldsToRootChanges);
741
850
  const prunedChange = { ...changeset, fieldChanges: prunedFields };
742
851
  if (prunedChange.fieldChanges === undefined) {
743
852
  delete prunedChange.fieldChanges;
744
853
  }
745
854
  if (isEmptyNodeChangeset(prunedChange)) {
746
- nodeMap.delete([nodeId.revision, nodeId.localId]);
855
+ const nodeIdKey = [
856
+ nodeId.revision,
857
+ nodeId.localId,
858
+ ];
859
+ // TODO: Shouldn't we also delete all aliases associated with this node?
860
+ nodes.delete(nodeIdKey);
861
+ nodeToParent.delete(nodeIdKey);
747
862
  return undefined;
748
863
  }
749
864
  else {
750
- setInChangeAtomIdMap(nodeMap, nodeId, prunedChange);
865
+ setInChangeAtomIdMap(nodes, nodeId, prunedChange);
751
866
  return nodeId;
752
867
  }
753
868
  }
@@ -764,12 +879,13 @@ export class ModularChangeFamily {
764
879
  changeRevision(change, replacer) {
765
880
  const updatedFields = this.replaceFieldMapRevisions(change.fieldChanges, replacer);
766
881
  const updatedNodes = replaceIdMapRevisions(change.nodeChanges, replacer, (nodeChangeset) => this.replaceNodeChangesetRevisions(nodeChangeset, replacer));
767
- const updatedNodeToParent = replaceIdMapRevisions(change.nodeToParent, replacer, (fieldId) => replaceFieldIdRevision(normalizeFieldId(fieldId, change.nodeAliases), replacer));
882
+ const updatedNodeToParent = replaceIdMapRevisions(change.nodeToParent, replacer, (location) => replaceNodeLocationRevision(normalizeNodeLocation(location, change.nodeAliases), replacer));
768
883
  const updated = {
769
884
  ...change,
770
885
  fieldChanges: updatedFields,
771
886
  nodeChanges: updatedNodes,
772
887
  nodeToParent: updatedNodeToParent,
888
+ rootNodes: replaceRootTableRevision(change.rootNodes, replacer, change.nodeAliases),
773
889
  // We've updated all references to old node IDs, so we no longer need an alias table.
774
890
  nodeAliases: newChangeAtomIdBTree(),
775
891
  crossFieldKeys: replaceCrossFieldKeyTableRevisions(change.crossFieldKeys, replacer, change.nodeAliases),
@@ -802,7 +918,7 @@ export class ModularChangeFamily {
802
918
  return updatedFields;
803
919
  }
804
920
  makeCrossFieldKeyTable(fields, nodes) {
805
- const keys = newCrossFieldKeyTable();
921
+ const keys = newCrossFieldRangeTable();
806
922
  this.populateCrossFieldKeyTableForFieldMap(keys, fields, undefined);
807
923
  nodes.forEachPair(([revision, localId], node) => {
808
924
  if (node.fieldChanges !== undefined) {
@@ -822,49 +938,13 @@ export class ModularChangeFamily {
822
938
  }
823
939
  }
824
940
  }
825
- buildEditor(mintRevisionTag, changeReceiver) {
826
- return new ModularEditBuilder(this, this.fieldKinds, changeReceiver, this.codecOptions);
941
+ buildEditor(mintRevisionTag, changeReceiver, editorOptions) {
942
+ return new ModularEditBuilder(this, this.fieldKinds, changeReceiver, this.codecOptions, editorOptions);
827
943
  }
828
944
  createEmptyFieldChange(fieldKind) {
829
945
  const emptyChange = getChangeHandler(this.fieldKinds, fieldKind).createEmpty();
830
946
  return { fieldKind, change: brand(emptyChange) };
831
947
  }
832
- validateChangeset(change) {
833
- let numNodes = this.validateFieldChanges(change, change.fieldChanges, undefined);
834
- for (const [[revision, localId], node] of change.nodeChanges.entries()) {
835
- if (node.fieldChanges === undefined) {
836
- continue;
837
- }
838
- const nodeId = { revision, localId };
839
- const numChildren = this.validateFieldChanges(change, node.fieldChanges, nodeId);
840
- numNodes += numChildren;
841
- }
842
- assert(numNodes === change.nodeChanges.size, 0xa4d /* Node table contains unparented nodes */);
843
- }
844
- /**
845
- * Asserts that each child and cross field key in each field has a correct entry in
846
- * `nodeToParent` or `crossFieldKeyTable`.
847
- * @returns the number of children found.
848
- */
849
- validateFieldChanges(change, fieldChanges, nodeParent) {
850
- let numChildren = 0;
851
- for (const [field, fieldChange] of fieldChanges.entries()) {
852
- const fieldId = { nodeId: nodeParent, field };
853
- const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
854
- for (const [child, _index] of handler.getNestedChanges(fieldChange.change)) {
855
- const parentFieldId = getParentFieldId(change, child);
856
- assert(areEqualFieldIds(parentFieldId, fieldId), 0xa4e /* Inconsistent node parentage */);
857
- numChildren += 1;
858
- }
859
- for (const keyRange of handler.getCrossFieldKeys(fieldChange.change)) {
860
- const fields = getFieldsForCrossFieldKey(change, keyRange.key, keyRange.count);
861
- assert(fields.length === 1 &&
862
- fields[0] !== undefined &&
863
- areEqualFieldIds(fields[0], fieldId), 0xa4f /* Inconsistent cross field keys */);
864
- }
865
- }
866
- return numChildren;
867
- }
868
948
  getEffectiveChange(change) {
869
949
  if (hasConflicts(change)) {
870
950
  return this.muteChange(change);
@@ -877,7 +957,8 @@ export class ModularChangeFamily {
877
957
  muteChange(change) {
878
958
  const muted = {
879
959
  ...change,
880
- crossFieldKeys: newCrossFieldKeyTable(),
960
+ rootNodes: muteRootChanges(change.rootNodes),
961
+ crossFieldKeys: newCrossFieldRangeTable(),
881
962
  fieldChanges: this.muteFieldChanges(change.fieldChanges),
882
963
  nodeChanges: brand(change.nodeChanges.mapValues((v) => this.muteNodeChange(v))),
883
964
  };
@@ -905,7 +986,7 @@ export class ModularChangeFamily {
905
986
  }
906
987
  ModularChangeFamily.emptyChange = makeModularChangeset();
907
988
  function replaceCrossFieldKeyTableRevisions(table, replacer, nodeAliases) {
908
- const updated = newCrossFieldKeyTable();
989
+ const updated = newCrossFieldRangeTable();
909
990
  for (const entry of table.entries()) {
910
991
  const key = entry.start;
911
992
  const updatedKey = replacer.getUpdatedAtomId(key, entry.length);
@@ -964,6 +1045,19 @@ function composeBuildsDestroysAndRefreshers(change1, change2) {
964
1045
  }
965
1046
  }
966
1047
  }
1048
+ // It's possible to have a build and a refresher for the same root because an attach operation need not be performed in the same changeset as the corresponding build.
1049
+ if (change1.builds !== undefined && change2.refreshers !== undefined) {
1050
+ for (const [key, chunk] of change2.refreshers.entries()) {
1051
+ assert(chunk.topLevelLength === 1, "Expected refresher chunk to have length 1");
1052
+ const match = change1.builds.getPairOrNextLower(key);
1053
+ if (match !== undefined) {
1054
+ const [buildKey, buildChunk] = match;
1055
+ if (buildKey[0] === key[0] && buildKey[1] + buildChunk.topLevelLength > key[1]) {
1056
+ allRefreshers.delete(key);
1057
+ }
1058
+ }
1059
+ }
1060
+ }
967
1061
  return { allBuilds, allDestroys, allRefreshers };
968
1062
  }
969
1063
  function invertBuilds(builds) {
@@ -988,19 +1082,44 @@ function invertBuilds(builds) {
988
1082
  * @param fieldKinds - The field kinds to delegate to.
989
1083
  */
990
1084
  export function* relevantRemovedRoots(change, fieldKinds) {
991
- yield* relevantRemovedRootsFromFields(change.fieldChanges, change.nodeChanges, fieldKinds);
1085
+ const rootIds = newChangeAtomIdRangeMap();
1086
+ addAttachesToSet(change, rootIds);
1087
+ addRenamesToSet(change, rootIds);
1088
+ for (const [[revision, localId]] of change.rootNodes.nodeChanges.entries()) {
1089
+ rootIds.set({ revision, localId }, 1, true);
1090
+ }
1091
+ for (const entry of rootIds.entries()) {
1092
+ for (let offset = 0; offset < entry.length; offset++) {
1093
+ const detachId = offsetChangeAtomId(entry.start, offset);
1094
+ yield makeDetachedNodeId(detachId.revision, detachId.localId);
1095
+ }
1096
+ }
992
1097
  }
993
- function* relevantRemovedRootsFromNode(node, nodeChanges, fieldKinds) {
994
- const nodeChangeset = nodeChangeFromId(nodeChanges, node);
995
- if (nodeChangeset.fieldChanges !== undefined) {
996
- yield* relevantRemovedRootsFromFields(nodeChangeset.fieldChanges, nodeChanges, fieldKinds);
1098
+ function addAttachesToSet(change, rootIds) {
1099
+ // This includes each attach which does not have a corresponding detach.
1100
+ for (const entry of change.crossFieldKeys.entries()) {
1101
+ if (entry.start.target !== NodeMoveType.Attach) {
1102
+ continue;
1103
+ }
1104
+ for (const detachIdEntry of change.rootNodes.newToOldId.getAll2(entry.start, entry.length)) {
1105
+ const detachId = detachIdEntry.value ?? offsetChangeAtomId(entry.start, detachIdEntry.offset);
1106
+ for (const detachEntry of change.crossFieldKeys.getAll2({ ...detachId, target: NodeMoveType.Detach }, detachIdEntry.length)) {
1107
+ if (detachEntry.value === undefined) {
1108
+ rootIds.set(offsetChangeAtomId(detachId, detachEntry.offset), detachEntry.length, true);
1109
+ }
1110
+ }
1111
+ }
997
1112
  }
998
1113
  }
999
- function* relevantRemovedRootsFromFields(change, nodeChanges, fieldKinds) {
1000
- const delegate = (node) => relevantRemovedRootsFromNode(node, nodeChanges, fieldKinds);
1001
- for (const [_, fieldChange] of change) {
1002
- const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
1003
- yield* handler.relevantRemovedRoots(fieldChange.change, delegate);
1114
+ function addRenamesToSet(change, rootIds) {
1115
+ for (const renameEntry of change.rootNodes.oldToNewId.entries()) {
1116
+ for (const detachEntry of change.crossFieldKeys.getAll2({ ...renameEntry.start, target: NodeMoveType.Detach }, renameEntry.length)) {
1117
+ // We only want to include renames of nodes which are detached in the input context of the changeset.
1118
+ // So if there is a detach for the node, the rename is not relevant.
1119
+ if (detachEntry.value === undefined) {
1120
+ rootIds.set(renameEntry.start, renameEntry.length, true);
1121
+ }
1122
+ }
1004
1123
  }
1005
1124
  }
1006
1125
  /**
@@ -1046,13 +1165,15 @@ export function updateRefreshers(change, getDetachedNode, removedRoots, requireR
1046
1165
  refreshers.set([root.major, brand(root.minor)], node);
1047
1166
  }
1048
1167
  }
1049
- const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys, maxId, revisions, constraintViolationCount, constraintViolationCountOnRevert, builds, destroys, } = change;
1168
+ const { rebaseVersion, fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys, maxId, revisions, constraintViolationCount, constraintViolationCountOnRevert, builds, destroys, rootNodes, } = change;
1050
1169
  return makeModularChangeset({
1170
+ rebaseVersion,
1051
1171
  fieldChanges,
1052
1172
  nodeChanges,
1053
1173
  nodeToParent,
1054
1174
  nodeAliases,
1055
1175
  crossFieldKeys,
1176
+ rootNodes,
1056
1177
  maxId: maxId,
1057
1178
  revisions,
1058
1179
  constraintViolationCount,
@@ -1071,11 +1192,24 @@ export function updateRefreshers(change, getDetachedNode, removedRoots, requireR
1071
1192
  export function intoDelta(taggedChange, fieldKinds) {
1072
1193
  const change = taggedChange.change;
1073
1194
  const rootDelta = {};
1074
- const global = [];
1075
- const rename = [];
1076
1195
  if (!hasConflicts(change)) {
1077
1196
  // If there are no constraint violations, then tree changes apply.
1078
- const fieldDeltas = intoDeltaImpl(change.fieldChanges, change.nodeChanges, fieldKinds, global, rename);
1197
+ const fieldDeltas = intoDeltaImpl(change.fieldChanges, change.nodeChanges, change.nodeAliases, fieldKinds);
1198
+ const global = [];
1199
+ for (const [[major, minor], nodeId] of change.rootNodes.nodeChanges.entries()) {
1200
+ global.push({
1201
+ id: { major, minor },
1202
+ fields: deltaFromNodeChange(nodeChangeFromId(change.nodeChanges, change.nodeAliases, nodeId), change.nodeChanges, change.nodeAliases, fieldKinds),
1203
+ });
1204
+ }
1205
+ const rename = [];
1206
+ for (const { start: oldId, value: newId, length, } of change.rootNodes.oldToNewId.entries()) {
1207
+ rename.push({
1208
+ count: length,
1209
+ oldId: makeDetachedNodeId(oldId.revision, oldId.localId),
1210
+ newId: makeDetachedNodeId(newId.revision, newId.localId),
1211
+ });
1212
+ }
1079
1213
  if (fieldDeltas.size > 0) {
1080
1214
  rootDelta.fields = fieldDeltas;
1081
1215
  }
@@ -1121,28 +1255,22 @@ function copyDetachedNodes(detachedNodes) {
1121
1255
  /**
1122
1256
  * @param change - The change to convert into a delta.
1123
1257
  */
1124
- function intoDeltaImpl(change, nodeChanges, fieldKinds, global, rename) {
1258
+ function intoDeltaImpl(change, nodeChanges, nodeAliases, fieldKinds) {
1125
1259
  const delta = new Map();
1126
1260
  for (const [field, fieldChange] of change) {
1127
- const { local: fieldChanges, global: fieldGlobal, rename: fieldRename, } = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(fieldChange.change, (childChange) => {
1128
- const nodeChange = nodeChangeFromId(nodeChanges, childChange);
1129
- return deltaFromNodeChange(nodeChange, nodeChanges, fieldKinds, global, rename);
1261
+ const fieldDelta = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(fieldChange.change, (childChange) => {
1262
+ const nodeChange = nodeChangeFromId(nodeChanges, nodeAliases, childChange);
1263
+ return deltaFromNodeChange(nodeChange, nodeChanges, nodeAliases, fieldKinds);
1130
1264
  });
1131
- if (fieldChanges !== undefined && fieldChanges.marks.length > 0) {
1132
- delta.set(field, fieldChanges);
1133
- }
1134
- for (const c of fieldGlobal ?? []) {
1135
- global.push(c);
1136
- }
1137
- for (const r of fieldRename ?? []) {
1138
- rename.push(r);
1265
+ if (fieldDelta !== undefined && fieldDelta.marks.length > 0) {
1266
+ delta.set(field, fieldDelta);
1139
1267
  }
1140
1268
  }
1141
1269
  return delta;
1142
1270
  }
1143
- function deltaFromNodeChange(change, nodeChanges, fieldKinds, global, rename) {
1271
+ function deltaFromNodeChange(change, nodeChanges, nodeAliases, fieldKinds) {
1144
1272
  if (change.fieldChanges !== undefined) {
1145
- return intoDeltaImpl(change.fieldChanges, nodeChanges, fieldKinds, global, rename);
1273
+ return intoDeltaImpl(change.fieldChanges, nodeChanges, nodeAliases, fieldKinds);
1146
1274
  }
1147
1275
  // TODO: update the API to allow undefined to be returned here
1148
1276
  return new Map();
@@ -1189,30 +1317,22 @@ export function getFieldKind(fieldKinds, kind) {
1189
1317
  export function getChangeHandler(fieldKinds, kind) {
1190
1318
  return getFieldKind(fieldKinds, kind).changeHandler;
1191
1319
  }
1192
- function newComposeTable(baseChange, newChange, composedNodeToParent) {
1320
+ function newComposeTable(baseChange, newChange, composedRootNodes, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions) {
1193
1321
  return {
1194
- ...newCrossFieldTable(),
1322
+ rebaseVersion: Math.max(baseChange.rebaseVersion, newChange.rebaseVersion),
1323
+ entries: newDetachedEntryMap(),
1195
1324
  baseChange,
1196
1325
  newChange,
1197
1326
  fieldToContext: new Map(),
1198
1327
  newFieldToBaseField: new Map(),
1199
1328
  newToBaseNodeId: newChangeAtomIdBTree(),
1200
1329
  composedNodes: new Set(),
1201
- composedNodeToParent,
1202
- pendingCompositions: {
1203
- nodeIdsToCompose: [],
1204
- affectedBaseFields: newFieldIdKeyBTree(),
1205
- affectedNewFields: newFieldIdKeyBTree(),
1206
- },
1207
- };
1208
- }
1209
- function newCrossFieldTable() {
1210
- return {
1211
- srcTable: newChangeAtomIdRangeMap(),
1212
- dstTable: newChangeAtomIdRangeMap(),
1213
- srcDependents: newChangeAtomIdRangeMap(),
1214
- dstDependents: newChangeAtomIdRangeMap(),
1215
- invalidatedFields: new Set(),
1330
+ movedNodeToParent: newChangeAtomIdBTree(),
1331
+ composedRootNodes,
1332
+ movedCrossFieldKeys,
1333
+ removedCrossFieldKeys,
1334
+ renamesToDelete: newChangeAtomIdRangeMap(),
1335
+ pendingCompositions,
1216
1336
  };
1217
1337
  }
1218
1338
  function newConstraintState(violationCount) {
@@ -1220,146 +1340,348 @@ function newConstraintState(violationCount) {
1220
1340
  violationCount,
1221
1341
  };
1222
1342
  }
1223
- class CrossFieldManagerI {
1224
- constructor(crossFieldTable, currentFieldKey, allowInval = true) {
1225
- this.crossFieldTable = crossFieldTable;
1226
- this.currentFieldKey = currentFieldKey;
1227
- this.allowInval = allowInval;
1343
+ class InvertNodeManagerI {
1344
+ constructor(table, fieldId) {
1345
+ this.table = table;
1346
+ this.fieldId = fieldId;
1228
1347
  }
1229
- set(target, revision, id, count, newValue, invalidateDependents) {
1230
- if (invalidateDependents && this.allowInval) {
1231
- const lastChangedId = id + count - 1;
1232
- let firstId = id;
1233
- while (firstId <= lastChangedId) {
1234
- const dependentEntry = getFirstFromCrossFieldMap(this.getDependents(target), revision, firstId, lastChangedId - firstId + 1);
1235
- if (dependentEntry.value !== undefined) {
1236
- this.crossFieldTable.invalidatedFields.add(dependentEntry.value);
1348
+ invertDetach(detachId, count, nodeChange, newAttachId) {
1349
+ if (nodeChange !== undefined) {
1350
+ assert(count === 1, "A node change should only affect one node");
1351
+ const attachEntry = firstAttachIdFromDetachId(this.table.change.rootNodes, detachId, count);
1352
+ const attachFieldEntry = this.table.change.crossFieldKeys.getFirst({ target: NodeMoveType.Attach, ...attachEntry.value }, count);
1353
+ if (attachFieldEntry.value === undefined) {
1354
+ assignRootChange(this.table.invertedRoots, this.table.invertedNodeToParent, attachEntry.value, nodeChange, this.fieldId, this.table.change.rebaseVersion);
1355
+ }
1356
+ else {
1357
+ setInCrossFieldMap(this.table.entries, attachEntry.value, count, nodeChange);
1358
+ this.table.invalidatedFields.add(fieldChangeFromId(this.table.change, attachFieldEntry.value));
1359
+ }
1360
+ }
1361
+ if (!areEqualChangeAtomIds(detachId, newAttachId)) {
1362
+ for (const entry of doesChangeAttachNodes(this.table.change.crossFieldKeys, detachId, count)) {
1363
+ if (!entry.value) {
1364
+ this.table.attachToDetachId.set(newAttachId, count, detachId);
1365
+ this.table.invertedRoots.detachLocations.set(detachId, count, this.fieldId);
1237
1366
  }
1238
- firstId = brand(firstId + dependentEntry.length);
1239
1367
  }
1240
1368
  }
1241
- setInCrossFieldMap(this.getMap(target), revision, id, count, newValue);
1242
1369
  }
1243
- get(target, revision, id, count, addDependency) {
1244
- if (addDependency) {
1245
- // We assume that if there is already an entry for this ID it is because
1246
- // a field handler has called compose on the same node multiple times.
1247
- // In this case we only want to update the latest version, so we overwrite the dependency.
1248
- setInCrossFieldMap(this.getDependents(target), revision, id, count, this.currentFieldKey);
1370
+ invertAttach(attachId, count) {
1371
+ let countToProcess = count;
1372
+ const detachIdEntry = firstDetachIdFromAttachId(this.table.change.rootNodes, attachId, countToProcess);
1373
+ countToProcess = detachIdEntry.length;
1374
+ const detachEntry = getFirstFieldForCrossFieldKey(this.table.change, { target: NodeMoveType.Detach, ...detachIdEntry.value }, countToProcess);
1375
+ countToProcess = detachEntry.length;
1376
+ let result;
1377
+ if (detachEntry.value === undefined) {
1378
+ // This node is detached in the input context of the original change.
1379
+ const nodeIdEntry = rangeQueryChangeAtomIdMap(this.table.change.rootNodes.nodeChanges, detachIdEntry.value, countToProcess);
1380
+ countToProcess = nodeIdEntry.length;
1381
+ const detachLocationEntry = this.table.change.rootNodes.detachLocations.getFirst(detachIdEntry.value, countToProcess);
1382
+ countToProcess = detachLocationEntry.length;
1383
+ if (this.table.isRollback &&
1384
+ detachLocationEntry.value !== undefined &&
1385
+ !areEqualFieldIds(normalizeFieldId(detachLocationEntry.value, this.table.change.nodeAliases), this.fieldId)) {
1386
+ // These nodes are detached in the input context of the original change,
1387
+ // and the change attaches these nodes in a different location from their detach location.
1388
+ // The rollback change should send them back to that prior detach location.
1389
+ this.table.invertedRoots.outputDetachLocations.set(detachIdEntry.value, countToProcess, detachLocationEntry.value);
1390
+ }
1391
+ result = {
1392
+ value: {
1393
+ nodeChange: nodeIdEntry.value,
1394
+ detachId: detachIdEntry.value,
1395
+ },
1396
+ length: countToProcess,
1397
+ };
1249
1398
  }
1250
- return getFirstFromCrossFieldMap(this.getMap(target), revision, id, count);
1251
- }
1252
- getMap(target) {
1253
- return target === CrossFieldTarget.Source
1254
- ? this.crossFieldTable.srcTable
1255
- : this.crossFieldTable.dstTable;
1256
- }
1257
- getDependents(target) {
1258
- return target === CrossFieldTarget.Source
1259
- ? this.crossFieldTable.srcDependents
1260
- : this.crossFieldTable.dstDependents;
1261
- }
1262
- }
1263
- class InvertManager extends CrossFieldManagerI {
1264
- constructor(table, field, fieldId, allowInval = true) {
1265
- super(table, field, allowInval);
1266
- this.fieldId = fieldId;
1267
- }
1268
- onMoveIn(id) {
1269
- setInChangeAtomIdMap(this.table.invertedNodeToParent, id, this.fieldId);
1270
- }
1271
- moveKey(target, revision, id, count) {
1272
- assert(false, 0x9c5 /* Keys should not be moved manually during invert */);
1273
- }
1274
- get table() {
1275
- return this.crossFieldTable;
1399
+ else {
1400
+ const moveEntry = this.table.entries.getFirst(attachId, countToProcess);
1401
+ result = { ...moveEntry, value: { nodeChange: moveEntry.value } };
1402
+ }
1403
+ if (result.value?.nodeChange !== undefined) {
1404
+ setInChangeAtomIdMap(this.table.invertedNodeToParent, result.value.nodeChange, {
1405
+ field: this.fieldId,
1406
+ });
1407
+ }
1408
+ return result;
1276
1409
  }
1277
1410
  }
1278
- class RebaseManager extends CrossFieldManagerI {
1279
- constructor(table, currentField, fieldId, allowInval = true) {
1280
- super(table, currentField, allowInval);
1411
+ class RebaseNodeManagerI {
1412
+ constructor(table, fieldId, allowInval = true) {
1413
+ this.table = table;
1281
1414
  this.fieldId = fieldId;
1415
+ this.allowInval = allowInval;
1282
1416
  }
1283
- set(target, revision, id, count, newValue, invalidateDependents) {
1284
- if (invalidateDependents && this.allowInval) {
1285
- const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, {
1286
- target,
1287
- revision,
1288
- localId: id,
1289
- }, count);
1290
- assert(newFieldIds.length === 0, 0x9c6 /* TODO: Modifying a cross-field key from the new changeset is currently unsupported */);
1291
- const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, {
1292
- target,
1293
- revision,
1294
- localId: id,
1295
- }, count);
1296
- assert(baseFieldIds.length > 0, 0x9c7 /* Cross field key not registered in base or new change */);
1297
- for (const baseFieldId of baseFieldIds) {
1298
- this.table.affectedBaseFields.set([baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field], true);
1417
+ getNewChangesForBaseAttach(baseAttachId, count) {
1418
+ let countToProcess = count;
1419
+ const detachEntry = firstDetachIdFromAttachId(this.table.baseChange.rootNodes, baseAttachId, countToProcess);
1420
+ countToProcess = detachEntry.length;
1421
+ const nodeEntry = rangeQueryChangeAtomIdMap(this.table.newChange.rootNodes.nodeChanges, detachEntry.value, countToProcess);
1422
+ countToProcess = nodeEntry.length;
1423
+ const newNodeId = nodeEntry.value;
1424
+ const newRenameEntry = this.table.newChange.rootNodes.oldToNewId.getFirst(detachEntry.value, countToProcess);
1425
+ countToProcess = newRenameEntry.length;
1426
+ let result;
1427
+ // eslint-disable-next-line unicorn/prefer-ternary
1428
+ if (newNodeId !== undefined || newRenameEntry.value !== undefined) {
1429
+ result = {
1430
+ ...newRenameEntry,
1431
+ value: { detachId: newRenameEntry.value, nodeChange: newNodeId },
1432
+ };
1433
+ }
1434
+ else {
1435
+ // This handles the case where the base changeset has moved these nodes,
1436
+ // meaning they were attached in the input context of the base changeset.
1437
+ result = this.table.entries.getFirst(baseAttachId, countToProcess);
1438
+ }
1439
+ // TODO: Consider moving these two checks into a separate method so that this function has no side effects.
1440
+ if (result.value?.detachId !== undefined) {
1441
+ this.table.rebasedDetachLocations.set(result.value.detachId, result.length, this.fieldId);
1442
+ }
1443
+ if (result.value?.nodeChange !== undefined) {
1444
+ setInChangeAtomIdMap(this.table.rebasedNodeToParent, result.value.nodeChange, {
1445
+ field: this.fieldId,
1446
+ });
1447
+ }
1448
+ return result;
1449
+ }
1450
+ rebaseOverDetach(baseDetachId, count, newDetachId, nodeChange, cellRename) {
1451
+ let countToProcess = count;
1452
+ const attachIdEntry = firstAttachIdFromDetachId(this.table.baseRoots, baseDetachId, countToProcess);
1453
+ const baseAttachId = attachIdEntry.value;
1454
+ countToProcess = attachIdEntry.length;
1455
+ const attachFieldEntry = getFirstFieldForCrossFieldKey(this.table.baseChange, { ...baseAttachId, target: NodeMoveType.Attach }, countToProcess);
1456
+ countToProcess = attachFieldEntry.length;
1457
+ const detachedMoveEntry = this.table.baseChange.rootNodes.outputDetachLocations.getFirst(baseDetachId, countToProcess);
1458
+ countToProcess = detachedMoveEntry.length;
1459
+ const destinationField = attachFieldEntry.value ?? detachedMoveEntry.value;
1460
+ if (destinationField !== undefined) {
1461
+ // The base detach is part of a move (or move of detach location) in the base changeset.
1462
+ setInCrossFieldMap(this.table.entries, baseAttachId, countToProcess, {
1463
+ nodeChange,
1464
+ detachId: newDetachId,
1465
+ cellRename,
1466
+ });
1467
+ if (nodeChange !== undefined || newDetachId !== undefined) {
1468
+ this.invalidateBaseFields([destinationField]);
1469
+ }
1470
+ }
1471
+ if (attachFieldEntry.value === undefined) {
1472
+ // These nodes are detached in the output context of the base changeset.
1473
+ if (nodeChange !== undefined) {
1474
+ assignRootChange(this.table.rebasedRootNodes, this.table.rebasedNodeToParent, baseAttachId, nodeChange, this.fieldId, this.table.rebaseVersion);
1475
+ }
1476
+ if (newDetachId !== undefined) {
1477
+ addNodeRename(this.table.rebasedRootNodes, baseAttachId, newDetachId, countToProcess, this.fieldId);
1478
+ }
1479
+ }
1480
+ if (newDetachId !== undefined) {
1481
+ this.table.movedDetaches.set(newDetachId, countToProcess, true);
1482
+ }
1483
+ if (countToProcess < count) {
1484
+ const remainingCount = count - countToProcess;
1485
+ const nextDetachId = newDetachId === undefined
1486
+ ? undefined
1487
+ : offsetChangeAtomId(newDetachId, countToProcess);
1488
+ this.rebaseOverDetach(offsetChangeAtomId(baseDetachId, countToProcess), remainingCount, nextDetachId, nodeChange);
1489
+ }
1490
+ }
1491
+ addDetach(id, count) {
1492
+ this.table.rebasedDetachLocations.set(id, count, this.fieldId);
1493
+ }
1494
+ removeDetach(id, count) {
1495
+ this.table.movedDetaches.set(id, count, true);
1496
+ }
1497
+ doesBaseAttachNodes(id, count) {
1498
+ let countToProcess = count;
1499
+ const attachEntry = getFirstAttachField(this.table.baseChange.crossFieldKeys, id, countToProcess);
1500
+ countToProcess = attachEntry.length;
1501
+ return { start: id, value: attachEntry.value !== undefined, length: countToProcess };
1502
+ }
1503
+ getBaseRename(id, count) {
1504
+ return this.table.baseChange.rootNodes.oldToNewId.getFirst(id, count);
1505
+ }
1506
+ getNewRenameForBaseRename(baseRenameTo, count) {
1507
+ let countToProcess = count;
1508
+ const inputEntry = firstDetachIdFromAttachId(this.table.baseChange.rootNodes, baseRenameTo, countToProcess);
1509
+ const attachEntry = getFirstAttachField(this.table.baseChange.crossFieldKeys, baseRenameTo, countToProcess);
1510
+ countToProcess = attachEntry.length;
1511
+ if (attachEntry.value !== undefined) {
1512
+ // These nodes are attached in the output context of the base changeset.
1513
+ return { value: undefined, length: countToProcess };
1514
+ }
1515
+ countToProcess = inputEntry.length;
1516
+ const inputId = inputEntry.value;
1517
+ const moveEntry = this.table.entries.getFirst(inputId, countToProcess);
1518
+ countToProcess = moveEntry.length;
1519
+ if (moveEntry.value !== undefined) {
1520
+ return { ...moveEntry, value: moveEntry.value.cellRename ?? moveEntry.value.detachId };
1521
+ }
1522
+ return this.table.newChange.rootNodes.oldToNewId.getFirst(inputId, countToProcess);
1523
+ }
1524
+ invalidateBaseFields(fields) {
1525
+ if (this.allowInval) {
1526
+ for (const fieldId of fields) {
1527
+ this.table.affectedBaseFields.set(fieldIdKeyFromFieldId(fieldId), true);
1299
1528
  }
1300
1529
  }
1301
- super.set(target, revision, id, count, newValue, invalidateDependents);
1302
- }
1303
- onMoveIn(id) {
1304
- setInChangeAtomIdMap(this.table.rebasedNodeToParent, id, this.fieldId);
1305
- }
1306
- moveKey(target, revision, id, count) {
1307
- this.table.rebasedCrossFieldKeys.set({ target, revision, localId: id }, count, this.fieldId);
1308
- }
1309
- get table() {
1310
- return this.crossFieldTable;
1311
1530
  }
1312
1531
  }
1313
- // TODO: Deduplicate this with RebaseTable
1314
- class ComposeManager extends CrossFieldManagerI {
1315
- constructor(table, currentField, fieldId, allowInval = true) {
1316
- super(table, currentField, allowInval);
1532
+ function assignRootChange(table, nodeToParent, detachId, nodeId, detachLocation, rebaseVersion) {
1533
+ assert(rebaseVersion >= 2 || detachLocation !== undefined, "All root changes need a detach location to support compatibility with older client versions");
1534
+ setInChangeAtomIdMap(table.nodeChanges, detachId, nodeId);
1535
+ setInChangeAtomIdMap(nodeToParent, nodeId, { root: detachId });
1536
+ table.detachLocations.set(detachId, 1, detachLocation);
1537
+ }
1538
+ class ComposeNodeManagerI {
1539
+ constructor(table, fieldId, allowInval = true) {
1540
+ this.table = table;
1317
1541
  this.fieldId = fieldId;
1542
+ this.allowInval = allowInval;
1318
1543
  }
1319
- set(target, revision, id, count, newValue, invalidateDependents) {
1320
- if (invalidateDependents && this.allowInval) {
1321
- const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, {
1322
- target,
1323
- revision,
1324
- localId: id,
1325
- }, count);
1326
- if (newFieldIds.length > 0) {
1327
- for (const newFieldId of newFieldIds) {
1328
- this.table.pendingCompositions.affectedNewFields.set([newFieldId.nodeId?.revision, newFieldId.nodeId?.localId, newFieldId.field], true);
1329
- }
1544
+ getNewChangesForBaseDetach(baseDetachId, count) {
1545
+ let countToProcess = count;
1546
+ const baseAttachEntry = getFirstFieldForCrossFieldKey(this.table.baseChange, { target: NodeMoveType.Attach, ...baseDetachId }, countToProcess);
1547
+ countToProcess = baseAttachEntry.length;
1548
+ let result;
1549
+ if (baseAttachEntry.value === undefined) {
1550
+ // The detached nodes are still detached in the new change's input context.
1551
+ const rootEntry = rangeQueryChangeAtomIdMap(this.table.newChange.rootNodes.nodeChanges, baseDetachId, countToProcess);
1552
+ countToProcess = rootEntry.length;
1553
+ const newRenameEntry = this.table.newChange.rootNodes.oldToNewId.getFirst(baseDetachId, countToProcess);
1554
+ countToProcess = newRenameEntry.length;
1555
+ result = {
1556
+ value: { nodeChange: rootEntry.value, detachId: newRenameEntry.value },
1557
+ length: countToProcess,
1558
+ };
1559
+ }
1560
+ else {
1561
+ // The base detach was part of a move.
1562
+ // We check if we've previously seen a node change at the move destination.
1563
+ const entry = this.table.entries.getFirst(baseDetachId, countToProcess);
1564
+ result = { value: entry.value, length: entry.length };
1565
+ }
1566
+ // TODO: Consider moving this to a separate method so that this method can be side-effect free.
1567
+ if (result.value?.nodeChange !== undefined) {
1568
+ setInChangeAtomIdMap(this.table.movedNodeToParent, result.value.nodeChange, {
1569
+ field: this.fieldId,
1570
+ });
1571
+ }
1572
+ return result;
1573
+ }
1574
+ composeAttachDetach(baseAttachId, newDetachId, count) {
1575
+ let countToProcess = count;
1576
+ const newAttachEntry = getFirstAttachField(this.table.newChange.crossFieldKeys, newDetachId, countToProcess);
1577
+ countToProcess = newAttachEntry.length;
1578
+ // Both changes can have the same ID if they came from inverse changesets.
1579
+ // If the new detach is part of a move,
1580
+ // then both input changesets contain the attach cross-field key for this ID.
1581
+ // The new attach may still exist in the composed changeset so we do not remove it here.
1582
+ // The new attach will typically cancel with a base detach,
1583
+ // in which case the cross-field key will be removed in `composeDetachAttach`.
1584
+ const hasNewAttachWithBaseAttachId = areEqualChangeAtomIds(baseAttachId, newDetachId) && newAttachEntry.value !== undefined;
1585
+ if (!hasNewAttachWithBaseAttachId) {
1586
+ this.table.removedCrossFieldKeys.set({ ...baseAttachId, target: NodeMoveType.Attach }, countToProcess, true);
1587
+ }
1588
+ const baseDetachEntry = getFirstDetachField(this.table.baseChange.crossFieldKeys, baseAttachId, countToProcess);
1589
+ countToProcess = baseDetachEntry.length;
1590
+ const baseRootIdEntry = firstDetachIdFromAttachId(this.table.baseChange.rootNodes, baseAttachId, countToProcess);
1591
+ countToProcess = baseRootIdEntry.length;
1592
+ const baseDetachId = baseRootIdEntry.value;
1593
+ if (baseDetachEntry.value === undefined) {
1594
+ const baseDetachLocationEntry = this.table.baseChange.rootNodes.detachLocations.getFirst(baseDetachId, countToProcess);
1595
+ countToProcess = baseDetachLocationEntry.length;
1596
+ // These nodes were detached in the base change's input context,
1597
+ // so the net effect of the two changes is a rename.
1598
+ appendNodeRename(this.table.composedRootNodes, baseAttachId, newDetachId, baseDetachEntry.length, this.table.baseChange.rootNodes, baseDetachLocationEntry.value ?? this.fieldId);
1599
+ this.table.removedCrossFieldKeys.set({ ...newDetachId, target: NodeMoveType.Detach }, countToProcess, true);
1600
+ }
1601
+ else {
1602
+ // The base change moves these nodes.
1603
+ const prevEntry = this.table.entries.getFirst(baseAttachId, baseDetachEntry.length).value ?? {};
1604
+ this.table.entries.set(baseAttachId, baseDetachEntry.length, {
1605
+ ...prevEntry,
1606
+ detachId: newDetachId,
1607
+ });
1608
+ // The new detach will replace the base detach, so we remove the key for the base detach, unless they have the same ID.
1609
+ if (!areEqualChangeAtomIds(baseAttachId, newDetachId)) {
1610
+ this.table.removedCrossFieldKeys.set({ ...baseAttachId, target: NodeMoveType.Detach }, countToProcess, true);
1611
+ }
1612
+ this.table.movedCrossFieldKeys.set({ ...newDetachId, target: NodeMoveType.Detach }, countToProcess, baseDetachEntry.value);
1613
+ this.invalidateBaseFields([baseDetachEntry.value]);
1614
+ }
1615
+ if (newAttachEntry.value === undefined) {
1616
+ const newOutputDetachLocationEntry = this.table.newChange.rootNodes.outputDetachLocations.getFirst(newDetachId, countToProcess);
1617
+ countToProcess = newOutputDetachLocationEntry.length;
1618
+ this.table.composedRootNodes.outputDetachLocations.set(newDetachId, countToProcess, newOutputDetachLocationEntry.value ?? this.fieldId);
1619
+ }
1620
+ if (countToProcess < count) {
1621
+ const remainingCount = count - countToProcess;
1622
+ this.composeAttachDetach(offsetChangeAtomId(baseAttachId, countToProcess), offsetChangeAtomId(newDetachId, countToProcess), remainingCount);
1623
+ }
1624
+ }
1625
+ sendNewChangesToBaseSourceLocation(baseAttachId, newChanges) {
1626
+ const { value: baseDetachId } = firstDetachIdFromAttachId(this.table.baseChange.rootNodes, baseAttachId, 1);
1627
+ const detachFields = getFieldsForCrossFieldKey(this.table.baseChange, {
1628
+ ...baseDetachId,
1629
+ target: NodeMoveType.Detach,
1630
+ }, 1);
1631
+ if (detachFields.length > 0) {
1632
+ // The base attach is part of a move in the base changeset.
1633
+ const prevEntry = this.table.entries.getFirst(baseDetachId, 1).value ?? {};
1634
+ this.table.entries.set(baseDetachId, 1, { ...prevEntry, nodeChange: newChanges });
1635
+ if (newChanges !== undefined) {
1636
+ this.invalidateBaseFields(detachFields);
1637
+ }
1638
+ }
1639
+ else {
1640
+ const baseNodeId = getFromChangeAtomIdMap(this.table.baseChange.rootNodes.nodeChanges, baseDetachId);
1641
+ if (baseNodeId === undefined) {
1642
+ assignRootChange(this.table.composedRootNodes, this.table.movedNodeToParent, baseDetachId, newChanges, this.fieldId, this.table.rebaseVersion);
1330
1643
  }
1331
1644
  else {
1332
- const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, {
1333
- target,
1334
- revision,
1335
- localId: id,
1336
- }, count);
1337
- assert(baseFieldIds.length > 0, 0x9c8 /* Cross field key not registered in base or new change */);
1338
- for (const baseFieldId of baseFieldIds) {
1339
- this.table.pendingCompositions.affectedBaseFields.set([baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field], true);
1340
- }
1645
+ addNodesToCompose(this.table, baseNodeId, newChanges);
1341
1646
  }
1342
1647
  }
1343
- super.set(target, revision, id, count, newValue, invalidateDependents);
1344
1648
  }
1345
- onMoveIn(id) {
1346
- setInChangeAtomIdMap(this.table.composedNodeToParent, id, this.fieldId);
1347
- }
1348
- moveKey(target, revision, id, count) {
1349
- throw new Error("Moving cross-field keys during compose is currently unsupported");
1649
+ composeDetachAttach(baseDetachId, newAttachId, count, composeToPin) {
1650
+ if (composeToPin) {
1651
+ this.table.movedCrossFieldKeys.set({ target: NodeMoveType.Detach, ...newAttachId }, count, this.fieldId);
1652
+ if (!areEqualChangeAtomIds(baseDetachId, newAttachId)) {
1653
+ // The pin will have `newAttachId` as both its detach and attach ID.
1654
+ // So we remove `baseDetachId` unless that is equal to the pin's detach ID.
1655
+ this.table.removedCrossFieldKeys.set({ target: NodeMoveType.Detach, ...baseDetachId }, count, true);
1656
+ }
1657
+ // Note that while change2 should already have this key, change1 may have a rollback for the same ID in a different location.
1658
+ // In that case, change1's attach should be canceled out by a detach from change2.
1659
+ // Here we make sure that the composed change has the correct location (this field) for the attach ID.
1660
+ this.table.movedCrossFieldKeys.set({ target: NodeMoveType.Attach, ...newAttachId }, count, this.fieldId);
1661
+ }
1662
+ else {
1663
+ this.table.removedCrossFieldKeys.set({ target: NodeMoveType.Detach, ...baseDetachId }, count, true);
1664
+ this.table.removedCrossFieldKeys.set({ target: NodeMoveType.Attach, ...newAttachId }, count, true);
1665
+ }
1350
1666
  }
1351
- get table() {
1352
- return this.crossFieldTable;
1667
+ invalidateBaseFields(fields) {
1668
+ if (this.allowInval) {
1669
+ for (const fieldId of fields) {
1670
+ this.table.pendingCompositions.affectedBaseFields.set(fieldIdKeyFromFieldId(fieldId), true);
1671
+ }
1672
+ }
1353
1673
  }
1354
1674
  }
1355
1675
  function makeModularChangeset(props) {
1356
- const p = props ?? { maxId: -1 };
1676
+ const p = props ?? { maxId: -1, rebaseVersion: 1 };
1357
1677
  const changeset = {
1678
+ rebaseVersion: p.rebaseVersion,
1358
1679
  fieldChanges: p.fieldChanges ?? new Map(),
1359
1680
  nodeChanges: p.nodeChanges ?? newChangeAtomIdBTree(),
1681
+ rootNodes: p.rootNodes ?? newRootTable(),
1360
1682
  nodeToParent: p.nodeToParent ?? newChangeAtomIdBTree(),
1361
1683
  nodeAliases: p.nodeAliases ?? newChangeAtomIdBTree(),
1362
- crossFieldKeys: p.crossFieldKeys ?? newCrossFieldKeyTable(),
1684
+ crossFieldKeys: p.crossFieldKeys ?? newCrossFieldRangeTable(),
1363
1685
  };
1364
1686
  if (p.revisions !== undefined && p.revisions.length > 0) {
1365
1687
  changeset.revisions = p.revisions;
@@ -1392,12 +1714,19 @@ function makeModularChangeset(props) {
1392
1714
  return changeset;
1393
1715
  }
1394
1716
  export class ModularEditBuilder extends EditBuilder {
1395
- constructor(family, fieldKinds, changeReceiver, codecOptions) {
1717
+ constructor(family, fieldKinds, changeReceiver, codecOptions, editorOptions) {
1396
1718
  super(family, changeReceiver);
1397
1719
  this.fieldKinds = fieldKinds;
1398
1720
  this.transactionDepth = 0;
1399
1721
  this.idAllocator = idAllocatorFromMaxId();
1400
1722
  this.codecOptions = codecOptions;
1723
+ // TODO: make this dependent on the CodecWriteOptions once there is an FF version that supports RebaseVersion 2
1724
+ this.rebaseVersion =
1725
+ editorOptions?.rebaseVersionOverride ??
1726
+ (editorOptions?.enableDetachedRootEditing === true ? 2 : 1);
1727
+ }
1728
+ isInTransaction() {
1729
+ return this.transactionDepth > 0;
1401
1730
  }
1402
1731
  enterTransaction() {
1403
1732
  this.transactionDepth += 1;
@@ -1445,11 +1774,13 @@ export class ModularEditBuilder extends EditBuilder {
1445
1774
  submitChange(field, fieldKind, change, revision) {
1446
1775
  const localCrossFieldKeys = getChangeHandler(this.fieldKinds, fieldKind).getCrossFieldKeys(change);
1447
1776
  const modularChange = buildModularChangesetFromField({
1777
+ rebaseVersion: this.rebaseVersion,
1448
1778
  path: field,
1449
1779
  fieldChange: { fieldKind, change },
1450
1780
  nodeChanges: newChangeAtomIdBTree(),
1451
1781
  nodeToParent: newChangeAtomIdBTree(),
1452
- crossFieldKeys: newCrossFieldKeyTable(),
1782
+ crossFieldKeys: newCrossFieldRangeTable(),
1783
+ rootNodes: newRootTable(),
1453
1784
  idAllocator: this.idAllocator,
1454
1785
  localCrossFieldKeys,
1455
1786
  revision,
@@ -1466,11 +1797,14 @@ export class ModularEditBuilder extends EditBuilder {
1466
1797
  revisions.add(change.revision);
1467
1798
  return makeAnonChange(change.type === "global"
1468
1799
  ? makeModularChangeset({
1800
+ rebaseVersion: this.rebaseVersion,
1469
1801
  maxId: this.idAllocator.getMaxId(),
1470
1802
  builds: change.builds,
1803
+ rootNodes: renameTableFromRenameDescriptions(change.renames ?? []),
1471
1804
  revisions: [{ revision: change.revision }],
1472
1805
  })
1473
1806
  : buildModularChangesetFromField({
1807
+ rebaseVersion: this.rebaseVersion,
1474
1808
  path: change.field,
1475
1809
  fieldChange: {
1476
1810
  fieldKind: change.fieldKind,
@@ -1478,7 +1812,8 @@ export class ModularEditBuilder extends EditBuilder {
1478
1812
  },
1479
1813
  nodeChanges: newChangeAtomIdBTree(),
1480
1814
  nodeToParent: newChangeAtomIdBTree(),
1481
- crossFieldKeys: newCrossFieldKeyTable(),
1815
+ crossFieldKeys: newCrossFieldRangeTable(),
1816
+ rootNodes: newRootTable(),
1482
1817
  idAllocator: this.idAllocator,
1483
1818
  localCrossFieldKeys: getChangeHandler(this.fieldKinds, change.fieldKind).getCrossFieldKeys(change.change),
1484
1819
  revision: change.revision,
@@ -1503,11 +1838,13 @@ export class ModularEditBuilder extends EditBuilder {
1503
1838
  nodeExistsConstraint: { violated: false },
1504
1839
  };
1505
1840
  this.applyChange(tagChange(buildModularChangesetFromNode({
1841
+ rebaseVersion: this.rebaseVersion,
1506
1842
  path,
1507
1843
  nodeChange,
1508
1844
  nodeChanges: newChangeAtomIdBTree(),
1509
1845
  nodeToParent: newChangeAtomIdBTree(),
1510
- crossFieldKeys: newCrossFieldKeyTable(),
1846
+ crossFieldKeys: newCrossFieldRangeTable(),
1847
+ rootNodes: newRootTable(),
1511
1848
  idAllocator: this.idAllocator,
1512
1849
  revision,
1513
1850
  }), revision));
@@ -1517,11 +1854,13 @@ export class ModularEditBuilder extends EditBuilder {
1517
1854
  nodeExistsConstraintOnRevert: { violated: false },
1518
1855
  };
1519
1856
  this.applyChange(tagChange(buildModularChangesetFromNode({
1857
+ rebaseVersion: this.rebaseVersion,
1520
1858
  path,
1521
1859
  nodeChange,
1522
1860
  nodeChanges: newChangeAtomIdBTree(),
1523
1861
  nodeToParent: newChangeAtomIdBTree(),
1524
- crossFieldKeys: newCrossFieldKeyTable(),
1862
+ crossFieldKeys: newCrossFieldRangeTable(),
1863
+ rootNodes: newRootTable(),
1525
1864
  idAllocator: this.idAllocator,
1526
1865
  revision,
1527
1866
  }), revision));
@@ -1531,6 +1870,7 @@ export class ModularEditBuilder extends EditBuilder {
1531
1870
  throw new UsageError(`No change constraints require min client version of at least ${FluidClientVersion.v2_80}`);
1532
1871
  }
1533
1872
  const changeset = makeModularChangeset({
1873
+ rebaseVersion: this.rebaseVersion,
1534
1874
  maxId: -1,
1535
1875
  noChangeConstraint: { violated: false },
1536
1876
  });
@@ -1541,30 +1881,36 @@ export class ModularEditBuilder extends EditBuilder {
1541
1881
  throw new UsageError(`No change constraints require min client version of at least ${FluidClientVersion.v2_80}`);
1542
1882
  }
1543
1883
  const changeset = makeModularChangeset({
1884
+ rebaseVersion: this.rebaseVersion,
1544
1885
  maxId: -1,
1545
1886
  noChangeConstraintOnRevert: { violated: false },
1546
1887
  });
1547
1888
  this.applyChange(tagChange(changeset, revision));
1548
1889
  }
1549
1890
  }
1550
- function buildModularChangesetFromField(props) {
1551
- const { path, fieldChange, nodeChanges, nodeToParent, crossFieldKeys, idAllocator = idAllocatorFromMaxId(), localCrossFieldKeys = [], childId, revision, } = props;
1891
+ export function buildModularChangesetFromField(props) {
1892
+ const { rebaseVersion, path, fieldChange, nodeChanges, nodeToParent, crossFieldKeys, rootNodes, idAllocator = idAllocatorFromMaxId(), localCrossFieldKeys = [], childId, revision, } = props;
1552
1893
  const fieldChanges = new Map([[path.field, fieldChange]]);
1553
1894
  if (path.parent === undefined) {
1895
+ const field = { nodeId: undefined, field: path.field };
1554
1896
  for (const { key, count } of localCrossFieldKeys) {
1555
- crossFieldKeys.set(key, count, { nodeId: undefined, field: path.field });
1897
+ crossFieldKeys.set(key, count, field);
1556
1898
  }
1557
1899
  if (childId !== undefined) {
1558
1900
  setInChangeAtomIdMap(nodeToParent, childId, {
1559
- nodeId: undefined,
1560
- field: path.field,
1901
+ field: {
1902
+ nodeId: undefined,
1903
+ field: path.field,
1904
+ },
1561
1905
  });
1562
1906
  }
1563
1907
  return makeModularChangeset({
1908
+ rebaseVersion,
1564
1909
  fieldChanges,
1565
1910
  nodeChanges,
1566
1911
  nodeToParent,
1567
1912
  crossFieldKeys,
1913
+ rootNodes,
1568
1914
  maxId: idAllocator.getMaxId(),
1569
1915
  revisions: [{ revision }],
1570
1916
  });
@@ -1573,21 +1919,23 @@ function buildModularChangesetFromField(props) {
1573
1919
  fieldChanges,
1574
1920
  };
1575
1921
  const parentId = { localId: brand(idAllocator.allocate()), revision };
1922
+ const fieldId = { nodeId: parentId, field: path.field };
1576
1923
  for (const { key, count } of localCrossFieldKeys) {
1577
1924
  crossFieldKeys.set(key, count, { nodeId: parentId, field: path.field });
1578
1925
  }
1579
1926
  if (childId !== undefined) {
1580
1927
  setInChangeAtomIdMap(nodeToParent, childId, {
1581
- nodeId: parentId,
1582
- field: path.field,
1928
+ field: fieldId,
1583
1929
  });
1584
1930
  }
1585
1931
  return buildModularChangesetFromNode({
1932
+ rebaseVersion,
1586
1933
  path: path.parent,
1587
1934
  nodeChange: nodeChangeset,
1588
1935
  nodeChanges,
1589
1936
  nodeToParent,
1590
1937
  crossFieldKeys,
1938
+ rootNodes,
1591
1939
  idAllocator,
1592
1940
  revision,
1593
1941
  nodeId: parentId,
@@ -1596,20 +1944,41 @@ function buildModularChangesetFromField(props) {
1596
1944
  function buildModularChangesetFromNode(props) {
1597
1945
  const { path, idAllocator, revision, nodeChanges, nodeChange, nodeId = { localId: brand(idAllocator.allocate()), revision }, } = props;
1598
1946
  setInChangeAtomIdMap(nodeChanges, nodeId, nodeChange);
1599
- const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChanges([
1600
- [path.parentIndex, nodeId],
1601
- ]);
1602
- const fieldChange = {
1603
- fieldKind: genericFieldKind.identifier,
1604
- change: fieldChangeset,
1605
- };
1606
- return buildModularChangesetFromField({
1607
- ...props,
1608
- path: { parent: path.parent, field: path.parentField },
1609
- fieldChange,
1610
- localCrossFieldKeys: [],
1611
- childId: nodeId,
1612
- });
1947
+ if (isDetachedUpPathRoot(path)) {
1948
+ props.rootNodes.nodeChanges.set([path.detachedNodeId.major, brand(path.detachedNodeId.minor)], nodeId);
1949
+ return makeModularChangeset({
1950
+ rebaseVersion: props.rebaseVersion,
1951
+ rootNodes: props.rootNodes,
1952
+ nodeChanges: props.nodeChanges,
1953
+ nodeToParent: props.nodeToParent,
1954
+ crossFieldKeys: props.crossFieldKeys,
1955
+ maxId: props.idAllocator.getMaxId(),
1956
+ revisions: [{ revision: props.revision }],
1957
+ });
1958
+ }
1959
+ else {
1960
+ const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChanges([
1961
+ [path.parentIndex, nodeId],
1962
+ ]);
1963
+ const fieldChange = {
1964
+ fieldKind: genericFieldKind.identifier,
1965
+ change: fieldChangeset,
1966
+ };
1967
+ return buildModularChangesetFromField({
1968
+ ...props,
1969
+ path: { parent: path.parent, field: path.parentField },
1970
+ fieldChange,
1971
+ localCrossFieldKeys: [],
1972
+ childId: nodeId,
1973
+ });
1974
+ }
1975
+ }
1976
+ function renameTableFromRenameDescriptions(renames) {
1977
+ const table = newRootTable();
1978
+ for (const rename of renames) {
1979
+ addNodeRename(table, rename.oldId, rename.newId, rename.count, rename.detachLocation);
1980
+ }
1981
+ return table;
1613
1982
  }
1614
1983
  function getRevInfoFromTaggedChanges(changes) {
1615
1984
  let maxId = -1;
@@ -1626,18 +1995,6 @@ function getRevInfoFromTaggedChanges(changes) {
1626
1995
  }
1627
1996
  }
1628
1997
  }
1629
- const rolledBackRevisions = [];
1630
- for (const info of revInfos) {
1631
- if (info.rollbackOf !== undefined) {
1632
- rolledBackRevisions.push(info.rollbackOf);
1633
- }
1634
- }
1635
- rolledBackRevisions.reverse();
1636
- for (const revision of rolledBackRevisions) {
1637
- if (!revisions.has(revision)) {
1638
- revInfos.push({ revision });
1639
- }
1640
- }
1641
1998
  return { maxId: brand(maxId), revInfos };
1642
1999
  }
1643
2000
  function revisionInfoFromTaggedChange(taggedChange) {
@@ -1654,15 +2011,16 @@ function revisionInfoFromTaggedChange(taggedChange) {
1654
2011
  }
1655
2012
  return revInfos;
1656
2013
  }
1657
- function fieldChangeFromId(fields, nodes, id) {
1658
- const fieldMap = fieldMapFromNodeId(fields, nodes, id.nodeId);
2014
+ function fieldChangeFromId(change, id) {
2015
+ const fieldId = normalizeFieldId(id, change.nodeAliases);
2016
+ const fieldMap = fieldMapFromNodeId(change.fieldChanges, change.nodeChanges, change.nodeAliases, fieldId.nodeId);
1659
2017
  return fieldMap.get(id.field) ?? fail(0xb25 /* No field exists for the given ID */);
1660
2018
  }
1661
- function fieldMapFromNodeId(rootFieldMap, nodes, nodeId) {
2019
+ function fieldMapFromNodeId(rootFieldMap, nodes, aliases, nodeId) {
1662
2020
  if (nodeId === undefined) {
1663
2021
  return rootFieldMap;
1664
2022
  }
1665
- const node = nodeChangeFromId(nodes, nodeId);
2023
+ const node = nodeChangeFromId(nodes, aliases, nodeId);
1666
2024
  assert(node.fieldChanges !== undefined, 0x9c9 /* Expected node to have field changes */);
1667
2025
  return node.fieldChanges;
1668
2026
  }
@@ -1675,8 +2033,9 @@ function rebasedFieldIdFromBaseId(table, baseId) {
1675
2033
  function rebasedNodeIdFromBaseNodeId(table, baseId) {
1676
2034
  return getFromChangeAtomIdMap(table.baseToRebasedNodeId, baseId) ?? baseId;
1677
2035
  }
1678
- function nodeChangeFromId(nodes, id) {
1679
- const node = getFromChangeAtomIdMap(nodes, id);
2036
+ function nodeChangeFromId(nodes, aliases, id) {
2037
+ const normalizedId = normalizeNodeId(id, aliases);
2038
+ const node = getFromChangeAtomIdMap(nodes, normalizedId);
1680
2039
  assert(node !== undefined, 0x9ca /* Unknown node ID */);
1681
2040
  return node;
1682
2041
  }
@@ -1684,12 +2043,20 @@ function fieldIdFromFieldIdKey([revision, localId, field]) {
1684
2043
  const nodeId = localId === undefined ? undefined : { revision, localId };
1685
2044
  return { nodeId, field };
1686
2045
  }
2046
+ function fieldIdKeyFromFieldId(fieldId) {
2047
+ return [fieldId.nodeId?.revision, fieldId.nodeId?.localId, fieldId.field];
2048
+ }
1687
2049
  function cloneNodeChangeset(nodeChangeset) {
1688
2050
  if (nodeChangeset.fieldChanges !== undefined) {
1689
2051
  return { ...nodeChangeset, fieldChanges: new Map(nodeChangeset.fieldChanges) };
1690
2052
  }
1691
2053
  return { ...nodeChangeset };
1692
2054
  }
2055
+ function replaceNodeLocationRevision(location, replacer) {
2056
+ return location.field === undefined
2057
+ ? { root: replacer.getUpdatedAtomId(location.root) }
2058
+ : { field: replaceFieldIdRevision(location.field, replacer) };
2059
+ }
1693
2060
  function replaceFieldIdRevision(fieldId, replacer) {
1694
2061
  if (fieldId.nodeId === undefined) {
1695
2062
  return fieldId;
@@ -1699,16 +2066,33 @@ function replaceFieldIdRevision(fieldId, replacer) {
1699
2066
  nodeId: replacer.getUpdatedAtomId(fieldId.nodeId),
1700
2067
  };
1701
2068
  }
1702
- export function getParentFieldId(changeset, nodeId) {
1703
- const parentId = getFromChangeAtomIdMap(changeset.nodeToParent, nodeId);
1704
- assert(parentId !== undefined, 0x9cb /* Parent field should be defined */);
1705
- return normalizeFieldId(parentId, changeset.nodeAliases);
2069
+ export function getNodeParent(changeset, nodeId) {
2070
+ const normalizedNodeId = normalizeNodeId(nodeId, changeset.nodeAliases);
2071
+ const location = getFromChangeAtomIdMap(changeset.nodeToParent, normalizedNodeId);
2072
+ assert(location !== undefined, 0x9cb /* Parent field should be defined */);
2073
+ if (location.field !== undefined) {
2074
+ return { field: normalizeFieldId(location.field, changeset.nodeAliases) };
2075
+ }
2076
+ return location;
1706
2077
  }
1707
2078
  function getFieldsForCrossFieldKey(changeset, key, count) {
1708
2079
  return changeset.crossFieldKeys
1709
2080
  .getAll(key, count)
1710
2081
  .map(({ value: fieldId }) => normalizeFieldId(fieldId, changeset.nodeAliases));
1711
2082
  }
2083
+ function getFirstFieldForCrossFieldKey(changeset, key, count) {
2084
+ const result = changeset.crossFieldKeys.getFirst(key, count);
2085
+ if (result.value === undefined) {
2086
+ return result;
2087
+ }
2088
+ return { ...result, value: normalizeFieldId(result.value, changeset.nodeAliases) };
2089
+ }
2090
+ function normalizeNodeLocation(location, nodeAliases) {
2091
+ if (location.field !== undefined) {
2092
+ return { field: normalizeFieldId(location.field, nodeAliases) };
2093
+ }
2094
+ return location;
2095
+ }
1712
2096
  // This is only exported for use in test utilities.
1713
2097
  export function normalizeFieldId(fieldId, nodeAliases) {
1714
2098
  return fieldId.nodeId === undefined
@@ -1718,8 +2102,9 @@ export function normalizeFieldId(fieldId, nodeAliases) {
1718
2102
  /**
1719
2103
  * @returns The canonical form of nodeId, according to nodeAliases
1720
2104
  */
1721
- function normalizeNodeId(nodeId, nodeAliases) {
2105
+ export function normalizeNodeId(nodeId, nodeAliases) {
1722
2106
  let currentId = nodeId;
2107
+ let cycleProbeId = nodeId;
1723
2108
  // eslint-disable-next-line no-constant-condition
1724
2109
  while (true) {
1725
2110
  const dealiased = getFromChangeAtomIdMap(nodeAliases, currentId);
@@ -1727,23 +2112,557 @@ function normalizeNodeId(nodeId, nodeAliases) {
1727
2112
  return currentId;
1728
2113
  }
1729
2114
  currentId = dealiased;
2115
+ if (cycleProbeId !== undefined) {
2116
+ cycleProbeId = getFromChangeAtomIdMap(nodeAliases, cycleProbeId);
2117
+ }
2118
+ if (cycleProbeId !== undefined) {
2119
+ cycleProbeId = getFromChangeAtomIdMap(nodeAliases, cycleProbeId);
2120
+ }
2121
+ assert(!areEqualChangeAtomIdOpts(cycleProbeId, currentId), "Alias cycle detected");
1730
2122
  }
1731
2123
  }
1732
2124
  function hasConflicts(change) {
1733
2125
  return (change.constraintViolationCount ?? 0) > 0;
1734
2126
  }
2127
+ function areEqualFieldIds(a, b) {
2128
+ return areEqualChangeAtomIdOpts(a.nodeId, b.nodeId) && a.field === b.field;
2129
+ }
2130
+ function firstAttachIdFromDetachId(roots, detachId, count) {
2131
+ const result = roots.oldToNewId.getFirst(detachId, count);
2132
+ return { ...result, value: result.value ?? detachId };
2133
+ }
2134
+ function firstDetachIdFromAttachId(roots, attachId, count) {
2135
+ const result = roots.newToOldId.getFirst(attachId, count);
2136
+ return { ...result, start: attachId, value: result.value ?? attachId };
2137
+ }
2138
+ function rebaseCrossFieldKeys(sourceTable, movedDetaches, newDetachLocations) {
2139
+ const rebasedTable = sourceTable.clone();
2140
+ for (const entry of movedDetaches.entries()) {
2141
+ rebasedTable.delete({ ...entry.start, target: NodeMoveType.Detach }, entry.length);
2142
+ }
2143
+ for (const entry of newDetachLocations.entries()) {
2144
+ rebasedTable.set({ ...entry.start, target: NodeMoveType.Detach }, entry.length, entry.value);
2145
+ }
2146
+ return rebasedTable;
2147
+ }
2148
+ export function newRootTable() {
2149
+ return {
2150
+ newToOldId: newChangeAtomIdTransform(),
2151
+ oldToNewId: newChangeAtomIdTransform(),
2152
+ nodeChanges: newChangeAtomIdBTree(),
2153
+ detachLocations: newChangeAtomIdRangeMap(),
2154
+ outputDetachLocations: newChangeAtomIdRangeMap(),
2155
+ };
2156
+ }
2157
+ function rebaseRoots(change, base, affectedBaseFields, nodesToRebase, rebasedNodeToParent, rebaseVersion) {
2158
+ const rebasedRoots = newRootTable();
2159
+ for (const renameEntry of change.rootNodes.oldToNewId.entries()) {
2160
+ rebaseRename(change.rootNodes, rebasedRoots, renameEntry, base, affectedBaseFields);
2161
+ }
2162
+ for (const [detachIdKey, nodeId] of change.rootNodes.nodeChanges.entries()) {
2163
+ const changes = base.rootNodes.nodeChanges.get(detachIdKey);
2164
+ if (changes !== undefined) {
2165
+ nodesToRebase.push([nodeId, changes]);
2166
+ }
2167
+ const detachId = makeChangeAtomId(detachIdKey[1], detachIdKey[0]);
2168
+ const attachId = firstAttachIdFromDetachId(base.rootNodes, detachId, 1).value;
2169
+ const baseAttachEntry = base.crossFieldKeys.getFirst({ target: NodeMoveType.Attach, ...attachId }, 1);
2170
+ if (baseAttachEntry.value === undefined) {
2171
+ const renamedDetachId = firstAttachIdFromDetachId(base.rootNodes, detachId, 1).value;
2172
+ const baseOutputDetachLocation = base.rootNodes.outputDetachLocations.getFirst(renamedDetachId, 1).value;
2173
+ if (baseOutputDetachLocation !== undefined) {
2174
+ affectedBaseFields.set(fieldIdKeyFromFieldId(baseOutputDetachLocation), true);
2175
+ }
2176
+ const detachLocation = baseOutputDetachLocation ??
2177
+ change.rootNodes.detachLocations.getFirst(detachId, 1).value;
2178
+ // Note that `baseOutputDetachLocation` may contain a node ID from the base changeset.
2179
+ // We will replace the detach location entry with the node ID from the rebased changeset in `fixupRebasedDetachLocations`
2180
+ assignRootChange(rebasedRoots, rebasedNodeToParent, renamedDetachId, nodeId, detachLocation, rebaseVersion);
2181
+ }
2182
+ else {
2183
+ affectedBaseFields.set(fieldIdKeyFromFieldId(baseAttachEntry.value), true);
2184
+ rebasedNodeToParent.delete(detachIdKey);
2185
+ }
2186
+ }
2187
+ for (const entry of change.rootNodes.outputDetachLocations.entries()) {
2188
+ rebasedRoots.outputDetachLocations.set(entry.start, entry.length, entry.value);
2189
+ }
2190
+ return rebasedRoots;
2191
+ }
2192
+ function rebaseRename(newRoots, rebasedRoots, renameEntry, base, affectedBaseFields) {
2193
+ let count = renameEntry.length;
2194
+ const baseRenameEntry = firstAttachIdFromDetachId(base.rootNodes, renameEntry.start, count);
2195
+ count = baseRenameEntry.length;
2196
+ const baseAttachEntry = base.crossFieldKeys.getFirst({
2197
+ ...baseRenameEntry.value,
2198
+ target: NodeMoveType.Attach,
2199
+ }, count);
2200
+ count = baseAttachEntry.length;
2201
+ if (baseAttachEntry.value === undefined) {
2202
+ const baseOutputDetachLocation = base.rootNodes.outputDetachLocations.getFirst(baseRenameEntry.value, 1).value;
2203
+ if (baseOutputDetachLocation !== undefined) {
2204
+ affectedBaseFields.set(fieldIdKeyFromFieldId(baseOutputDetachLocation), true);
2205
+ }
2206
+ const detachEntry = newRoots.detachLocations.getFirst(renameEntry.start, count);
2207
+ count = detachEntry.length;
2208
+ const detachLocation = baseOutputDetachLocation ?? detachEntry.value;
2209
+ // Note that `baseOutputDetachLocation` may contain a node ID from the base changeset.
2210
+ // We will replace the detach location entry with the node ID from the rebased changeset in `fixupRebasedDetachLocations`
2211
+ addNodeRename(rebasedRoots, baseRenameEntry.value, renameEntry.value, count, detachLocation);
2212
+ }
2213
+ else {
2214
+ // This rename represents an intention to detach these nodes.
2215
+ // The rebased change should have a detach in the field where the base change attaches the nodes,
2216
+ // so we need to ensure that field is processed.
2217
+ affectedBaseFields.set(fieldIdKeyFromFieldId(normalizeFieldId(baseAttachEntry.value, base.nodeAliases)), true);
2218
+ }
2219
+ const countRemaining = renameEntry.length - count;
2220
+ if (countRemaining > 0) {
2221
+ rebaseRename(newRoots, rebasedRoots, {
2222
+ start: offsetChangeAtomId(renameEntry.start, count),
2223
+ value: offsetChangeAtomId(renameEntry.value, count),
2224
+ length: countRemaining,
2225
+ }, base, affectedBaseFields);
2226
+ }
2227
+ }
1735
2228
  /**
1736
- * A rebaseChild callback for fields with no new changes.
1737
- * Asserts that there are no new changes and returns undefined.
2229
+ * For each root detach location, replaces any node ID from the base changeset
2230
+ * with the corresponding ID in the new changeset.
1738
2231
  */
1739
- function noNewChangesRebaseChild(child, _baseChild, _stateChange) {
1740
- assert(child === undefined, 0x9c3 /* There should be no new changes in this field */);
1741
- return undefined;
2232
+ function fixupRebasedDetachLocations(table) {
2233
+ for (const { start, length, value: detachLocation, } of table.rebasedRootNodes.detachLocations.entries()) {
2234
+ const normalizedDetachLocation = normalizeFieldId(detachLocation, table.baseChange.nodeAliases);
2235
+ if (normalizedDetachLocation.nodeId !== undefined) {
2236
+ const rebasedNodeId = getFromChangeAtomIdMap(table.baseToRebasedNodeId, normalizedDetachLocation.nodeId);
2237
+ if (rebasedNodeId !== undefined) {
2238
+ table.rebasedRootNodes.detachLocations.set(start, length, {
2239
+ ...normalizedDetachLocation,
2240
+ nodeId: rebasedNodeId,
2241
+ });
2242
+ }
2243
+ }
2244
+ }
1742
2245
  }
1743
- function areEqualFieldIds(a, b) {
1744
- return areEqualChangeAtomIdOpts(a.nodeId, b.nodeId) && a.field === b.field;
2246
+ function addNodesToCompose(table, id1, id2) {
2247
+ const normalizedId1 = normalizeNodeId(id1, table.baseChange.nodeAliases);
2248
+ const normalizedId2 = normalizeNodeId(id2, table.newChange.nodeAliases);
2249
+ if (getFromChangeAtomIdMap(table.newToBaseNodeId, normalizedId2) === undefined) {
2250
+ setInChangeAtomIdMap(table.newToBaseNodeId, normalizedId2, normalizedId1);
2251
+ table.pendingCompositions.nodeIdsToCompose.push([normalizedId1, normalizedId2]);
2252
+ }
2253
+ }
2254
+ function composeRevInfos(revisions1, revisions2) {
2255
+ if (revisions1?.length === 1 &&
2256
+ revisions2?.length === 1 &&
2257
+ revisions1[0]?.revision === revisions2[0]?.revision) {
2258
+ // This is a special case where we are composing two changesets from the same transaction.
2259
+ // We return one of the input arrays to avoid duplicating revision entries.
2260
+ return revisions1;
2261
+ }
2262
+ const result = [...(revisions1 ?? []), ...(revisions2 ?? [])];
2263
+ return result;
2264
+ }
2265
+ function composeCrossFieldKeyTables(table1, table2, movedCrossFieldKeys, removedCrossFieldKeys) {
2266
+ const composedTable = RangeMap.union(table1, table2);
2267
+ for (const entry of movedCrossFieldKeys.entries()) {
2268
+ composedTable.set(entry.start, entry.length, entry.value);
2269
+ }
2270
+ for (const entry of removedCrossFieldKeys.entries()) {
2271
+ composedTable.delete(entry.start, entry.length);
2272
+ }
2273
+ return composedTable;
2274
+ }
2275
+ function composeRootTables(change1, change2, composedNodeToParent, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions) {
2276
+ const composedTable = cloneRootTable(change1.rootNodes);
2277
+ for (const renameEntry of change2.rootNodes.oldToNewId.entries()) {
2278
+ composeRename(change1, change2, composedTable, renameEntry.start, renameEntry.value, renameEntry.length, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions);
2279
+ }
2280
+ for (const [[revision2, id2], nodeId2] of change2.rootNodes.nodeChanges.entries()) {
2281
+ const detachId2 = { revision: revision2, localId: id2 };
2282
+ const detachId1 = firstDetachIdFromAttachId(change1.rootNodes, detachId2, 1).value;
2283
+ const nodeId1 = getFromChangeAtomIdMap(change1.rootNodes.nodeChanges, detachId1);
2284
+ if (nodeId1 === undefined) {
2285
+ const fieldId = getFieldsForCrossFieldKey(change1, { ...detachId1, target: NodeMoveType.Detach }, 1)[0];
2286
+ if (fieldId === undefined) {
2287
+ assignRootChange(composedTable, composedNodeToParent, detachId1, nodeId2, change1.rootNodes.detachLocations.getFirst(detachId1, 1).value ??
2288
+ change2.rootNodes.detachLocations.getFirst(detachId2, 1).value, Math.max(change1.rebaseVersion, change2.rebaseVersion));
2289
+ }
2290
+ else {
2291
+ // In this case, this node is attached in the input context of change1,
2292
+ // and is represented in detachFieldId.
2293
+ pendingCompositions.affectedBaseFields.set([fieldId.nodeId?.revision, fieldId.nodeId?.localId, fieldId.field], true);
2294
+ }
2295
+ }
2296
+ else {
2297
+ pendingCompositions.nodeIdsToCompose.push([nodeId1, nodeId2]);
2298
+ }
2299
+ }
2300
+ for (const outputDetachEntry of change1.rootNodes.outputDetachLocations.entries()) {
2301
+ composeOutputDetachLocation(outputDetachEntry.start, outputDetachEntry.length, outputDetachEntry.value, change2, composedTable);
2302
+ }
2303
+ for (const entry of change2.rootNodes.outputDetachLocations.entries()) {
2304
+ composedTable.outputDetachLocations.set(entry.start, entry.length, entry.value);
2305
+ }
2306
+ return composedTable;
2307
+ }
2308
+ function composeOutputDetachLocation(outputDetachId1, count, detachLocation, change2, composedTable) {
2309
+ let countToProcess = count;
2310
+ const renameEntry = firstAttachIdFromDetachId(change2.rootNodes, outputDetachId1, countToProcess);
2311
+ countToProcess = renameEntry.length;
2312
+ const attachEntry = getFirstAttachField(change2.crossFieldKeys, renameEntry.value, countToProcess);
2313
+ countToProcess = attachEntry.length;
2314
+ composedTable.outputDetachLocations.delete(outputDetachId1, countToProcess);
2315
+ if (attachEntry.value === undefined) {
2316
+ // We update the key for the detach location to the renamed ID of the root in the composed output context.
2317
+ composedTable.outputDetachLocations.set(renameEntry.value, countToProcess, detachLocation);
2318
+ }
2319
+ else {
2320
+ // These nodes are attached by `change2` and thus attached in the composed output context,
2321
+ // so there should be no output detach location.
2322
+ }
2323
+ const countRemaining = count - countToProcess;
2324
+ if (countRemaining > 0) {
2325
+ composeOutputDetachLocation(offsetChangeAtomId(outputDetachId1, countToProcess), countRemaining, detachLocation, change2, composedTable);
2326
+ }
2327
+ }
2328
+ function composeRename(change1, change2, mergedTable, oldId, newId, count, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions) {
2329
+ let countToProcess = count;
2330
+ const detachEntry = getFirstDetachField(change1.crossFieldKeys, oldId, countToProcess);
2331
+ countToProcess = detachEntry.length;
2332
+ if (detachEntry.value === undefined) {
2333
+ // `change1` may also have a rename to `renameEntry.value`, in which case it must refer to a different node.
2334
+ // That node must have been attached by `change1` and detached by `change2`.
2335
+ // The final rename for that node will be created in `composeAttachDetach`.
2336
+ // We delete any such rename for now to avoid colliding with the rename currently being processed.
2337
+ deleteNodeRenameTo(mergedTable, newId, countToProcess);
2338
+ // The nodes were detached before `change`, so we append this rename.
2339
+ appendNodeRename(mergedTable, oldId, newId, countToProcess, change1.rootNodes, change2.rootNodes.detachLocations.getFirst(oldId, countToProcess).value);
2340
+ }
2341
+ else {
2342
+ // `change1` detached these nodes,
2343
+ // so we invalidate the detach location so that the detach's ID can be replaced with the new ID.
2344
+ pendingCompositions.affectedBaseFields.set(fieldIdKeyFromFieldId(detachEntry.value), true);
2345
+ if (!areEqualChangeAtomIds(oldId, newId)) {
2346
+ // `change1`'s detach will be replaced by `change2`'s detach, so we update the cross-field keys.
2347
+ removedCrossFieldKeys.set({ ...oldId, target: NodeMoveType.Detach }, countToProcess, true);
2348
+ }
2349
+ movedCrossFieldKeys.set({ ...newId, target: NodeMoveType.Detach }, countToProcess, detachEntry.value);
2350
+ }
2351
+ if (countToProcess < count) {
2352
+ composeRename(change1, change2, mergedTable, offsetChangeAtomId(oldId, countToProcess), offsetChangeAtomId(newId, countToProcess), count - countToProcess, movedCrossFieldKeys, removedCrossFieldKeys, pendingCompositions);
2353
+ }
2354
+ }
2355
+ export function cloneRootTable(table) {
2356
+ return {
2357
+ oldToNewId: table.oldToNewId.clone(),
2358
+ newToOldId: table.newToOldId.clone(),
2359
+ nodeChanges: brand(table.nodeChanges.clone()),
2360
+ detachLocations: table.detachLocations.clone(),
2361
+ outputDetachLocations: table.outputDetachLocations.clone(),
2362
+ };
2363
+ }
2364
+ function invertRootTable(change, invertedNodeToParent, isRollback) {
2365
+ const invertedRoots = newRootTable();
2366
+ if (isRollback) {
2367
+ // We only invert renames of nodes which are not attached or detached by this changeset.
2368
+ // When we invert an attach we will create a detach which incorporates the rename.
2369
+ for (const { start: oldId, value: newId, length, } of change.rootNodes.oldToNewId.entries()) {
2370
+ invertRename(change, invertedRoots, oldId, newId, length);
2371
+ }
2372
+ }
2373
+ for (const [[revision, localId], nodeId] of change.rootNodes.nodeChanges.entries()) {
2374
+ const detachId = { revision, localId };
2375
+ const renamedId = firstAttachIdFromDetachId(change.rootNodes, detachId, 1).value;
2376
+ // This checks whether `change` attaches this node.
2377
+ // If it does, the node is not detached in the input context of the inverse, and so should not be included in the root table.
2378
+ if (change.crossFieldKeys.getFirst({ ...renamedId, target: NodeMoveType.Attach }, 1)
2379
+ .value === undefined) {
2380
+ assignRootChange(invertedRoots, invertedNodeToParent, renamedId, nodeId, change.rootNodes.detachLocations.getFirst(detachId, 1).value, change.rebaseVersion);
2381
+ }
2382
+ }
2383
+ return invertedRoots;
2384
+ }
2385
+ function invertRename(change, invertedRoots, oldId, newId, length) {
2386
+ for (const detachEntry of doesChangeDetachNodes(change.crossFieldKeys, newId, length)) {
2387
+ assert(!detachEntry.value, "A changeset should not have a rename and detach for the same node.");
2388
+ }
2389
+ let countProcessed = length;
2390
+ const outputDetachEntry = change.rootNodes.outputDetachLocations.getFirst(newId, countProcessed);
2391
+ countProcessed = outputDetachEntry.length;
2392
+ const inputDetachEntry = change.rootNodes.detachLocations.getFirst(oldId, countProcessed);
2393
+ countProcessed = inputDetachEntry.length;
2394
+ const attachEntry = getFirstAttachField(change.crossFieldKeys, newId, countProcessed);
2395
+ countProcessed = attachEntry.length;
2396
+ if (attachEntry.value === undefined) {
2397
+ addNodeRename(invertedRoots, newId, oldId, countProcessed, outputDetachEntry.value ?? inputDetachEntry.value);
2398
+ // The original change moves the detached node, so the inverse should also record a move back to the original location.
2399
+ if (outputDetachEntry.value !== undefined && inputDetachEntry.value !== undefined) {
2400
+ invertedRoots.outputDetachLocations.set(oldId, countProcessed, inputDetachEntry.value);
2401
+ }
2402
+ }
2403
+ if (countProcessed < length) {
2404
+ invertRename(change, invertedRoots, offsetChangeAtomId(oldId, countProcessed), offsetChangeAtomId(newId, countProcessed), length - countProcessed);
2405
+ }
2406
+ }
2407
+ function doesChangeAttachNodes(table, id, count) {
2408
+ return table
2409
+ .getAll2({ ...id, target: NodeMoveType.Attach }, count)
2410
+ .map((entry) => ({ ...entry, value: entry.value !== undefined }));
2411
+ }
2412
+ function doesChangeDetachNodes(table, id, count) {
2413
+ return table
2414
+ .getAll2({ ...id, target: NodeMoveType.Detach }, count)
2415
+ .map((entry) => ({ ...entry, value: entry.value !== undefined }));
2416
+ }
2417
+ export function getFirstDetachField(table, id, count) {
2418
+ return table.getFirst({ target: NodeMoveType.Detach, ...id }, count);
2419
+ }
2420
+ export function getFirstAttachField(table, id, count) {
2421
+ return table.getFirst({ target: NodeMoveType.Attach, ...id }, count);
2422
+ }
2423
+ export function addNodeRename(table, oldId, newId, count, detachLocation) {
2424
+ if (areEqualChangeAtomIds(oldId, newId)) {
2425
+ return;
2426
+ }
2427
+ for (const entry of table.oldToNewId.getAll2(oldId, count)) {
2428
+ assert(entry.value === undefined ||
2429
+ areEqualChangeAtomIds(entry.value, offsetChangeAtomId(newId, entry.offset)), "Rename collision detected");
2430
+ }
2431
+ for (const entry of table.newToOldId.getAll2(newId, count)) {
2432
+ assert(entry.value === undefined ||
2433
+ areEqualChangeAtomIds(entry.value, offsetChangeAtomId(oldId, entry.offset)), "Rename collision detected");
2434
+ }
2435
+ table.oldToNewId.set(oldId, count, newId);
2436
+ table.newToOldId.set(newId, count, oldId);
2437
+ if (detachLocation !== undefined) {
2438
+ table.detachLocations.set(oldId, count, detachLocation);
2439
+ }
2440
+ }
2441
+ /**
2442
+ * Deletes any renames from `id`.
2443
+ */
2444
+ function deleteNodeRenameFrom(roots, id, count) {
2445
+ for (const entry of roots.oldToNewId.getAll(id, count)) {
2446
+ deleteNodeRenameEntry(roots, entry.start, entry.value, entry.length);
2447
+ }
2448
+ }
2449
+ /**
2450
+ * Deletes any renames to `id`.
2451
+ */
2452
+ function deleteNodeRenameTo(roots, id, count) {
2453
+ for (const entry of roots.newToOldId.getAll(id, count)) {
2454
+ deleteNodeRenameEntry(roots, entry.value, entry.start, entry.length);
2455
+ }
2456
+ }
2457
+ function appendNodeRename(composedTable, oldId, newId, count, change1Table, detachLocation) {
2458
+ let countToProcess = count;
2459
+ const rename1Entry = change1Table.newToOldId.getFirst(oldId, countToProcess);
2460
+ countToProcess = rename1Entry.length;
2461
+ if (rename1Entry.value !== undefined) {
2462
+ deleteNodeRenameFrom(composedTable, rename1Entry.value, countToProcess);
2463
+ }
2464
+ addNodeRename(composedTable, rename1Entry.value ?? oldId, newId, countToProcess, detachLocation);
2465
+ tryRemoveDetachLocation(composedTable, newId, countToProcess);
2466
+ if (countToProcess < count) {
2467
+ const countRemaining = count - countToProcess;
2468
+ appendNodeRename(composedTable, offsetChangeAtomId(oldId, countToProcess), offsetChangeAtomId(newId, countToProcess), countRemaining, change1Table, detachLocation);
2469
+ }
2470
+ }
2471
+ function tryRemoveDetachLocation(roots, rootId, count) {
2472
+ let countProcessed = count;
2473
+ const renameEntry = roots.oldToNewId.getFirst(rootId, countProcessed);
2474
+ countProcessed = renameEntry.length;
2475
+ const outputDetachEntry = roots.outputDetachLocations.getFirst(rootId, countProcessed);
2476
+ countProcessed = outputDetachEntry.length;
2477
+ const nodeChangeEntry = rangeQueryChangeAtomIdMap(roots.nodeChanges, rootId, countProcessed);
2478
+ countProcessed = nodeChangeEntry.length;
2479
+ if (nodeChangeEntry.value === undefined &&
2480
+ renameEntry.value === undefined &&
2481
+ outputDetachEntry.value === undefined) {
2482
+ roots.detachLocations.delete(rootId, countProcessed);
2483
+ }
2484
+ const countRemaining = count - countProcessed;
2485
+ if (countRemaining > 0) {
2486
+ tryRemoveDetachLocation(roots, offsetChangeAtomId(rootId, countProcessed), countRemaining);
2487
+ }
2488
+ }
2489
+ /**
2490
+ * Deletes the entry renaming the ID range of length `count` from `oldId` to `newId`.
2491
+ * This function assumes that such an entry exists.
2492
+ */
2493
+ function deleteNodeRenameEntry(roots, oldId, newId, count) {
2494
+ roots.oldToNewId.delete(oldId, count);
2495
+ roots.newToOldId.delete(newId, count);
2496
+ }
2497
+ function replaceRootTableRevision(table, replacer, nodeAliases) {
2498
+ const oldToNewId = table.oldToNewId.mapEntries((id) => replacer.getUpdatedAtomId(id), (id) => replacer.getUpdatedAtomId(id));
2499
+ const newToOldId = table.newToOldId.mapEntries((id) => replacer.getUpdatedAtomId(id), (id) => replacer.getUpdatedAtomId(id));
2500
+ const nodeChanges = replaceIdMapRevisions(table.nodeChanges, replacer, (nodeId) => replacer.getUpdatedAtomId(normalizeNodeId(nodeId, nodeAliases)));
2501
+ const detachLocations = table.detachLocations.mapEntries((id) => replacer.getUpdatedAtomId(id), (fieldId) => replaceFieldIdRevision(normalizeFieldId(fieldId, nodeAliases), replacer));
2502
+ const outputDetachLocations = table.outputDetachLocations.mapEntries((id) => replacer.getUpdatedAtomId(id), (fieldId) => replaceFieldIdRevision(normalizeFieldId(fieldId, nodeAliases), replacer));
2503
+ return { oldToNewId, newToOldId, nodeChanges, detachLocations, outputDetachLocations };
2504
+ }
2505
+ function newDetachedEntryMap() {
2506
+ return new RangeMap(offsetChangeAtomId, subtractChangeAtomIds, offsetDetachedNodeEntry);
2507
+ }
2508
+ function offsetDetachedNodeEntry(entry, count) {
2509
+ assert(count <= 1 || entry.nodeChange === undefined, "Cannot split an entry with a node change");
2510
+ return entry.detachId === undefined
2511
+ ? entry
2512
+ : { ...entry, detachId: offsetChangeAtomId(entry.detachId, count) };
2513
+ }
2514
+ function getFieldsWithRootMoves(roots, nodeAliases) {
2515
+ const fields = newFieldIdKeyBTree();
2516
+ for (const { start: rootId, value: fieldId, length } of roots.detachLocations.entries()) {
2517
+ let isRootMoved = false;
2518
+ for (const renameEntry of roots.oldToNewId.getAll2(rootId, length)) {
2519
+ if (renameEntry.value !== undefined) {
2520
+ isRootMoved = true;
2521
+ }
2522
+ }
2523
+ for (const outputDetachEntry of roots.outputDetachLocations.getAll2(rootId, length)) {
2524
+ if (outputDetachEntry.value !== undefined) {
2525
+ isRootMoved = true;
2526
+ }
2527
+ }
2528
+ if (isRootMoved) {
2529
+ fields.set(fieldIdKeyFromFieldId(normalizeFieldId(fieldId, nodeAliases)), true);
2530
+ }
2531
+ }
2532
+ return fields;
2533
+ }
2534
+ function getFieldToRootChanges(roots, nodeAliases) {
2535
+ const fields = newFieldIdKeyBTree();
2536
+ for (const rootIdKey of roots.nodeChanges.keys()) {
2537
+ const rootId = { revision: rootIdKey[0], localId: rootIdKey[1] };
2538
+ const detachLocation = roots.detachLocations.getFirst(rootId, 1).value;
2539
+ if (detachLocation !== undefined) {
2540
+ const fieldIdKey = fieldIdKeyFromFieldId(normalizeFieldId(detachLocation, nodeAliases));
2541
+ let rootsInField = fields.get(fieldIdKey);
2542
+ if (rootsInField === undefined) {
2543
+ rootsInField = [];
2544
+ fields.set(fieldIdKey, rootsInField);
2545
+ }
2546
+ rootsInField.push(rootId);
2547
+ }
2548
+ }
2549
+ return fields;
2550
+ }
2551
+ function muteRootChanges(roots) {
2552
+ return {
2553
+ oldToNewId: newChangeAtomIdTransform(),
2554
+ newToOldId: newChangeAtomIdTransform(),
2555
+ nodeChanges: brand(roots.nodeChanges.clone()),
2556
+ detachLocations: roots.detachLocations.clone(),
2557
+ outputDetachLocations: newChangeAtomIdRangeMap(),
2558
+ };
2559
+ }
2560
+ export function validateChangeset(change, fieldKinds) {
2561
+ const unreachableNodes = brand(change.nodeToParent.clone());
2562
+ const unreachableCFKs = change.crossFieldKeys.clone();
2563
+ validateFieldChanges(fieldKinds, change, change.fieldChanges, undefined, unreachableNodes, unreachableCFKs);
2564
+ for (const [[revision, localId], node] of change.nodeChanges.entries()) {
2565
+ if (node.fieldChanges === undefined) {
2566
+ continue;
2567
+ }
2568
+ const nodeId = normalizeNodeId({ revision, localId }, change.nodeAliases);
2569
+ validateFieldChanges(fieldKinds, change, node.fieldChanges, nodeId, unreachableNodes, unreachableCFKs);
2570
+ }
2571
+ for (const [detachIdKey, nodeId] of change.rootNodes.nodeChanges.entries()) {
2572
+ const detachId = { revision: detachIdKey[0], localId: detachIdKey[1] };
2573
+ const location = getNodeParent(change, nodeId);
2574
+ assert(areEqualChangeAtomIdOpts(location.root, detachId), "Inconsistent node location");
2575
+ const normalizedNodeId = normalizeNodeId(nodeId, change.nodeAliases);
2576
+ unreachableNodes.delete([normalizedNodeId.revision, normalizedNodeId.localId]);
2577
+ const fieldChanges = nodeChangeFromId(change.nodeChanges, change.nodeAliases, nodeId).fieldChanges;
2578
+ if (fieldChanges !== undefined) {
2579
+ validateFieldChanges(fieldKinds, change, fieldChanges, normalizedNodeId, unreachableNodes, unreachableCFKs);
2580
+ }
2581
+ }
2582
+ if (!containsRollbacks(change)) {
2583
+ for (const entry of change.crossFieldKeys.entries()) {
2584
+ if (entry.start.target !== NodeMoveType.Attach) {
2585
+ continue;
2586
+ }
2587
+ validateAttach(change, entry.start, entry.length);
2588
+ }
2589
+ }
2590
+ assert(unreachableNodes.size === 0, "Unreachable nodes found");
2591
+ assert(unreachableCFKs.entries().length === 0, "Unreachable cross-field keys found");
2592
+ }
2593
+ function containsRollbacks(change) {
2594
+ if (change.revisions === undefined) {
2595
+ return false;
2596
+ }
2597
+ for (const revInfo of change.revisions) {
2598
+ if (revInfo.rollbackOf !== undefined) {
2599
+ return true;
2600
+ }
2601
+ }
2602
+ return false;
2603
+ }
2604
+ function validateAttach(changeset, attachId, count) {
2605
+ let countProcessed = count;
2606
+ const buildEntry = hasBuildForIdRange(changeset.builds, attachId, count);
2607
+ countProcessed = buildEntry.length;
2608
+ const detachEntry = changeset.crossFieldKeys.getFirst({ ...attachId, target: NodeMoveType.Detach }, countProcessed);
2609
+ countProcessed = detachEntry.length;
2610
+ const renameEntry = changeset.rootNodes.newToOldId.getFirst(attachId, countProcessed);
2611
+ countProcessed = renameEntry.length;
2612
+ // assert(
2613
+ // buildEntry.value || detachEntry.value !== undefined || renameEntry.value !== undefined,
2614
+ // "No build, detach, or rename found for attach",
2615
+ // );
2616
+ if (countProcessed < count) {
2617
+ validateAttach(changeset, offsetChangeAtomId(attachId, countProcessed), count - countProcessed);
2618
+ }
2619
+ }
2620
+ function hasBuildForIdRange(builds, id, count) {
2621
+ if (builds === undefined) {
2622
+ return { value: false, length: count };
2623
+ }
2624
+ const prevBuildEntry = builds.nextLowerPair([id.revision, id.localId]);
2625
+ if (prevBuildEntry !== undefined) {
2626
+ const prevBuildKey = {
2627
+ revision: prevBuildEntry[0][0],
2628
+ localId: prevBuildEntry[0][1],
2629
+ };
2630
+ const prevBuildLength = prevBuildEntry[1].topLevelLength;
2631
+ const lastLocalId = prevBuildKey.localId + prevBuildLength - 1;
2632
+ if (prevBuildKey.revision === id.revision && lastLocalId >= id.localId) {
2633
+ return { value: true, length: Math.min(count, lastLocalId - id.localId + 1) };
2634
+ }
2635
+ }
2636
+ const buildEntry = rangeQueryChangeAtomIdMap(builds, id, count);
2637
+ const length = buildEntry.value === undefined ? buildEntry.length : buildEntry.value.topLevelLength;
2638
+ const hasBuild = buildEntry.value !== undefined;
2639
+ return { value: hasBuild, length };
2640
+ }
2641
+ /**
2642
+ * Asserts that each node has a correct entry in `change.nodeToParent`,
2643
+ * and each cross field key has a correct entry in `change.crossFieldKeys`.
2644
+ * @returns the number of children found.
2645
+ */
2646
+ function validateFieldChanges(fieldKinds, change, fieldChanges, nodeParent, unreachableNodes, unreachableCFKs) {
2647
+ for (const [field, fieldChange] of fieldChanges.entries()) {
2648
+ const fieldId = { nodeId: nodeParent, field };
2649
+ const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
2650
+ for (const [child, _index] of handler.getNestedChanges(fieldChange.change)) {
2651
+ const parentFieldId = getNodeParent(change, child);
2652
+ assert(parentFieldId.field !== undefined && areEqualFieldIds(parentFieldId.field, fieldId), 0xa4e /* Inconsistent node parentage */);
2653
+ unreachableNodes.delete([child.revision, child.localId]);
2654
+ }
2655
+ for (const keyRange of handler.getCrossFieldKeys(fieldChange.change)) {
2656
+ const fields = getFieldsForCrossFieldKey(change, keyRange.key, keyRange.count);
2657
+ assert(fields.length > 0, "Unregistered cross-field key");
2658
+ for (const fieldFromLookup of fields) {
2659
+ assert(areEqualFieldIds(fieldFromLookup, fieldId), 0xa4f /* Inconsistent cross field keys */);
2660
+ }
2661
+ unreachableCFKs.delete(keyRange.key, keyRange.count);
2662
+ }
2663
+ }
1745
2664
  }
1746
- function newFieldIdKeyBTree() {
2665
+ export function newFieldIdKeyBTree() {
1747
2666
  return newTupleBTree(compareFieldIdKeys);
1748
2667
  }
1749
2668
  const compareFieldIdKeys = createTupleComparator([