@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
@@ -28,12 +28,10 @@ import {
28
28
  EditBuilder,
29
29
  type FieldKey,
30
30
  type FieldKindIdentifier,
31
- type FieldUpPath,
32
31
  type RevisionInfo,
33
32
  type RevisionMetadataSource,
34
33
  type RevisionTag,
35
34
  type TaggedChange,
36
- type UpPath,
37
35
  makeDetachedNodeId,
38
36
  revisionMetadataSourceFromInfo,
39
37
  areEqualChangeAtomIds,
@@ -41,10 +39,18 @@ import {
41
39
  areEqualChangeAtomIdOpts,
42
40
  tagChange,
43
41
  makeAnonChange,
44
- newChangeAtomIdRangeMap,
45
42
  type DeltaDetachedNodeChanges,
46
43
  type DeltaDetachedNodeRename,
47
44
  mapTaggedChange,
45
+ newChangeAtomIdRangeMap,
46
+ newChangeAtomIdTransform,
47
+ type ChangeAtomIdRangeMap,
48
+ offsetChangeAtomId,
49
+ type NormalizedUpPath,
50
+ type NormalizedFieldUpPath,
51
+ isDetachedUpPathRoot,
52
+ subtractChangeAtomIds,
53
+ makeChangeAtomId,
48
54
  type RevisionReplacer,
49
55
  comparePartialRevisions,
50
56
  comparePartialChangesetLocalIds,
@@ -62,12 +68,15 @@ import {
62
68
  type TupleBTree,
63
69
  RangeMap,
64
70
  balancedReduce,
65
- newTupleBTree,
66
71
  compareStrings,
67
72
  createTupleComparator,
73
+ type RangeQueryEntry,
74
+ type RangeQueryResultFragment,
75
+ newTupleBTree,
68
76
  } from "../../util/index.js";
69
77
  import {
70
78
  getFromChangeAtomIdMap,
79
+ rangeQueryChangeAtomIdMap,
71
80
  newChangeAtomIdBTree,
72
81
  setInChangeAtomIdMap,
73
82
  type ChangeAtomIdBTree,
@@ -75,10 +84,13 @@ import {
75
84
  import type { TreeChunk } from "../chunked-forest/index.js";
76
85
 
77
86
  import {
78
- type CrossFieldManager,
87
+ type ComposeNodeManager,
79
88
  type CrossFieldMap,
80
- CrossFieldTarget,
81
- getFirstFromCrossFieldMap,
89
+ NodeMoveType,
90
+ type DetachedNodeEntry,
91
+ type InvertNodeManager,
92
+ type RebaseDetachedNodeEntry,
93
+ type RebaseNodeManager,
82
94
  setInCrossFieldMap,
83
95
  } from "./crossFieldQueries.js";
84
96
  import {
@@ -93,15 +105,20 @@ import {
93
105
  type CrossFieldKey,
94
106
  type CrossFieldKeyRange,
95
107
  type CrossFieldKeyTable,
108
+ type CrossFieldRangeTable,
96
109
  type FieldChange,
97
110
  type FieldChangeMap,
98
111
  type FieldChangeset,
99
112
  type FieldId,
100
113
  type ModularChangeset,
101
- newCrossFieldKeyTable,
114
+ type ModularEditorOptions,
115
+ newCrossFieldRangeTable,
102
116
  type NoChangeConstraint,
103
117
  type NodeChangeset,
104
118
  type NodeId,
119
+ type NodeLocation,
120
+ type RebaseVersion,
121
+ type RootNodeTable,
105
122
  } from "./modularChangeTypes.js";
106
123
 
107
124
  /**
@@ -188,14 +205,14 @@ export class ModularChangeFamily
188
205
  }
189
206
 
190
207
  public compose(changes: TaggedChange<ModularChangeset>[]): ModularChangeset {
191
- const { revInfos, maxId } = getRevInfoFromTaggedChanges(changes);
208
+ const { maxId } = getRevInfoFromTaggedChanges(changes);
192
209
  const idState: IdAllocationState = { maxId };
193
210
 
194
211
  const pairwiseDelegate = (
195
212
  left: ModularChangeset,
196
213
  right: ModularChangeset,
197
214
  ): ModularChangeset => {
198
- return this.composePair(left, right, revInfos, idState);
215
+ return this.composePair(left, right, idState);
199
216
  };
200
217
 
201
218
  const innerChanges = changes.map((change) => change.change);
@@ -205,10 +222,11 @@ export class ModularChangeFamily
205
222
  private composePair(
206
223
  change1: ModularChangeset,
207
224
  change2: ModularChangeset,
208
- revInfos: RevisionInfo[],
209
225
  idState: IdAllocationState,
210
226
  ): ModularChangeset {
211
- const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys } =
227
+ const revInfos = composeRevInfos(change1.revisions, change2.revisions);
228
+
229
+ const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys, rootNodes } =
212
230
  this.composeAllFields(change1, change2, revInfos, idState);
213
231
 
214
232
  const { allBuilds, allDestroys, allRefreshers } = composeBuildsDestroysAndRefreshers(
@@ -221,7 +239,8 @@ export class ModularChangeFamily
221
239
  const noChangeConstraintOnRevert =
222
240
  change1.noChangeConstraintOnRevert ?? change2.noChangeConstraintOnRevert;
223
241
 
224
- return makeModularChangeset({
242
+ const composed = makeModularChangeset({
243
+ rebaseVersion: Math.max(change1.rebaseVersion, change2.rebaseVersion) as RebaseVersion,
225
244
  fieldChanges,
226
245
  nodeChanges,
227
246
  nodeToParent,
@@ -229,18 +248,23 @@ export class ModularChangeFamily
229
248
  crossFieldKeys,
230
249
  maxId: idState.maxId,
231
250
  revisions: revInfos,
232
- noChangeConstraint,
233
- noChangeConstraintOnRevert,
251
+ rootNodes,
234
252
  builds: allBuilds,
235
253
  destroys: allDestroys,
236
254
  refreshers: allRefreshers,
255
+ noChangeConstraint,
256
+ noChangeConstraintOnRevert,
237
257
  });
258
+
259
+ // XXX: This is an expensive assert which should be disabled before merging.
260
+ validateChangeset(composed, this.fieldKinds);
261
+ return composed;
238
262
  }
239
263
 
240
264
  private composeAllFields(
241
265
  potentiallyConflictedChange1: ModularChangeset,
242
266
  potentiallyConflictedChange2: ModularChangeset,
243
- revInfos: RevisionInfo[],
267
+ revInfos: readonly RevisionInfo[],
244
268
  idState: IdAllocationState,
245
269
  ): ModularChangesetContent {
246
270
  // Our current cell ordering scheme in sequences depends on being able to rebase over a change with conflicts.
@@ -264,14 +288,38 @@ export class ModularChangeFamily
264
288
  mergeTupleBTrees(change1.nodeChanges, change2.nodeChanges),
265
289
  );
266
290
 
267
- const composedNodeToParent: ChangeAtomIdBTree<FieldId> = brand(
291
+ const composedNodeToParent: ChangeAtomIdBTree<NodeLocation> = brand(
268
292
  mergeTupleBTrees(change1.nodeToParent, change2.nodeToParent),
269
293
  );
270
294
  const composedNodeAliases: ChangeAtomIdBTree<NodeId> = brand(
271
295
  mergeTupleBTrees(change1.nodeAliases, change2.nodeAliases),
272
296
  );
273
297
 
274
- const crossFieldTable = newComposeTable(change1, change2, composedNodeToParent);
298
+ const pendingCompositions: PendingCompositions = {
299
+ nodeIdsToCompose: [],
300
+ affectedBaseFields: newFieldIdKeyBTree(),
301
+ };
302
+
303
+ const movedCrossFieldKeys: CrossFieldKeyTable = newCrossFieldRangeTable();
304
+ const removedCrossFieldKeys: CrossFieldRangeTable<boolean> = newCrossFieldRangeTable();
305
+
306
+ const composedRoots = composeRootTables(
307
+ change1,
308
+ change2,
309
+ composedNodeToParent,
310
+ movedCrossFieldKeys,
311
+ removedCrossFieldKeys,
312
+ pendingCompositions,
313
+ );
314
+
315
+ const crossFieldTable = newComposeTable(
316
+ change1,
317
+ change2,
318
+ composedRoots,
319
+ movedCrossFieldKeys,
320
+ removedCrossFieldKeys,
321
+ pendingCompositions,
322
+ );
275
323
 
276
324
  const composedFields = this.composeFieldMaps(
277
325
  change1.fieldChanges,
@@ -292,17 +340,32 @@ export class ModularChangeFamily
292
340
  revisionMetadata,
293
341
  );
294
342
 
295
- // Currently no field kinds require making changes to cross-field keys during composition, so we can just merge the two tables.
296
- const composedCrossFieldKeys = RangeMap.union(
297
- change1.crossFieldKeys,
298
- change2.crossFieldKeys,
299
- );
343
+ for (const entry of crossFieldTable.renamesToDelete.entries()) {
344
+ deleteNodeRenameFrom(crossFieldTable.composedRootNodes, entry.start, entry.length);
345
+ }
346
+
347
+ for (const [nodeId, location] of crossFieldTable.movedNodeToParent.entries()) {
348
+ // Moved nodes are from change2.
349
+ // If there is a corresponding node in change1, then composedNodeToParent will already have the correct entry,
350
+ // because the location of the node is the same in change1 and the composed change
351
+ // (since they have the same input context).
352
+ if (crossFieldTable.newToBaseNodeId.get(nodeId) === undefined) {
353
+ composedNodeToParent.set(nodeId, location);
354
+ }
355
+ }
356
+
300
357
  return {
301
358
  fieldChanges: composedFields,
302
359
  nodeChanges: composedNodeChanges,
303
360
  nodeToParent: composedNodeToParent,
304
361
  nodeAliases: composedNodeAliases,
305
- crossFieldKeys: composedCrossFieldKeys,
362
+ crossFieldKeys: composeCrossFieldKeyTables(
363
+ change1.crossFieldKeys,
364
+ change2.crossFieldKeys,
365
+ crossFieldTable.movedCrossFieldKeys,
366
+ crossFieldTable.removedCrossFieldKeys,
367
+ ),
368
+ rootNodes: composedRoots,
306
369
  };
307
370
  }
308
371
 
@@ -314,17 +377,16 @@ export class ModularChangeFamily
314
377
  ): void {
315
378
  const context = crossFieldTable.fieldToContext.get(fieldChange);
316
379
  assert(context !== undefined, 0x8cc /* Should have context for every invalidated field */);
317
- const { fieldId, change1: fieldChange1, change2: fieldChange2, composedChange } = context;
380
+ const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
381
+
382
+ crossFieldTable.pendingCompositions.affectedBaseFields.delete(
383
+ fieldIdKeyFromFieldId(context.fieldId),
384
+ );
318
385
 
319
386
  const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
320
387
  const composeNodes = (child1: NodeId | undefined, child2: NodeId | undefined): NodeId => {
321
- if (
322
- child1 !== undefined &&
323
- child2 !== undefined &&
324
- getFromChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2) === undefined
325
- ) {
326
- setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
327
- crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
388
+ if (child1 !== undefined && child2 !== undefined) {
389
+ addNodesToCompose(crossFieldTable, child1, child2);
328
390
  }
329
391
 
330
392
  return child1 ?? child2 ?? fail(0xb22 /* Should not compose two undefined nodes */);
@@ -335,7 +397,7 @@ export class ModularChangeFamily
335
397
  fieldChange2,
336
398
  composeNodes,
337
399
  genId,
338
- new ComposeManager(crossFieldTable, fieldChange, fieldId, false),
400
+ new ComposeNodeManagerI(crossFieldTable, context.fieldId, false),
339
401
  revisionMetadata,
340
402
  );
341
403
  composedChange.change = brand(amendedChange);
@@ -347,7 +409,7 @@ export class ModularChangeFamily
347
409
  * - discovering that two node changesets refer to the same node (`nodeIdsToCompose`)
348
410
  * - a previously composed field being invalidated by a cross field effect (`invalidatedFields`)
349
411
  * - a field which was copied directly from an input changeset being invalidated by a cross field effect
350
- * (`affectedBaseFields` and `affectedNewFields`)
412
+ * (`affectedBaseFields`)
351
413
  *
352
414
  * Updating an element may invalidate further elements. This function runs until there is no more invalidation.
353
415
  */
@@ -355,72 +417,59 @@ export class ModularChangeFamily
355
417
  table: ComposeTable,
356
418
  composedFields: FieldChangeMap,
357
419
  composedNodes: ChangeAtomIdBTree<NodeChangeset>,
358
- composedNodeToParent: ChangeAtomIdBTree<FieldId>,
420
+ composedNodeToParent: ChangeAtomIdBTree<NodeLocation>,
359
421
  nodeAliases: ChangeAtomIdBTree<NodeId>,
360
422
  genId: IdAllocator,
361
423
  metadata: RevisionMetadataSource,
362
424
  ): void {
363
425
  const pending = table.pendingCompositions;
364
- while (
365
- table.invalidatedFields.size > 0 ||
366
- pending.nodeIdsToCompose.length > 0 ||
367
- pending.affectedBaseFields.length > 0 ||
368
- pending.affectedNewFields.length > 0
369
- ) {
370
- // Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
371
- for (const [id1, id2] of pending.nodeIdsToCompose) {
372
- this.composeNodesById(
373
- table.baseChange.nodeChanges,
374
- table.newChange.nodeChanges,
375
- composedNodes,
376
- composedNodeToParent,
377
- nodeAliases,
378
- id1,
379
- id2,
380
- genId,
381
- table,
382
- metadata,
383
- );
384
- }
385
-
386
- pending.nodeIdsToCompose.length = 0;
387
-
388
- this.composeAffectedFields(
426
+ while (pending.nodeIdsToCompose.length > 0 || pending.affectedBaseFields.length > 0) {
427
+ this.processPendingNodeCompositions(
389
428
  table,
390
- table.baseChange,
391
- true,
392
- pending.affectedBaseFields,
393
- composedFields,
394
429
  composedNodes,
430
+ composedNodeToParent,
431
+ nodeAliases,
395
432
  genId,
396
433
  metadata,
397
434
  );
398
435
 
399
436
  this.composeAffectedFields(
400
437
  table,
401
- table.newChange,
402
- false,
403
- pending.affectedNewFields,
438
+ table.baseChange,
439
+ pending.affectedBaseFields,
404
440
  composedFields,
405
441
  composedNodes,
406
442
  genId,
407
443
  metadata,
408
444
  );
409
-
410
- this.processInvalidatedCompositions(table, genId, metadata);
411
445
  }
412
446
  }
413
447
 
414
- private processInvalidatedCompositions(
448
+ private processPendingNodeCompositions(
415
449
  table: ComposeTable,
450
+ composedNodes: ChangeAtomIdBTree<NodeChangeset>,
451
+ composedNodeToParent: ChangeAtomIdBTree<NodeLocation>,
452
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
416
453
  genId: IdAllocator,
417
454
  metadata: RevisionMetadataSource,
418
455
  ): void {
419
- const fieldsToUpdate = table.invalidatedFields;
420
- table.invalidatedFields = new Set();
421
- for (const fieldChange of fieldsToUpdate) {
422
- this.composeInvalidatedField(fieldChange, table, genId, metadata);
456
+ // Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
457
+ for (const [id1, id2] of table.pendingCompositions.nodeIdsToCompose) {
458
+ this.composeNodesById(
459
+ table.baseChange,
460
+ table.newChange,
461
+ composedNodes,
462
+ composedNodeToParent,
463
+ nodeAliases,
464
+ id1,
465
+ id2,
466
+ genId,
467
+ table,
468
+ metadata,
469
+ );
423
470
  }
471
+
472
+ table.pendingCompositions.nodeIdsToCompose.length = 0;
424
473
  }
425
474
 
426
475
  /**
@@ -437,64 +486,77 @@ export class ModularChangeFamily
437
486
  private composeAffectedFields(
438
487
  table: ComposeTable,
439
488
  change: ModularChangeset,
440
- areBaseFields: boolean,
441
489
  affectedFields: BTree<FieldIdKey, true>,
442
490
  composedFields: FieldChangeMap,
443
491
  composedNodes: ChangeAtomIdBTree<NodeChangeset>,
444
492
  genId: IdAllocator,
445
493
  metadata: RevisionMetadataSource,
446
494
  ): void {
447
- for (const fieldIdKey of affectedFields.keys()) {
448
- const fieldId = normalizeFieldId(fieldIdFromFieldIdKey(fieldIdKey), change.nodeAliases);
449
- const fieldChange = fieldChangeFromId(change.fieldChanges, change.nodeChanges, fieldId);
495
+ const fieldsToProcess = affectedFields.clone();
496
+ affectedFields.clear();
497
+
498
+ for (const fieldIdKey of fieldsToProcess.keys()) {
499
+ const fieldId = fieldIdFromFieldIdKey(fieldIdKey);
500
+ const fieldChange = fieldChangeFromId(change, fieldId);
450
501
 
451
502
  if (
452
503
  table.fieldToContext.has(fieldChange) ||
453
504
  table.newFieldToBaseField.has(fieldChange)
454
505
  ) {
455
- // This function handles fields which were not part of the intersection of the two changesets but which need to be updated anyway.
456
- // If we've already processed this field then either it is up to date
457
- // or there is pending inval which will be handled in processInvalidatedCompositions.
458
- continue;
506
+ this.composeInvalidatedField(fieldChange, table, genId, metadata);
507
+ } else {
508
+ this.composeFieldWithNoNewChange(
509
+ table,
510
+ fieldChange,
511
+ fieldId,
512
+ composedFields,
513
+ composedNodes,
514
+ genId,
515
+ metadata,
516
+ );
459
517
  }
518
+ }
519
+ }
460
520
 
461
- const emptyChange = this.createEmptyFieldChange(fieldChange.fieldKind);
462
- const [change1, change2] = areBaseFields
463
- ? [fieldChange, emptyChange]
464
- : [emptyChange, fieldChange];
465
-
466
- const composedField = this.composeFieldChanges(
467
- fieldId,
468
- change1,
469
- change2,
470
- genId,
471
- table,
472
- metadata,
473
- );
521
+ private composeFieldWithNoNewChange(
522
+ table: ComposeTable,
523
+ baseFieldChange: FieldChange,
524
+ fieldId: FieldId,
525
+ composedFields: FieldChangeMap,
526
+ composedNodes: ChangeAtomIdBTree<NodeChangeset>,
527
+ genId: IdAllocator,
528
+ metadata: RevisionMetadataSource,
529
+ ): void {
530
+ const emptyChange = this.createEmptyFieldChange(baseFieldChange.fieldKind);
474
531
 
475
- if (fieldId.nodeId === undefined) {
476
- composedFields.set(fieldId.field, composedField);
477
- continue;
478
- }
532
+ const composedField = this.composeFieldChanges(
533
+ fieldId,
534
+ baseFieldChange,
535
+ emptyChange,
536
+ genId,
537
+ table,
538
+ metadata,
539
+ );
479
540
 
480
- const nodeId =
481
- getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId;
541
+ if (fieldId.nodeId === undefined) {
542
+ composedFields.set(fieldId.field, composedField);
543
+ return;
544
+ }
482
545
 
483
- let nodeChangeset = nodeChangeFromId(composedNodes, nodeId);
484
- if (!table.composedNodes.has(nodeChangeset)) {
485
- nodeChangeset = cloneNodeChangeset(nodeChangeset);
486
- setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
487
- }
546
+ const nodeId = normalizeNodeId(
547
+ getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId,
548
+ table.baseChange.nodeAliases,
549
+ );
488
550
 
489
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy
490
- if (nodeChangeset.fieldChanges === undefined) {
491
- nodeChangeset.fieldChanges = new Map();
492
- }
551
+ // We clone the node changeset before mutating it, as it may be from one of the input changesets.
552
+ const nodeChangeset: Mutable<NodeChangeset> = cloneNodeChangeset(
553
+ nodeChangeFromId(composedNodes, table.baseChange.nodeAliases, nodeId),
554
+ );
555
+ setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
493
556
 
494
- nodeChangeset.fieldChanges.set(fieldId.field, composedField);
495
- }
557
+ nodeChangeset.fieldChanges ??= new Map();
496
558
 
497
- affectedFields.clear();
559
+ nodeChangeset.fieldChanges.set(fieldId.field, composedField);
498
560
  }
499
561
 
500
562
  private composeFieldMaps(
@@ -513,6 +575,18 @@ export class ModularChangeFamily
513
575
  for (const [field, fieldChange1] of change1) {
514
576
  const fieldId: FieldId = { nodeId: parentId, field };
515
577
  const fieldChange2 = change2.get(field);
578
+
579
+ const cachedComposedFieldChange =
580
+ crossFieldTable.fieldToContext.get(fieldChange1)?.composedChange;
581
+
582
+ if (fieldChange2 === undefined && cachedComposedFieldChange !== undefined) {
583
+ // This can happen if the field was previous processed in `composeFieldWithNoNewChange`.
584
+ // If `change2` does not have a change for this field, then without this check we would
585
+ // lose the composed field change and instead simply have `change1`'s change.
586
+ composedFields.set(field, cachedComposedFieldChange);
587
+ continue;
588
+ }
589
+
516
590
  const composedField =
517
591
  fieldChange2 === undefined
518
592
  ? fieldChange1
@@ -544,7 +618,7 @@ export class ModularChangeFamily
544
618
  * will be added to `crossFieldTable.pendingCompositions.nodeIdsToCompose`.
545
619
  *
546
620
  * Any fields which had cross-field information sent to them as part of this field composition
547
- * will be added to either `affectedBaseFields` or `affectedNewFields` in `crossFieldTable.pendingCompositions`.
621
+ * will be added to `affectedBaseFields` in `crossFieldTable.pendingCompositions`.
548
622
  *
549
623
  * Any composed `FieldChange` which is invalidated by new cross-field information will be added to `crossFieldTable.invalidatedFields`.
550
624
  */
@@ -563,15 +637,14 @@ export class ModularChangeFamily
563
637
  change2: change2Normalized,
564
638
  } = this.normalizeFieldChanges(change1, change2);
565
639
 
566
- const manager = new ComposeManager(crossFieldTable, change1, fieldId);
640
+ const manager = new ComposeNodeManagerI(crossFieldTable, fieldId);
567
641
 
568
642
  const composedChange = changeHandler.rebaser.compose(
569
643
  change1Normalized,
570
644
  change2Normalized,
571
645
  (child1, child2) => {
572
646
  if (child1 !== undefined && child2 !== undefined) {
573
- setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
574
- crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
647
+ addNodesToCompose(crossFieldTable, child1, child2);
575
648
  }
576
649
  return child1 ?? child2 ?? fail(0xb23 /* Should not compose two undefined nodes */);
577
650
  },
@@ -597,19 +670,19 @@ export class ModularChangeFamily
597
670
  }
598
671
 
599
672
  private composeNodesById(
600
- nodeChanges1: ChangeAtomIdBTree<NodeChangeset>,
601
- nodeChanges2: ChangeAtomIdBTree<NodeChangeset>,
673
+ change1: ModularChangeset,
674
+ change2: ModularChangeset,
602
675
  composedNodes: ChangeAtomIdBTree<NodeChangeset>,
603
- composedNodeToParent: ChangeAtomIdBTree<FieldId>,
604
- nodeAliases: ChangeAtomIdBTree<NodeId>,
676
+ composedNodeToParent: ChangeAtomIdBTree<NodeLocation>,
677
+ composedAliases: ChangeAtomIdBTree<NodeId>,
605
678
  id1: NodeId,
606
679
  id2: NodeId,
607
680
  idAllocator: IdAllocator,
608
681
  crossFieldTable: ComposeTable,
609
682
  revisionMetadata: RevisionMetadataSource,
610
683
  ): void {
611
- const nodeChangeset1 = nodeChangeFromId(nodeChanges1, id1);
612
- const nodeChangeset2 = nodeChangeFromId(nodeChanges2, id2);
684
+ const nodeChangeset1 = nodeChangeFromId(change1.nodeChanges, change1.nodeAliases, id1);
685
+ const nodeChangeset2 = nodeChangeFromId(change2.nodeChanges, change2.nodeAliases, id2);
613
686
  const composedNodeChangeset = this.composeNodeChanges(
614
687
  id1,
615
688
  nodeChangeset1,
@@ -624,13 +697,11 @@ export class ModularChangeFamily
624
697
  if (!areEqualChangeAtomIds(id1, id2)) {
625
698
  composedNodes.delete([id2.revision, id2.localId]);
626
699
  composedNodeToParent.delete([id2.revision, id2.localId]);
627
- setInChangeAtomIdMap(nodeAliases, id2, id1);
700
+ setInChangeAtomIdMap(composedAliases, id2, id1);
628
701
 
629
702
  // We need to delete id1 to avoid forming a cycle in case id1 already had an alias.
630
- nodeAliases.delete([id1.revision, id1.localId]);
703
+ composedAliases.delete([id1.revision, id1.localId]);
631
704
  }
632
-
633
- crossFieldTable.composedNodes.add(composedNodeChangeset);
634
705
  }
635
706
 
636
707
  private composeNodeChanges(
@@ -665,7 +736,7 @@ export class ModularChangeFamily
665
736
  revisionMetadata,
666
737
  );
667
738
 
668
- const composedNodeChange: NodeChangeset = {};
739
+ const composedNodeChange: Mutable<NodeChangeset> = {};
669
740
 
670
741
  if (composedFieldChanges.size > 0) {
671
742
  composedNodeChange.fieldChanges = composedFieldChanges;
@@ -711,6 +782,7 @@ export class ModularChangeFamily
711
782
 
712
783
  if (hasConflicts(change.change)) {
713
784
  return makeModularChangeset({
785
+ rebaseVersion: change.change.rebaseVersion,
714
786
  maxId: change.change.maxId as number,
715
787
  revisions: revInfos,
716
788
  destroys,
@@ -719,10 +791,20 @@ export class ModularChangeFamily
719
791
 
720
792
  const genId: IdAllocator = idAllocatorFromMaxId(change.change.maxId ?? -1);
721
793
 
794
+ const invertedNodeToParent: ChangeAtomIdBTree<NodeLocation> = brand(
795
+ change.change.nodeToParent.clone(),
796
+ );
797
+
722
798
  const crossFieldTable: InvertTable = {
723
- ...newCrossFieldTable<FieldChange>(),
799
+ change: change.change,
800
+ isRollback,
801
+ entries: newChangeAtomIdRangeMap(),
724
802
  originalFieldToContext: new Map(),
725
- invertedNodeToParent: brand(change.change.nodeToParent.clone()),
803
+ invertRevision: revisionForInvert,
804
+ invertedNodeToParent,
805
+ invalidatedFields: new Set(),
806
+ invertedRoots: invertRootTable(change.change, invertedNodeToParent, isRollback),
807
+ attachToDetachId: newChangeAtomIdTransform(),
726
808
  };
727
809
  const { revInfos: oldRevInfos } = getRevInfoFromTaggedChanges([change]);
728
810
  const revisionMetadata = revisionMetadataSourceFromInfo(oldRevInfos);
@@ -763,7 +845,7 @@ export class ModularChangeFamily
763
845
  context !== undefined,
764
846
  0x851 /* Should have context for every invalidated field */,
765
847
  );
766
- const { invertedField, fieldId } = context;
848
+ const { invertedField } = context;
767
849
 
768
850
  const amendedChange = getChangeHandler(
769
851
  this.fieldKinds,
@@ -773,7 +855,7 @@ export class ModularChangeFamily
773
855
  isRollback,
774
856
  genId,
775
857
  revisionForInvert,
776
- new InvertManager(crossFieldTable, fieldChange, fieldId),
858
+ new InvertNodeManagerI(crossFieldTable, context.fieldId),
777
859
  revisionMetadata,
778
860
  );
779
861
  invertedField.change = brand(amendedChange);
@@ -782,10 +864,14 @@ export class ModularChangeFamily
782
864
 
783
865
  const crossFieldKeys = this.makeCrossFieldKeyTable(invertedFields, invertedNodes);
784
866
 
867
+ this.processInvertRenames(crossFieldTable);
868
+
785
869
  return makeModularChangeset({
870
+ rebaseVersion: change.change.rebaseVersion,
786
871
  fieldChanges: invertedFields,
787
872
  nodeChanges: invertedNodes,
788
873
  nodeToParent: crossFieldTable.invertedNodeToParent,
874
+ rootNodes: crossFieldTable.invertedRoots,
789
875
  nodeAliases: change.change.nodeAliases,
790
876
  crossFieldKeys,
791
877
  maxId: genId.getMaxId(),
@@ -811,7 +897,7 @@ export class ModularChangeFamily
811
897
 
812
898
  for (const [field, fieldChange] of changes) {
813
899
  const fieldId = { nodeId: parentId, field };
814
- const manager = new InvertManager(crossFieldTable, fieldChange, fieldId);
900
+ const manager = new InvertNodeManagerI(crossFieldTable, fieldId);
815
901
  const invertedChange = getChangeHandler(
816
902
  this.fieldKinds,
817
903
  fieldChange.fieldKind,
@@ -848,7 +934,7 @@ export class ModularChangeFamily
848
934
  revisionMetadata: RevisionMetadataSource,
849
935
  revisionForInvert: RevisionTag,
850
936
  ): NodeChangeset {
851
- const inverse: NodeChangeset = {};
937
+ const inverse: Mutable<NodeChangeset> = {};
852
938
 
853
939
  // If the node has a constraint, it should be inverted to a node-exist-on-revert constraint. This ensure that if
854
940
  // the inverse is inverted again, the original input constraint will be restored.
@@ -878,6 +964,17 @@ export class ModularChangeFamily
878
964
  return inverse;
879
965
  }
880
966
 
967
+ private processInvertRenames(table: InvertTable): void {
968
+ for (const {
969
+ start: newAttachId,
970
+ value: originalDetachId,
971
+ length,
972
+ } of table.attachToDetachId.entries()) {
973
+ // Note that the detach location is already set in `invertDetach`.
974
+ addNodeRename(table.invertedRoots, originalDetachId, newAttachId, length, undefined);
975
+ }
976
+ }
977
+
881
978
  public rebase(
882
979
  taggedChange: TaggedChange<ModularChangeset>,
883
980
  potentiallyConflictedOver: TaggedChange<ModularChangeset>,
@@ -897,17 +994,41 @@ export class ModularChangeFamily
897
994
  const idState: IdAllocationState = { maxId };
898
995
  const genId: IdAllocator = idAllocatorFromState(idState);
899
996
 
997
+ const affectedBaseFields: TupleBTree<FieldIdKey, boolean> = newFieldIdKeyBTree();
998
+ const nodesToRebase: [newChangeset: NodeId, baseChangeset: NodeId][] = [];
999
+
1000
+ const rebasedNodeToParent: ChangeAtomIdBTree<NodeLocation> = brand(
1001
+ change.nodeToParent.clone(),
1002
+ );
1003
+
1004
+ const rebaseVersion = Math.max(
1005
+ change.rebaseVersion,
1006
+ over.change.rebaseVersion,
1007
+ ) as RebaseVersion;
1008
+
1009
+ const rebasedRootNodes = rebaseRoots(
1010
+ change,
1011
+ over.change,
1012
+ affectedBaseFields,
1013
+ nodesToRebase,
1014
+ rebasedNodeToParent,
1015
+ rebaseVersion,
1016
+ );
900
1017
  const crossFieldTable: RebaseTable = {
901
- ...newCrossFieldTable<FieldChange>(),
1018
+ rebaseVersion,
1019
+ entries: newDetachedEntryMap(),
902
1020
  newChange: change,
903
1021
  baseChange: over.change,
904
1022
  baseFieldToContext: new Map(),
1023
+ baseRoots: over.change.rootNodes,
1024
+ rebasedRootNodes,
905
1025
  baseToRebasedNodeId: newChangeAtomIdBTree(),
906
1026
  rebasedFields: new Set(),
907
- rebasedNodeToParent: brand(change.nodeToParent.clone()),
908
- rebasedCrossFieldKeys: change.crossFieldKeys.clone(),
1027
+ rebasedNodeToParent,
1028
+ rebasedDetachLocations: newChangeAtomIdRangeMap(),
1029
+ movedDetaches: newChangeAtomIdRangeMap(),
909
1030
  nodeIdPairs: [],
910
- affectedBaseFields: newFieldIdKeyBTree(),
1031
+ affectedBaseFields,
911
1032
  fieldsWithUnattachedChild: new Set(),
912
1033
  };
913
1034
 
@@ -923,13 +1044,14 @@ export class ModularChangeFamily
923
1044
  const rebasedNodes: ChangeAtomIdBTree<NodeChangeset> = brand(change.nodeChanges.clone());
924
1045
 
925
1046
  const rebasedFields = this.rebaseIntersectingFields(
1047
+ nodesToRebase,
926
1048
  crossFieldTable,
927
1049
  rebasedNodes,
928
1050
  genId,
929
1051
  rebaseMetadata,
930
1052
  );
931
1053
 
932
- this.rebaseInvalidatedElements(
1054
+ this.rebaseInvalidatedFields(
933
1055
  rebasedFields,
934
1056
  rebasedNodes,
935
1057
  crossFieldTable,
@@ -937,6 +1059,8 @@ export class ModularChangeFamily
937
1059
  genId,
938
1060
  );
939
1061
 
1062
+ fixupRebasedDetachLocations(crossFieldTable);
1063
+
940
1064
  const constraintState = newConstraintState(change.constraintViolationCount ?? 0);
941
1065
  const revertConstraintState = newConstraintState(
942
1066
  change.constraintViolationCountOnRevert ?? 0,
@@ -948,21 +1072,52 @@ export class ModularChangeFamily
948
1072
  constraintState.violationCount += 1;
949
1073
  }
950
1074
 
951
- this.updateConstraintsForFields(
1075
+ this.updateConstraints(
952
1076
  rebasedFields,
953
- NodeAttachState.Attached,
954
- NodeAttachState.Attached,
1077
+ rebasedNodes,
1078
+ rebasedRootNodes,
955
1079
  constraintState,
956
1080
  revertConstraintState,
957
- rebasedNodes,
1081
+ );
1082
+
1083
+ const fieldsWithRootMoves = getFieldsWithRootMoves(
1084
+ crossFieldTable.rebasedRootNodes,
1085
+ change.nodeAliases,
1086
+ );
1087
+
1088
+ const fieldToRootChanges = getFieldToRootChanges(
1089
+ crossFieldTable.rebasedRootNodes,
1090
+ change.nodeAliases,
958
1091
  );
959
1092
 
960
1093
  const rebased = makeModularChangeset({
961
- fieldChanges: this.pruneFieldMap(rebasedFields, rebasedNodes),
1094
+ fieldChanges: this.pruneFieldMap(
1095
+ rebasedFields,
1096
+ undefined,
1097
+ rebasedNodes,
1098
+ crossFieldTable.rebasedNodeToParent,
1099
+ change.nodeAliases,
1100
+ crossFieldTable.rebasedRootNodes,
1101
+ fieldsWithRootMoves,
1102
+ fieldToRootChanges,
1103
+ ),
962
1104
  nodeChanges: rebasedNodes,
963
1105
  nodeToParent: crossFieldTable.rebasedNodeToParent,
1106
+ rootNodes: this.pruneRoots(
1107
+ crossFieldTable.rebasedRootNodes,
1108
+ rebasedNodes,
1109
+ crossFieldTable.rebasedNodeToParent,
1110
+ change.nodeAliases,
1111
+ fieldsWithRootMoves,
1112
+ fieldToRootChanges,
1113
+ ),
1114
+ // TODO: Do we need to include aliases for node changesets added during rebasing?
964
1115
  nodeAliases: change.nodeAliases,
965
- crossFieldKeys: crossFieldTable.rebasedCrossFieldKeys,
1116
+ crossFieldKeys: rebaseCrossFieldKeys(
1117
+ change.crossFieldKeys,
1118
+ crossFieldTable.movedDetaches,
1119
+ crossFieldTable.rebasedDetachLocations,
1120
+ ),
966
1121
  maxId: idState.maxId,
967
1122
  revisions: change.revisions,
968
1123
  constraintViolationCount: constraintState.violationCount,
@@ -972,14 +1127,18 @@ export class ModularChangeFamily
972
1127
  builds: change.builds,
973
1128
  destroys: change.destroys,
974
1129
  refreshers: change.refreshers,
1130
+ rebaseVersion,
975
1131
  });
976
1132
 
1133
+ // XXX: This is an expensive assert which should be disabled before merging.
1134
+ validateChangeset(rebased, this.fieldKinds);
977
1135
  return rebased;
978
1136
  }
979
1137
 
980
1138
  // This performs a first pass on all fields which have both new and base changes.
981
1139
  // TODO: Can we also handle additional passes in this method?
982
1140
  private rebaseIntersectingFields(
1141
+ rootChanges: [newChangeset: NodeId, baseChangeset: NodeId][],
983
1142
  crossFieldTable: RebaseTable,
984
1143
  rebasedNodes: ChangeAtomIdBTree<NodeChangeset>,
985
1144
  genId: IdAllocator,
@@ -996,6 +1155,18 @@ export class ModularChangeFamily
996
1155
  metadata,
997
1156
  );
998
1157
 
1158
+ for (const [newChildChange, baseChildChange] of rootChanges) {
1159
+ const rebasedNode = this.rebaseNodeChange(
1160
+ newChildChange,
1161
+ baseChildChange,
1162
+ genId,
1163
+ crossFieldTable,
1164
+ metadata,
1165
+ );
1166
+
1167
+ setInChangeAtomIdMap(rebasedNodes, newChildChange, rebasedNode);
1168
+ }
1169
+
999
1170
  // This loop processes all fields which have both base and new changes.
1000
1171
  // Note that the call to `rebaseNodeChange` can add entries to `crossFieldTable.nodeIdPairs`.
1001
1172
  for (const [newId, baseId, _attachState] of crossFieldTable.nodeIdPairs) {
@@ -1013,137 +1184,150 @@ export class ModularChangeFamily
1013
1184
  return rebasedFields;
1014
1185
  }
1015
1186
 
1016
- // This processes fields which have no new changes but have been invalidated by another field.
1017
- private rebaseFieldsWithoutNewChanges(
1187
+ private rebaseFieldWithoutNewChanges(
1188
+ baseFieldChange: FieldChange,
1189
+ baseFieldId: FieldId,
1190
+ crossFieldTable: RebaseTable,
1018
1191
  rebasedFields: FieldChangeMap,
1019
1192
  rebasedNodes: ChangeAtomIdBTree<NodeChangeset>,
1020
- crossFieldTable: RebaseTable,
1021
1193
  genId: IdAllocator,
1022
1194
  metadata: RebaseRevisionMetadata,
1195
+
1196
+ /**
1197
+ * The ID of a node in `baseFieldChange` which should be included in the rebased field change.
1198
+ */
1199
+ baseNodeId?: NodeId,
1023
1200
  ): void {
1024
- const baseChange = crossFieldTable.baseChange;
1025
- for (const [revision, localId, fieldKey] of crossFieldTable.affectedBaseFields.keys()) {
1026
- const baseNodeId =
1027
- localId === undefined
1028
- ? undefined
1029
- : normalizeNodeId({ revision, localId }, baseChange.nodeAliases);
1201
+ // This field has no changes in the new changeset, otherwise it would have been added to
1202
+ // `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
1203
+ const rebaseChild = (
1204
+ child: NodeId | undefined,
1205
+ baseChild: NodeId | undefined,
1206
+ stateChange: NodeAttachState | undefined,
1207
+ ): NodeId | undefined => {
1208
+ assert(child === undefined, 0x9c3 /* There should be no new changes in this field */);
1209
+ if (baseChild === undefined || baseNodeId === undefined) {
1210
+ return undefined;
1211
+ }
1030
1212
 
1031
- const baseFieldChange = fieldMapFromNodeId(
1032
- baseChange.fieldChanges,
1033
- baseChange.nodeChanges,
1213
+ return areEqualChangeAtomIds(
1214
+ normalizeNodeId(baseChild, crossFieldTable.baseChange.nodeAliases),
1034
1215
  baseNodeId,
1035
- ).get(fieldKey);
1216
+ )
1217
+ ? baseNodeId
1218
+ : undefined;
1219
+ };
1036
1220
 
1037
- assert(
1038
- baseFieldChange !== undefined,
1039
- 0x9c2 /* Cross field key registered for empty field */,
1040
- );
1041
- if (crossFieldTable.baseFieldToContext.has(baseFieldChange)) {
1042
- // This field has already been processed because there were changes to rebase.
1043
- continue;
1044
- }
1221
+ const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
1222
+ const fieldChange: FieldChange = {
1223
+ ...baseFieldChange,
1224
+ change: brand(handler.createEmpty()),
1225
+ };
1045
1226
 
1046
- // This field has no changes in the new changeset, otherwise it would have been added to
1047
- // `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
1048
- const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
1049
- const fieldChange: FieldChange = {
1050
- ...baseFieldChange,
1051
- change: brand(handler.createEmpty()),
1052
- };
1227
+ const rebasedNodeId =
1228
+ baseFieldId.nodeId === undefined
1229
+ ? undefined
1230
+ : rebasedNodeIdFromBaseNodeId(crossFieldTable, baseFieldId.nodeId);
1053
1231
 
1054
- const rebasedNodeId =
1055
- baseNodeId === undefined
1056
- ? undefined
1057
- : rebasedNodeIdFromBaseNodeId(crossFieldTable, baseNodeId);
1232
+ const fieldId: FieldId = { nodeId: rebasedNodeId, field: baseFieldId.field };
1058
1233
 
1059
- const fieldId: FieldId = { nodeId: rebasedNodeId, field: fieldKey };
1060
- const rebasedField: unknown = handler.rebaser.rebase(
1061
- fieldChange.change,
1062
- baseFieldChange.change,
1063
- noNewChangesRebaseChild,
1064
- genId,
1065
- new RebaseManager(crossFieldTable, baseFieldChange, fieldId),
1066
- metadata,
1067
- );
1234
+ const rebasedField: unknown = handler.rebaser.rebase(
1235
+ fieldChange.change,
1236
+ baseFieldChange.change,
1237
+ rebaseChild,
1238
+ genId,
1239
+ new RebaseNodeManagerI(crossFieldTable, fieldId),
1240
+ metadata,
1241
+ crossFieldTable.rebaseVersion,
1242
+ );
1068
1243
 
1069
- const rebasedFieldChange: FieldChange = {
1070
- ...baseFieldChange,
1071
- change: brand(rebasedField),
1072
- };
1244
+ const rebasedFieldChange: FieldChange = {
1245
+ ...baseFieldChange,
1246
+ change: brand(rebasedField),
1247
+ };
1073
1248
 
1074
- // TODO: Deduplicate
1075
- crossFieldTable.baseFieldToContext.set(baseFieldChange, {
1076
- newChange: fieldChange,
1077
- baseChange: baseFieldChange,
1078
- rebasedChange: rebasedFieldChange,
1079
- fieldId,
1080
- baseNodeIds: [],
1081
- });
1082
- crossFieldTable.rebasedFields.add(rebasedFieldChange);
1249
+ const context: RebaseFieldContext = {
1250
+ newChange: fieldChange,
1251
+ baseChange: baseFieldChange,
1252
+ rebasedChange: rebasedFieldChange,
1253
+ fieldId,
1254
+ baseNodeIds: newChangeAtomIdBTree(),
1255
+ };
1083
1256
 
1084
- this.attachRebasedField(
1085
- rebasedFields,
1086
- rebasedNodes,
1087
- crossFieldTable,
1088
- rebasedFieldChange,
1089
- fieldId,
1090
- genId,
1091
- metadata,
1092
- );
1257
+ if (baseNodeId !== undefined) {
1258
+ setInChangeAtomIdMap(context.baseNodeIds, baseNodeId, true);
1093
1259
  }
1094
- }
1095
1260
 
1096
- private rebaseInvalidatedElements(
1097
- rebasedFields: FieldChangeMap,
1098
- rebasedNodes: ChangeAtomIdBTree<NodeChangeset>,
1099
- table: RebaseTable,
1100
- metadata: RebaseRevisionMetadata,
1101
- idAllocator: IdAllocator,
1102
- ): void {
1103
- this.rebaseFieldsWithoutNewChanges(
1261
+ crossFieldTable.baseFieldToContext.set(baseFieldChange, context);
1262
+
1263
+ crossFieldTable.rebasedFields.add(rebasedFieldChange);
1264
+
1265
+ this.attachRebasedField(
1104
1266
  rebasedFields,
1105
1267
  rebasedNodes,
1106
- table,
1107
- idAllocator,
1268
+ crossFieldTable,
1269
+ rebasedFieldChange,
1270
+ fieldId,
1271
+ genId,
1108
1272
  metadata,
1109
1273
  );
1110
-
1111
- this.rebaseFieldsWithUnattachedChild(table, metadata, idAllocator);
1112
- this.rebaseInvalidatedFields(table, metadata, idAllocator);
1113
1274
  }
1114
1275
 
1115
1276
  private rebaseInvalidatedFields(
1277
+ rebasedFields: FieldChangeMap,
1278
+ rebasedNodes: ChangeAtomIdBTree<NodeChangeset>,
1116
1279
  crossFieldTable: RebaseTable,
1117
1280
  rebaseMetadata: RebaseRevisionMetadata,
1118
1281
  genId: IdAllocator,
1119
1282
  ): void {
1120
- const fieldsToUpdate = crossFieldTable.invalidatedFields;
1121
- crossFieldTable.invalidatedFields = new Set();
1122
- for (const field of fieldsToUpdate) {
1123
- this.rebaseInvalidatedField(field, crossFieldTable, rebaseMetadata, genId);
1124
- }
1125
- }
1283
+ while (crossFieldTable.affectedBaseFields.size > 0) {
1284
+ const baseFields = crossFieldTable.affectedBaseFields.clone();
1285
+ crossFieldTable.affectedBaseFields.clear();
1286
+
1287
+ for (const baseFieldIdKey of baseFields.keys()) {
1288
+ const baseFieldId = normalizeFieldId(
1289
+ fieldIdFromFieldIdKey(baseFieldIdKey),
1290
+ crossFieldTable.baseChange.nodeAliases,
1291
+ );
1126
1292
 
1127
- private rebaseFieldsWithUnattachedChild(
1128
- table: RebaseTable,
1129
- metadata: RebaseRevisionMetadata,
1130
- idAllocator: IdAllocator,
1131
- ): void {
1132
- for (const field of table.fieldsWithUnattachedChild) {
1133
- table.invalidatedFields.delete(field);
1134
- this.rebaseInvalidatedField(field, table, metadata, idAllocator, true);
1293
+ const baseField = fieldChangeFromId(crossFieldTable.baseChange, baseFieldId);
1294
+
1295
+ assert(
1296
+ baseField !== undefined,
1297
+ 0x9c2 /* Cross field key registered for empty field */,
1298
+ );
1299
+
1300
+ const context = crossFieldTable.baseFieldToContext.get(baseField);
1301
+ if (context === undefined) {
1302
+ this.rebaseFieldWithoutNewChanges(
1303
+ baseField,
1304
+ baseFieldId,
1305
+ crossFieldTable,
1306
+ rebasedFields,
1307
+ rebasedNodes,
1308
+ genId,
1309
+ rebaseMetadata,
1310
+ );
1311
+ } else {
1312
+ this.rebaseInvalidatedField(
1313
+ baseField,
1314
+ crossFieldTable,
1315
+ context,
1316
+ rebaseMetadata,
1317
+ genId,
1318
+ );
1319
+ }
1320
+ }
1135
1321
  }
1136
1322
  }
1137
1323
 
1138
1324
  private rebaseInvalidatedField(
1139
1325
  baseField: FieldChange,
1140
1326
  crossFieldTable: RebaseTable,
1327
+ context: RebaseFieldContext,
1141
1328
  rebaseMetadata: RebaseRevisionMetadata,
1142
1329
  genId: IdAllocator,
1143
- allowInval = false,
1144
1330
  ): void {
1145
- const context = crossFieldTable.baseFieldToContext.get(baseField);
1146
- assert(context !== undefined, 0x852 /* Every field should have a context */);
1147
1331
  const {
1148
1332
  changeHandler,
1149
1333
  change1: fieldChangeset,
@@ -1158,25 +1342,28 @@ export class ModularChangeFamily
1158
1342
  return curr;
1159
1343
  }
1160
1344
 
1161
- if (base !== undefined) {
1162
- for (const id of context.baseNodeIds) {
1163
- if (areEqualChangeAtomIds(base, id)) {
1164
- return base;
1165
- }
1166
- }
1345
+ if (base !== undefined && getFromChangeAtomIdMap(context.baseNodeIds, base) === true) {
1346
+ return base;
1167
1347
  }
1168
1348
 
1169
1349
  return undefined;
1170
1350
  };
1171
1351
 
1352
+ let allowInval = false;
1353
+ if (crossFieldTable.fieldsWithUnattachedChild.has(baseField)) {
1354
+ crossFieldTable.fieldsWithUnattachedChild.delete(baseField);
1355
+ allowInval = true;
1356
+ }
1357
+
1172
1358
  context.rebasedChange.change = brand(
1173
1359
  changeHandler.rebaser.rebase(
1174
1360
  fieldChangeset,
1175
1361
  baseChangeset,
1176
1362
  rebaseChild,
1177
1363
  genId,
1178
- new RebaseManager(crossFieldTable, baseField, context.fieldId, allowInval),
1364
+ new RebaseNodeManagerI(crossFieldTable, context.fieldId, allowInval),
1179
1365
  rebaseMetadata,
1366
+ crossFieldTable.rebaseVersion,
1180
1367
  ),
1181
1368
  );
1182
1369
  }
@@ -1196,13 +1383,19 @@ export class ModularChangeFamily
1196
1383
  }
1197
1384
  const rebasedNode = getFromChangeAtomIdMap(rebasedNodes, nodeId);
1198
1385
  if (rebasedNode !== undefined) {
1199
- if (rebasedNode.fieldChanges === undefined) {
1200
- rebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
1386
+ const updatedRebasedNode: Mutable<NodeChangeset> = cloneNodeChangeset(rebasedNode);
1387
+ setInChangeAtomIdMap(rebasedNodes, nodeId, updatedRebasedNode);
1388
+
1389
+ if (updatedRebasedNode.fieldChanges === undefined) {
1390
+ updatedRebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
1201
1391
  return;
1202
1392
  }
1203
1393
 
1204
- assert(!rebasedNode.fieldChanges.has(fieldKey), 0x9c4 /* Expected an empty field */);
1205
- rebasedNode.fieldChanges.set(fieldKey, rebasedField);
1394
+ assert(
1395
+ !updatedRebasedNode.fieldChanges.has(fieldKey),
1396
+ 0x9c4 /* Expected an empty field */,
1397
+ );
1398
+ updatedRebasedNode.fieldChanges.set(fieldKey, rebasedField);
1206
1399
  return;
1207
1400
  }
1208
1401
 
@@ -1213,14 +1406,14 @@ export class ModularChangeFamily
1213
1406
  setInChangeAtomIdMap(rebasedNodes, nodeId, newNode);
1214
1407
  setInChangeAtomIdMap(table.baseToRebasedNodeId, nodeId, nodeId);
1215
1408
 
1216
- const parentFieldId = getParentFieldId(table.baseChange, nodeId);
1409
+ const parentBase = getNodeParent(table.baseChange, nodeId);
1217
1410
 
1218
1411
  this.attachRebasedNode(
1219
1412
  rebasedFields,
1220
1413
  rebasedNodes,
1221
1414
  table,
1222
1415
  nodeId,
1223
- parentFieldId,
1416
+ parentBase,
1224
1417
  idAllocator,
1225
1418
  metadata,
1226
1419
  );
@@ -1231,65 +1424,100 @@ export class ModularChangeFamily
1231
1424
  rebasedNodes: ChangeAtomIdBTree<NodeChangeset>,
1232
1425
  table: RebaseTable,
1233
1426
  baseNodeId: NodeId,
1234
- parentFieldIdBase: FieldId,
1427
+ parentBase: NodeLocation,
1235
1428
  idAllocator: IdAllocator,
1236
1429
  metadata: RebaseRevisionMetadata,
1237
1430
  ): void {
1238
- const baseFieldChange = fieldChangeFromId(
1239
- table.baseChange.fieldChanges,
1240
- table.baseChange.nodeChanges,
1241
- parentFieldIdBase,
1242
- );
1431
+ if (parentBase.root !== undefined) {
1432
+ const renamedRoot = firstAttachIdFromDetachId(
1433
+ table.baseChange.rootNodes,
1434
+ parentBase.root,
1435
+ 1,
1436
+ ).value;
1437
+
1438
+ const attachField = table.baseChange.crossFieldKeys.getFirst(
1439
+ { ...renamedRoot, target: NodeMoveType.Attach },
1440
+ 1,
1441
+ ).value;
1442
+
1443
+ if (attachField === undefined) {
1444
+ const baseDetachLocation = table.baseChange.rootNodes.detachLocations.getFirst(
1445
+ parentBase.root,
1446
+ 1,
1447
+ ).value;
1448
+
1449
+ assignRootChange(
1450
+ table.rebasedRootNodes,
1451
+ table.rebasedNodeToParent,
1452
+ renamedRoot,
1453
+ baseNodeId,
1454
+ baseDetachLocation,
1455
+ table.rebaseVersion,
1456
+ );
1243
1457
 
1244
- const rebasedFieldId = rebasedFieldIdFromBaseId(table, parentFieldIdBase);
1245
- setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, rebasedFieldId);
1458
+ // We need to make sure the rebased changeset includes the detach location,
1459
+ // so we add that field to `affectedBaseFields` unless it's already been processed.
1460
+ if (
1461
+ baseDetachLocation !== undefined &&
1462
+ !table.baseFieldToContext.has(
1463
+ fieldChangeFromId(table.baseChange, baseDetachLocation),
1464
+ )
1465
+ ) {
1466
+ table.affectedBaseFields.set(fieldIdKeyFromFieldId(baseDetachLocation), true);
1467
+ }
1468
+ } else {
1469
+ // The base change inserts this node into `attachField`, so the rebased change should represent this node there.
1470
+ const normalizedAttachField = normalizeFieldId(
1471
+ attachField,
1472
+ table.baseChange.nodeAliases,
1473
+ );
1474
+
1475
+ const entry: DetachedNodeEntry = table.entries.getFirst(renamedRoot, 1).value ?? {};
1476
+ table.entries.set(renamedRoot, 1, { ...entry, nodeChange: baseNodeId });
1477
+ table.affectedBaseFields.set(fieldIdKeyFromFieldId(normalizedAttachField), true);
1478
+ this.attachRebasedNode(
1479
+ rebasedFields,
1480
+ rebasedNodes,
1481
+ table,
1482
+ baseNodeId,
1483
+ { field: normalizedAttachField },
1484
+ idAllocator,
1485
+ metadata,
1486
+ );
1487
+ }
1246
1488
 
1247
- const context = table.baseFieldToContext.get(baseFieldChange);
1248
- if (context !== undefined) {
1249
- // We've already processed this field.
1250
- // The new child node will be attached in rebaseFieldsWithUnattachedChild.
1251
- context.baseNodeIds.push(baseNodeId);
1252
- table.fieldsWithUnattachedChild.add(baseFieldChange);
1253
1489
  return;
1254
1490
  }
1255
1491
 
1256
- const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
1257
-
1258
- const fieldChange: FieldChange = {
1259
- ...baseFieldChange,
1260
- change: brand(handler.createEmpty()),
1261
- };
1492
+ const parentFieldIdBase = parentBase.field;
1493
+ const baseFieldChange = fieldChangeFromId(table.baseChange, parentFieldIdBase);
1262
1494
 
1263
- const rebasedChangeset = handler.rebaser.rebase(
1264
- handler.createEmpty(),
1265
- baseFieldChange.change,
1266
- (_idNew, idBase) =>
1267
- idBase !== undefined && areEqualChangeAtomIds(idBase, baseNodeId)
1268
- ? baseNodeId
1269
- : undefined,
1270
- idAllocator,
1271
- new RebaseManager(table, baseFieldChange, rebasedFieldId),
1272
- metadata,
1273
- );
1495
+ const rebasedFieldId = rebasedFieldIdFromBaseId(table, parentFieldIdBase);
1496
+ setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, { field: rebasedFieldId });
1274
1497
 
1275
- const rebasedField: FieldChange = { ...baseFieldChange, change: brand(rebasedChangeset) };
1276
- table.rebasedFields.add(rebasedField);
1277
- table.baseFieldToContext.set(baseFieldChange, {
1278
- newChange: fieldChange,
1279
- baseChange: baseFieldChange,
1280
- rebasedChange: rebasedField,
1281
- fieldId: rebasedFieldId,
1282
- baseNodeIds: [],
1283
- });
1498
+ const context = table.baseFieldToContext.get(baseFieldChange);
1499
+ if (context !== undefined) {
1500
+ // We've already processed this field.
1501
+ // The new child node will be attached in the next pass.
1502
+ // Note that adding to `fieldsWithUnattachedChild` allows that field to generate new invalidations,
1503
+ // so to avoid invalidation cycles we make sure we only add to it once per new unattached child.
1504
+ // This is done by checking whether `context.baseNodeIds` already contained `baseNodeId`.
1505
+ if (setInChangeAtomIdMap(context.baseNodeIds, baseNodeId, true)) {
1506
+ table.fieldsWithUnattachedChild.add(baseFieldChange);
1507
+ table.affectedBaseFields.set(fieldIdKeyFromFieldId(parentFieldIdBase), true);
1508
+ }
1509
+ return;
1510
+ }
1284
1511
 
1285
- this.attachRebasedField(
1512
+ this.rebaseFieldWithoutNewChanges(
1513
+ baseFieldChange,
1514
+ parentFieldIdBase,
1515
+ table,
1286
1516
  rebasedFields,
1287
1517
  rebasedNodes,
1288
- table,
1289
- rebasedField,
1290
- rebasedFieldId,
1291
1518
  idAllocator,
1292
1519
  metadata,
1520
+ baseNodeId,
1293
1521
  );
1294
1522
  }
1295
1523
 
@@ -1328,7 +1556,7 @@ export class ModularChangeFamily
1328
1556
  change2: baseChangeset,
1329
1557
  } = this.normalizeFieldChanges(fieldChange, baseChange);
1330
1558
 
1331
- const manager = new RebaseManager(crossFieldTable, baseChange, fieldId);
1559
+ const manager = new RebaseNodeManagerI(crossFieldTable, fieldId);
1332
1560
 
1333
1561
  const rebasedField = changeHandler.rebaser.rebase(
1334
1562
  fieldChangeset,
@@ -1337,6 +1565,7 @@ export class ModularChangeFamily
1337
1565
  genId,
1338
1566
  manager,
1339
1567
  revisionMetadata,
1568
+ crossFieldTable.rebaseVersion,
1340
1569
  );
1341
1570
 
1342
1571
  const rebasedFieldChange: FieldChange = {
@@ -1351,7 +1580,7 @@ export class ModularChangeFamily
1351
1580
  newChange: fieldChange,
1352
1581
  rebasedChange: rebasedFieldChange,
1353
1582
  fieldId,
1354
- baseNodeIds: [],
1583
+ baseNodeIds: newChangeAtomIdBTree(),
1355
1584
  });
1356
1585
 
1357
1586
  crossFieldTable.rebasedFields.add(rebasedFieldChange);
@@ -1367,8 +1596,16 @@ export class ModularChangeFamily
1367
1596
  crossFieldTable: RebaseTable,
1368
1597
  revisionMetadata: RebaseRevisionMetadata,
1369
1598
  ): NodeChangeset {
1370
- const change = nodeChangeFromId(crossFieldTable.newChange.nodeChanges, newId);
1371
- const over = nodeChangeFromId(crossFieldTable.baseChange.nodeChanges, baseId);
1599
+ const change = nodeChangeFromId(
1600
+ crossFieldTable.newChange.nodeChanges,
1601
+ crossFieldTable.newChange.nodeAliases,
1602
+ newId,
1603
+ );
1604
+ const over = nodeChangeFromId(
1605
+ crossFieldTable.baseChange.nodeChanges,
1606
+ crossFieldTable.baseChange.nodeAliases,
1607
+ baseId,
1608
+ );
1372
1609
 
1373
1610
  const baseMap: FieldChangeMap = over?.fieldChanges ?? new Map<FieldKey, FieldChange>();
1374
1611
 
@@ -1384,7 +1621,7 @@ export class ModularChangeFamily
1384
1621
  )
1385
1622
  : change.fieldChanges;
1386
1623
 
1387
- const rebasedChange: NodeChangeset = {};
1624
+ const rebasedChange: Mutable<NodeChangeset> = {};
1388
1625
 
1389
1626
  if (fieldChanges !== undefined && fieldChanges.size > 0) {
1390
1627
  rebasedChange.fieldChanges = fieldChanges;
@@ -1402,6 +1639,37 @@ export class ModularChangeFamily
1402
1639
  return rebasedChange;
1403
1640
  }
1404
1641
 
1642
+ private updateConstraints(
1643
+ rebasedFields: FieldChangeMap,
1644
+ rebasedNodes: ChangeAtomIdBTree<NodeChangeset>,
1645
+ rebasedRoots: RootNodeTable,
1646
+ constraintState: ConstraintState,
1647
+ revertConstraintState: ConstraintState,
1648
+ ): void {
1649
+ this.updateConstraintsForFields(
1650
+ rebasedFields,
1651
+ NodeAttachState.Attached,
1652
+ NodeAttachState.Attached,
1653
+ constraintState,
1654
+ revertConstraintState,
1655
+ rebasedNodes,
1656
+ );
1657
+
1658
+ for (const [_detachId, nodeId] of rebasedRoots.nodeChanges.entries()) {
1659
+ // XXX: This is incorrect if the rebased changeset attaches the node.
1660
+ // Efficiently computing whether the changeset attaches the node would require maintaining a mapping from node ID to attach ID.
1661
+ const detachedInOutput = true;
1662
+ this.updateConstraintsForNode(
1663
+ nodeId,
1664
+ NodeAttachState.Detached,
1665
+ detachedInOutput ? NodeAttachState.Detached : NodeAttachState.Attached,
1666
+ rebasedNodes,
1667
+ constraintState,
1668
+ revertConstraintState,
1669
+ );
1670
+ }
1671
+ }
1672
+
1405
1673
  private updateConstraintsForFields(
1406
1674
  fields: FieldChangeMap,
1407
1675
  parentInputAttachState: NodeAttachState,
@@ -1412,20 +1680,18 @@ export class ModularChangeFamily
1412
1680
  ): void {
1413
1681
  for (const field of fields.values()) {
1414
1682
  const handler = getChangeHandler(this.fieldKinds, field.fieldKind);
1415
- for (const [nodeId, inputIndex, outputIndex] of handler.getNestedChanges(field.change)) {
1416
- const isInputDetached = inputIndex === undefined;
1417
- const inputAttachState =
1418
- parentInputAttachState === NodeAttachState.Detached || isInputDetached
1419
- ? NodeAttachState.Detached
1420
- : NodeAttachState.Attached;
1421
- const isOutputDetached = outputIndex === undefined;
1683
+ for (const [nodeId] of handler.getNestedChanges(field.change)) {
1684
+ // XXX: This is incorrect if the rebased changeset detaches this node.
1685
+ // Efficiently computing whether the changeset detaches the node would require maintaining a mapping from node ID to detach ID.
1686
+ const isOutputDetached = false;
1422
1687
  const outputAttachState =
1423
1688
  parentOutputAttachState === NodeAttachState.Detached || isOutputDetached
1424
1689
  ? NodeAttachState.Detached
1425
1690
  : NodeAttachState.Attached;
1691
+
1426
1692
  this.updateConstraintsForNode(
1427
1693
  nodeId,
1428
- inputAttachState,
1694
+ parentInputAttachState,
1429
1695
  outputAttachState,
1430
1696
  nodes,
1431
1697
  constraintState,
@@ -1443,12 +1709,15 @@ export class ModularChangeFamily
1443
1709
  constraintState: ConstraintState,
1444
1710
  revertConstraintState: ConstraintState,
1445
1711
  ): void {
1446
- const node =
1447
- nodes.get([nodeId.revision, nodeId.localId]) ?? fail(0xb24 /* Unknown node ID */);
1712
+ const node = getFromChangeAtomIdMap(nodes, nodeId) ?? fail(0xb24 /* Unknown node ID */);
1713
+
1714
+ const updatedNode: Mutable<NodeChangeset> = { ...node };
1715
+ setInChangeAtomIdMap(nodes, nodeId, updatedNode);
1716
+
1448
1717
  if (node.nodeExistsConstraint !== undefined) {
1449
1718
  const isNowViolated = inputAttachState === NodeAttachState.Detached;
1450
1719
  if (node.nodeExistsConstraint.violated !== isNowViolated) {
1451
- node.nodeExistsConstraint = {
1720
+ updatedNode.nodeExistsConstraint = {
1452
1721
  ...node.nodeExistsConstraint,
1453
1722
  violated: isNowViolated,
1454
1723
  };
@@ -1458,7 +1727,7 @@ export class ModularChangeFamily
1458
1727
  if (node.nodeExistsConstraintOnRevert !== undefined) {
1459
1728
  const isNowViolated = outputAttachState === NodeAttachState.Detached;
1460
1729
  if (node.nodeExistsConstraintOnRevert.violated !== isNowViolated) {
1461
- node.nodeExistsConstraintOnRevert = {
1730
+ updatedNode.nodeExistsConstraintOnRevert = {
1462
1731
  ...node.nodeExistsConstraintOnRevert,
1463
1732
  violated: isNowViolated,
1464
1733
  };
@@ -1480,7 +1749,13 @@ export class ModularChangeFamily
1480
1749
 
1481
1750
  private pruneFieldMap(
1482
1751
  changeset: FieldChangeMap | undefined,
1752
+ parentId: NodeId | undefined,
1483
1753
  nodeMap: ChangeAtomIdBTree<NodeChangeset>,
1754
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>,
1755
+ aliases: ChangeAtomIdBTree<NodeId>,
1756
+ roots: RootNodeTable,
1757
+ fieldsWithRootMoves: TupleBTree<FieldIdKey, boolean>,
1758
+ fieldsToRootChanges: TupleBTree<FieldIdKey, ChangeAtomId[]>,
1484
1759
  ): FieldChangeMap | undefined {
1485
1760
  if (changeset === undefined) {
1486
1761
  return undefined;
@@ -1491,10 +1766,48 @@ export class ModularChangeFamily
1491
1766
  const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
1492
1767
 
1493
1768
  const prunedFieldChangeset = handler.rebaser.prune(fieldChange.change, (nodeId) =>
1494
- this.pruneNodeChange(nodeId, nodeMap),
1769
+ this.pruneNodeChange(
1770
+ nodeId,
1771
+ nodeMap,
1772
+ nodeToParent,
1773
+ aliases,
1774
+ roots,
1775
+ fieldsWithRootMoves,
1776
+ fieldsToRootChanges,
1777
+ ),
1495
1778
  );
1496
1779
 
1497
- if (!handler.isEmpty(prunedFieldChangeset)) {
1780
+ const fieldId: FieldId = { nodeId: parentId, field };
1781
+ const fieldIdKey = fieldIdKeyFromFieldId(fieldId);
1782
+ const rootsWithChanges = fieldsToRootChanges.get(fieldIdKey) ?? [];
1783
+ let hasRootWithNodeChange = false;
1784
+ for (const rootId of rootsWithChanges) {
1785
+ const nodeId =
1786
+ getFromChangeAtomIdMap(roots.nodeChanges, rootId) ?? fail("No root change found");
1787
+
1788
+ const isRootChangeEmpty =
1789
+ this.pruneNodeChange(
1790
+ nodeId,
1791
+ nodeMap,
1792
+ nodeToParent,
1793
+ aliases,
1794
+ roots,
1795
+ fieldsWithRootMoves,
1796
+ fieldsToRootChanges,
1797
+ ) === undefined;
1798
+
1799
+ if (isRootChangeEmpty) {
1800
+ roots.nodeChanges.delete([rootId.revision, rootId.localId]);
1801
+ tryRemoveDetachLocation(roots, rootId, 1);
1802
+ } else {
1803
+ hasRootWithNodeChange = true;
1804
+ }
1805
+ }
1806
+
1807
+ const hasRootChanges =
1808
+ hasRootWithNodeChange || fieldsWithRootMoves.get(fieldIdKey) === true;
1809
+
1810
+ if (!handler.isEmpty(prunedFieldChangeset) || hasRootChanges) {
1498
1811
  prunedChangeset.set(field, { ...fieldChange, change: brand(prunedFieldChangeset) });
1499
1812
  }
1500
1813
  }
@@ -1502,15 +1815,65 @@ export class ModularChangeFamily
1502
1815
  return prunedChangeset.size > 0 ? prunedChangeset : undefined;
1503
1816
  }
1504
1817
 
1818
+ private pruneRoots(
1819
+ roots: RootNodeTable,
1820
+ nodeMap: ChangeAtomIdBTree<NodeChangeset>,
1821
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>,
1822
+ aliases: ChangeAtomIdBTree<NodeId>,
1823
+ fieldsWithRootMoves: TupleBTree<FieldIdKey, boolean>,
1824
+ fieldsToRootChanges: TupleBTree<FieldIdKey, ChangeAtomId[]>,
1825
+ ): RootNodeTable {
1826
+ const pruned: RootNodeTable = { ...roots, nodeChanges: newChangeAtomIdBTree() };
1827
+ for (const [rootIdKey, nodeId] of roots.nodeChanges.entries()) {
1828
+ const rootId: ChangeAtomId = { revision: rootIdKey[0], localId: rootIdKey[1] };
1829
+ const hasDetachLocation = roots.detachLocations.getFirst(rootId, 1).value !== undefined;
1830
+
1831
+ // If the root has a detach location it should be pruned by recursion when pruning the field it was detached from.
1832
+ const prunedId = hasDetachLocation
1833
+ ? nodeId
1834
+ : this.pruneNodeChange(
1835
+ nodeId,
1836
+ nodeMap,
1837
+ nodeToParent,
1838
+ aliases,
1839
+ roots,
1840
+ fieldsWithRootMoves,
1841
+ fieldsToRootChanges,
1842
+ );
1843
+
1844
+ if (prunedId !== undefined) {
1845
+ pruned.nodeChanges.set(rootIdKey, prunedId);
1846
+ }
1847
+
1848
+ tryRemoveDetachLocation(pruned, rootId, 1);
1849
+ }
1850
+
1851
+ return pruned;
1852
+ }
1853
+
1505
1854
  private pruneNodeChange(
1506
1855
  nodeId: NodeId,
1507
- nodeMap: ChangeAtomIdBTree<NodeChangeset>,
1856
+ nodes: ChangeAtomIdBTree<NodeChangeset>,
1857
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>,
1858
+ aliases: ChangeAtomIdBTree<NodeId>,
1859
+ roots: RootNodeTable,
1860
+ fieldsWithRootMoves: TupleBTree<FieldIdKey, boolean>,
1861
+ fieldsToRootChanges: TupleBTree<FieldIdKey, ChangeAtomId[]>,
1508
1862
  ): NodeId | undefined {
1509
- const changeset = nodeChangeFromId(nodeMap, nodeId);
1863
+ const changeset = nodeChangeFromId(nodes, aliases, nodeId);
1510
1864
  const prunedFields =
1511
1865
  changeset.fieldChanges === undefined
1512
1866
  ? undefined
1513
- : this.pruneFieldMap(changeset.fieldChanges, nodeMap);
1867
+ : this.pruneFieldMap(
1868
+ changeset.fieldChanges,
1869
+ nodeId,
1870
+ nodes,
1871
+ nodeToParent,
1872
+ aliases,
1873
+ roots,
1874
+ fieldsWithRootMoves,
1875
+ fieldsToRootChanges,
1876
+ );
1514
1877
 
1515
1878
  const prunedChange = { ...changeset, fieldChanges: prunedFields };
1516
1879
  if (prunedChange.fieldChanges === undefined) {
@@ -1518,10 +1881,17 @@ export class ModularChangeFamily
1518
1881
  }
1519
1882
 
1520
1883
  if (isEmptyNodeChangeset(prunedChange)) {
1521
- nodeMap.delete([nodeId.revision, nodeId.localId]);
1884
+ const nodeIdKey: [RevisionTag | undefined, ChangesetLocalId] = [
1885
+ nodeId.revision,
1886
+ nodeId.localId,
1887
+ ];
1888
+
1889
+ // TODO: Shouldn't we also delete all aliases associated with this node?
1890
+ nodes.delete(nodeIdKey);
1891
+ nodeToParent.delete(nodeIdKey);
1522
1892
  return undefined;
1523
1893
  } else {
1524
- setInChangeAtomIdMap(nodeMap, nodeId, prunedChange);
1894
+ setInChangeAtomIdMap(nodes, nodeId, prunedChange);
1525
1895
  return nodeId;
1526
1896
  }
1527
1897
  }
@@ -1548,8 +1918,11 @@ export class ModularChangeFamily
1548
1918
  const updatedNodeToParent = replaceIdMapRevisions(
1549
1919
  change.nodeToParent,
1550
1920
  replacer,
1551
- (fieldId) =>
1552
- replaceFieldIdRevision(normalizeFieldId(fieldId, change.nodeAliases), replacer),
1921
+ (location) =>
1922
+ replaceNodeLocationRevision(
1923
+ normalizeNodeLocation(location, change.nodeAliases),
1924
+ replacer,
1925
+ ),
1553
1926
  );
1554
1927
 
1555
1928
  const updated: Mutable<ModularChangeset> = {
@@ -1557,6 +1930,7 @@ export class ModularChangeFamily
1557
1930
  fieldChanges: updatedFields,
1558
1931
  nodeChanges: updatedNodes,
1559
1932
  nodeToParent: updatedNodeToParent,
1933
+ rootNodes: replaceRootTableRevision(change.rootNodes, replacer, change.nodeAliases),
1560
1934
 
1561
1935
  // We've updated all references to old node IDs, so we no longer need an alias table.
1562
1936
  nodeAliases: newChangeAtomIdBTree(),
@@ -1620,7 +1994,7 @@ export class ModularChangeFamily
1620
1994
  fields: FieldChangeMap,
1621
1995
  nodes: ChangeAtomIdBTree<NodeChangeset>,
1622
1996
  ): CrossFieldKeyTable {
1623
- const keys: CrossFieldKeyTable = newCrossFieldKeyTable();
1997
+ const keys: CrossFieldKeyTable = newCrossFieldRangeTable();
1624
1998
  this.populateCrossFieldKeyTableForFieldMap(keys, fields, undefined);
1625
1999
  nodes.forEachPair(([revision, localId], node) => {
1626
2000
  if (node.fieldChanges !== undefined) {
@@ -1652,8 +2026,15 @@ export class ModularChangeFamily
1652
2026
  public buildEditor(
1653
2027
  mintRevisionTag: () => RevisionTag,
1654
2028
  changeReceiver: (change: TaggedChange<ModularChangeset>) => void,
2029
+ editorOptions?: ModularEditorOptions,
1655
2030
  ): ModularEditBuilder {
1656
- return new ModularEditBuilder(this, this.fieldKinds, changeReceiver, this.codecOptions);
2031
+ return new ModularEditBuilder(
2032
+ this,
2033
+ this.fieldKinds,
2034
+ changeReceiver,
2035
+ this.codecOptions,
2036
+ editorOptions,
2037
+ );
1657
2038
  }
1658
2039
 
1659
2040
  private createEmptyFieldChange(fieldKind: FieldKindIdentifier): FieldChange {
@@ -1661,63 +2042,6 @@ export class ModularChangeFamily
1661
2042
  return { fieldKind, change: brand(emptyChange) };
1662
2043
  }
1663
2044
 
1664
- public validateChangeset(change: ModularChangeset): void {
1665
- let numNodes = this.validateFieldChanges(change, change.fieldChanges, undefined);
1666
-
1667
- for (const [[revision, localId], node] of change.nodeChanges.entries()) {
1668
- if (node.fieldChanges === undefined) {
1669
- continue;
1670
- }
1671
-
1672
- const nodeId: NodeId = { revision, localId };
1673
- const numChildren = this.validateFieldChanges(change, node.fieldChanges, nodeId);
1674
-
1675
- numNodes += numChildren;
1676
- }
1677
-
1678
- assert(
1679
- numNodes === change.nodeChanges.size,
1680
- 0xa4d /* Node table contains unparented nodes */,
1681
- );
1682
- }
1683
-
1684
- /**
1685
- * Asserts that each child and cross field key in each field has a correct entry in
1686
- * `nodeToParent` or `crossFieldKeyTable`.
1687
- * @returns the number of children found.
1688
- */
1689
- private validateFieldChanges(
1690
- change: ModularChangeset,
1691
- fieldChanges: FieldChangeMap,
1692
- nodeParent: NodeId | undefined,
1693
- ): number {
1694
- let numChildren = 0;
1695
- for (const [field, fieldChange] of fieldChanges.entries()) {
1696
- const fieldId = { nodeId: nodeParent, field };
1697
- const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
1698
- for (const [child, _index] of handler.getNestedChanges(fieldChange.change)) {
1699
- const parentFieldId = getParentFieldId(change, child);
1700
- assert(
1701
- areEqualFieldIds(parentFieldId, fieldId),
1702
- 0xa4e /* Inconsistent node parentage */,
1703
- );
1704
- numChildren += 1;
1705
- }
1706
-
1707
- for (const keyRange of handler.getCrossFieldKeys(fieldChange.change)) {
1708
- const fields = getFieldsForCrossFieldKey(change, keyRange.key, keyRange.count);
1709
- assert(
1710
- fields.length === 1 &&
1711
- fields[0] !== undefined &&
1712
- areEqualFieldIds(fields[0], fieldId),
1713
- 0xa4f /* Inconsistent cross field keys */,
1714
- );
1715
- }
1716
- }
1717
-
1718
- return numChildren;
1719
- }
1720
-
1721
2045
  private getEffectiveChange(change: ModularChangeset): ModularChangeset {
1722
2046
  if (hasConflicts(change)) {
1723
2047
  return this.muteChange(change);
@@ -1731,7 +2055,8 @@ export class ModularChangeFamily
1731
2055
  private muteChange(change: ModularChangeset): ModularChangeset {
1732
2056
  const muted: Mutable<ModularChangeset> = {
1733
2057
  ...change,
1734
- crossFieldKeys: newCrossFieldKeyTable(),
2058
+ rootNodes: muteRootChanges(change.rootNodes),
2059
+ crossFieldKeys: newCrossFieldRangeTable(),
1735
2060
  fieldChanges: this.muteFieldChanges(change.fieldChanges),
1736
2061
  nodeChanges: brand(change.nodeChanges.mapValues((v) => this.muteNodeChange(v))),
1737
2062
  };
@@ -1768,7 +2093,7 @@ function replaceCrossFieldKeyTableRevisions(
1768
2093
  replacer: RevisionReplacer,
1769
2094
  nodeAliases: ChangeAtomIdBTree<NodeId>,
1770
2095
  ): CrossFieldKeyTable {
1771
- const updated: CrossFieldKeyTable = newCrossFieldKeyTable();
2096
+ const updated: CrossFieldKeyTable = newCrossFieldRangeTable();
1772
2097
  for (const entry of table.entries()) {
1773
2098
  const key = entry.start;
1774
2099
  const updatedKey: CrossFieldKey = replacer.getUpdatedAtomId(key, entry.length);
@@ -1878,6 +2203,20 @@ function composeBuildsDestroysAndRefreshers(
1878
2203
  }
1879
2204
  }
1880
2205
 
2206
+ // 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.
2207
+ if (change1.builds !== undefined && change2.refreshers !== undefined) {
2208
+ for (const [key, chunk] of change2.refreshers.entries()) {
2209
+ assert(chunk.topLevelLength === 1, "Expected refresher chunk to have length 1");
2210
+ const match = change1.builds.getPairOrNextLower(key);
2211
+ if (match !== undefined) {
2212
+ const [buildKey, buildChunk] = match;
2213
+ if (buildKey[0] === key[0] && buildKey[1] + buildChunk.topLevelLength > key[1]) {
2214
+ allRefreshers.delete(key);
2215
+ }
2216
+ }
2217
+ }
2218
+ }
2219
+
1881
2220
  return { allBuilds, allDestroys, allRefreshers };
1882
2221
  }
1883
2222
 
@@ -1909,30 +2248,69 @@ export function* relevantRemovedRoots(
1909
2248
  change: ModularChangeset,
1910
2249
  fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
1911
2250
  ): Iterable<DeltaDetachedNodeId> {
1912
- yield* relevantRemovedRootsFromFields(change.fieldChanges, change.nodeChanges, fieldKinds);
2251
+ const rootIds: ChangeAtomIdRangeMap<boolean> = newChangeAtomIdRangeMap();
2252
+ addAttachesToSet(change, rootIds);
2253
+ addRenamesToSet(change, rootIds);
2254
+
2255
+ for (const [[revision, localId]] of change.rootNodes.nodeChanges.entries()) {
2256
+ rootIds.set({ revision, localId }, 1, true);
2257
+ }
2258
+
2259
+ for (const entry of rootIds.entries()) {
2260
+ for (let offset = 0; offset < entry.length; offset++) {
2261
+ const detachId = offsetChangeAtomId(entry.start, offset);
2262
+ yield makeDetachedNodeId(detachId.revision, detachId.localId);
2263
+ }
2264
+ }
1913
2265
  }
1914
2266
 
1915
- function* relevantRemovedRootsFromNode(
1916
- node: NodeId,
1917
- nodeChanges: ChangeAtomIdBTree<NodeChangeset>,
1918
- fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
1919
- ): Iterable<DeltaDetachedNodeId> {
1920
- const nodeChangeset = nodeChangeFromId(nodeChanges, node);
1921
- if (nodeChangeset.fieldChanges !== undefined) {
1922
- yield* relevantRemovedRootsFromFields(nodeChangeset.fieldChanges, nodeChanges, fieldKinds);
2267
+ function addAttachesToSet(
2268
+ change: ModularChangeset,
2269
+ rootIds: ChangeAtomIdRangeMap<boolean>,
2270
+ ): void {
2271
+ // This includes each attach which does not have a corresponding detach.
2272
+ for (const entry of change.crossFieldKeys.entries()) {
2273
+ if (entry.start.target !== NodeMoveType.Attach) {
2274
+ continue;
2275
+ }
2276
+
2277
+ for (const detachIdEntry of change.rootNodes.newToOldId.getAll2(
2278
+ entry.start,
2279
+ entry.length,
2280
+ )) {
2281
+ const detachId =
2282
+ detachIdEntry.value ?? offsetChangeAtomId(entry.start, detachIdEntry.offset);
2283
+ for (const detachEntry of change.crossFieldKeys.getAll2(
2284
+ { ...detachId, target: NodeMoveType.Detach },
2285
+ detachIdEntry.length,
2286
+ )) {
2287
+ if (detachEntry.value === undefined) {
2288
+ rootIds.set(
2289
+ offsetChangeAtomId(detachId, detachEntry.offset),
2290
+ detachEntry.length,
2291
+ true,
2292
+ );
2293
+ }
2294
+ }
2295
+ }
1923
2296
  }
1924
2297
  }
1925
2298
 
1926
- function* relevantRemovedRootsFromFields(
1927
- change: FieldChangeMap,
1928
- nodeChanges: ChangeAtomIdBTree<NodeChangeset>,
1929
- fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
1930
- ): Iterable<DeltaDetachedNodeId> {
1931
- const delegate = (node: NodeId): Iterable<DeltaDetachedNodeId> =>
1932
- relevantRemovedRootsFromNode(node, nodeChanges, fieldKinds);
1933
- for (const [_, fieldChange] of change) {
1934
- const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
1935
- yield* handler.relevantRemovedRoots(fieldChange.change, delegate);
2299
+ function addRenamesToSet(
2300
+ change: ModularChangeset,
2301
+ rootIds: ChangeAtomIdRangeMap<boolean>,
2302
+ ): void {
2303
+ for (const renameEntry of change.rootNodes.oldToNewId.entries()) {
2304
+ for (const detachEntry of change.crossFieldKeys.getAll2(
2305
+ { ...renameEntry.start, target: NodeMoveType.Detach },
2306
+ renameEntry.length,
2307
+ )) {
2308
+ // We only want to include renames of nodes which are detached in the input context of the changeset.
2309
+ // So if there is a detach for the node, the rename is not relevant.
2310
+ if (detachEntry.value === undefined) {
2311
+ rootIds.set(renameEntry.start, renameEntry.length, true);
2312
+ }
2313
+ }
1936
2314
  }
1937
2315
  }
1938
2316
 
@@ -1990,6 +2368,7 @@ export function updateRefreshers(
1990
2368
  }
1991
2369
 
1992
2370
  const {
2371
+ rebaseVersion,
1993
2372
  fieldChanges,
1994
2373
  nodeChanges,
1995
2374
  nodeToParent,
@@ -2001,14 +2380,17 @@ export function updateRefreshers(
2001
2380
  constraintViolationCountOnRevert,
2002
2381
  builds,
2003
2382
  destroys,
2383
+ rootNodes,
2004
2384
  } = change;
2005
2385
 
2006
2386
  return makeModularChangeset({
2387
+ rebaseVersion,
2007
2388
  fieldChanges,
2008
2389
  nodeChanges,
2009
2390
  nodeToParent,
2010
2391
  nodeAliases,
2011
2392
  crossFieldKeys,
2393
+ rootNodes,
2012
2394
  maxId: maxId as number,
2013
2395
  revisions,
2014
2396
  constraintViolationCount,
@@ -2031,18 +2413,42 @@ export function intoDelta(
2031
2413
  ): DeltaRoot {
2032
2414
  const change = taggedChange.change;
2033
2415
  const rootDelta: Mutable<DeltaRoot> = {};
2034
- const global: DeltaDetachedNodeChanges[] = [];
2035
- const rename: DeltaDetachedNodeRename[] = [];
2036
2416
 
2037
2417
  if (!hasConflicts(change)) {
2038
2418
  // If there are no constraint violations, then tree changes apply.
2039
2419
  const fieldDeltas = intoDeltaImpl(
2040
2420
  change.fieldChanges,
2041
2421
  change.nodeChanges,
2422
+ change.nodeAliases,
2042
2423
  fieldKinds,
2043
- global,
2044
- rename,
2045
2424
  );
2425
+
2426
+ const global: DeltaDetachedNodeChanges[] = [];
2427
+ for (const [[major, minor], nodeId] of change.rootNodes.nodeChanges.entries()) {
2428
+ global.push({
2429
+ id: { major, minor },
2430
+ fields: deltaFromNodeChange(
2431
+ nodeChangeFromId(change.nodeChanges, change.nodeAliases, nodeId),
2432
+ change.nodeChanges,
2433
+ change.nodeAliases,
2434
+ fieldKinds,
2435
+ ),
2436
+ });
2437
+ }
2438
+
2439
+ const rename: DeltaDetachedNodeRename[] = [];
2440
+ for (const {
2441
+ start: oldId,
2442
+ value: newId,
2443
+ length,
2444
+ } of change.rootNodes.oldToNewId.entries()) {
2445
+ rename.push({
2446
+ count: length,
2447
+ oldId: makeDetachedNodeId(oldId.revision, oldId.localId),
2448
+ newId: makeDetachedNodeId(newId.revision, newId.localId),
2449
+ });
2450
+ }
2451
+
2046
2452
  if (fieldDeltas.size > 0) {
2047
2453
  rootDelta.fields = fieldDeltas;
2048
2454
  }
@@ -2071,6 +2477,7 @@ export function intoDelta(
2071
2477
  if (change.refreshers && change.refreshers.size > 0) {
2072
2478
  rootDelta.refreshers = copyDetachedNodes(change.refreshers);
2073
2479
  }
2480
+
2074
2481
  return rootDelta;
2075
2482
  }
2076
2483
 
@@ -2096,32 +2503,21 @@ function copyDetachedNodes(
2096
2503
  function intoDeltaImpl(
2097
2504
  change: FieldChangeMap,
2098
2505
  nodeChanges: ChangeAtomIdBTree<NodeChangeset>,
2506
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
2099
2507
  fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
2100
- global: DeltaDetachedNodeChanges[],
2101
- rename: DeltaDetachedNodeRename[],
2102
2508
  ): Map<FieldKey, DeltaFieldChanges> {
2103
2509
  const delta: Map<FieldKey, DeltaFieldChanges> = new Map();
2104
2510
 
2105
2511
  for (const [field, fieldChange] of change) {
2106
- const {
2107
- local: fieldChanges,
2108
- global: fieldGlobal,
2109
- rename: fieldRename,
2110
- } = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(
2512
+ const fieldDelta = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(
2111
2513
  fieldChange.change,
2112
2514
  (childChange): DeltaFieldMap => {
2113
- const nodeChange = nodeChangeFromId(nodeChanges, childChange);
2114
- return deltaFromNodeChange(nodeChange, nodeChanges, fieldKinds, global, rename);
2515
+ const nodeChange = nodeChangeFromId(nodeChanges, nodeAliases, childChange);
2516
+ return deltaFromNodeChange(nodeChange, nodeChanges, nodeAliases, fieldKinds);
2115
2517
  },
2116
2518
  );
2117
- if (fieldChanges !== undefined && fieldChanges.marks.length > 0) {
2118
- delta.set(field, fieldChanges);
2119
- }
2120
- for (const c of fieldGlobal ?? []) {
2121
- global.push(c);
2122
- }
2123
- for (const r of fieldRename ?? []) {
2124
- rename.push(r);
2519
+ if (fieldDelta !== undefined && fieldDelta.marks.length > 0) {
2520
+ delta.set(field, fieldDelta);
2125
2521
  }
2126
2522
  }
2127
2523
  return delta;
@@ -2130,12 +2526,11 @@ function intoDeltaImpl(
2130
2526
  function deltaFromNodeChange(
2131
2527
  change: NodeChangeset,
2132
2528
  nodeChanges: ChangeAtomIdBTree<NodeChangeset>,
2529
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
2133
2530
  fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
2134
- global: DeltaDetachedNodeChanges[],
2135
- rename: DeltaDetachedNodeRename[],
2136
2531
  ): DeltaFieldMap {
2137
2532
  if (change.fieldChanges !== undefined) {
2138
- return intoDeltaImpl(change.fieldChanges, nodeChanges, fieldKinds, global, rename);
2533
+ return intoDeltaImpl(change.fieldChanges, nodeChanges, nodeAliases, fieldKinds);
2139
2534
  }
2140
2535
  // TODO: update the API to allow undefined to be returned here
2141
2536
  return new Map();
@@ -2200,19 +2595,22 @@ export function getChangeHandler(
2200
2595
  return getFieldKind(fieldKinds, kind).changeHandler;
2201
2596
  }
2202
2597
 
2203
- // TODO: TFieldData could instead just be a numeric ID generated by the CrossFieldTable
2204
- // The CrossFieldTable could have a generic field ID to context table
2205
- interface CrossFieldTable<TFieldData> {
2206
- srcTable: CrossFieldMap<unknown>;
2207
- dstTable: CrossFieldMap<unknown>;
2208
- srcDependents: CrossFieldMap<TFieldData>;
2209
- dstDependents: CrossFieldMap<TFieldData>;
2210
- invalidatedFields: Set<TFieldData>;
2211
- }
2598
+ interface InvertTable {
2599
+ change: ModularChangeset;
2600
+ isRollback: boolean;
2212
2601
 
2213
- interface InvertTable extends CrossFieldTable<FieldChange> {
2602
+ // Entries are keyed on attach ID
2603
+ entries: CrossFieldMap<NodeId>;
2214
2604
  originalFieldToContext: Map<FieldChange, InvertContext>;
2215
- invertedNodeToParent: ChangeAtomIdBTree<FieldId>;
2605
+ invertedNodeToParent: ChangeAtomIdBTree<NodeLocation>;
2606
+ invertRevision: RevisionTag;
2607
+ invalidatedFields: Set<FieldChange>;
2608
+ invertedRoots: RootNodeTable;
2609
+
2610
+ /**
2611
+ * Maps from attach ID in the inverted changeset to the corresponding detach ID in the base changeset.
2612
+ */
2613
+ attachToDetachId: ChangeAtomIdRangeMap<ChangeAtomId>;
2216
2614
  }
2217
2615
 
2218
2616
  interface InvertContext {
@@ -2220,7 +2618,11 @@ interface InvertContext {
2220
2618
  invertedField: FieldChange;
2221
2619
  }
2222
2620
 
2223
- interface RebaseTable extends CrossFieldTable<FieldChange> {
2621
+ interface RebaseTable {
2622
+ readonly rebaseVersion: RebaseVersion;
2623
+
2624
+ // Entries are keyed on attach ID
2625
+ readonly entries: CrossFieldMap<RebaseDetachedNodeEntry>;
2224
2626
  readonly baseChange: ModularChangeset;
2225
2627
  readonly newChange: ModularChangeset;
2226
2628
 
@@ -2229,10 +2631,13 @@ interface RebaseTable extends CrossFieldTable<FieldChange> {
2229
2631
  * to the context for the field.
2230
2632
  */
2231
2633
  readonly baseFieldToContext: Map<FieldChange, RebaseFieldContext>;
2634
+ readonly baseRoots: RootNodeTable;
2232
2635
  readonly baseToRebasedNodeId: ChangeAtomIdBTree<NodeId>;
2233
2636
  readonly rebasedFields: Set<FieldChange>;
2234
- readonly rebasedNodeToParent: ChangeAtomIdBTree<FieldId>;
2235
- readonly rebasedCrossFieldKeys: CrossFieldKeyTable;
2637
+ readonly rebasedNodeToParent: ChangeAtomIdBTree<NodeLocation>;
2638
+ readonly rebasedDetachLocations: ChangeAtomIdRangeMap<FieldId>;
2639
+ readonly movedDetaches: ChangeAtomIdRangeMap<boolean>;
2640
+ readonly rebasedRootNodes: RootNodeTable;
2236
2641
 
2237
2642
  /**
2238
2643
  * List of unprocessed (newId, baseId) pairs encountered so far.
@@ -2246,7 +2651,11 @@ interface RebaseTable extends CrossFieldTable<FieldChange> {
2246
2651
  readonly fieldsWithUnattachedChild: Set<FieldChange>;
2247
2652
  }
2248
2653
 
2249
- type FieldIdKey = readonly [RevisionTag | undefined, ChangesetLocalId | undefined, FieldKey];
2654
+ export type FieldIdKey = readonly [
2655
+ RevisionTag | undefined,
2656
+ ChangesetLocalId | undefined,
2657
+ FieldKey,
2658
+ ];
2250
2659
 
2251
2660
  interface RebaseFieldContext {
2252
2661
  baseChange: FieldChange;
@@ -2258,32 +2667,43 @@ interface RebaseFieldContext {
2258
2667
  * The set of node IDs in the base changeset which should be included in the rebased field,
2259
2668
  * even if there is no corresponding node changeset in the new change.
2260
2669
  */
2261
- baseNodeIds: NodeId[];
2670
+ baseNodeIds: ChangeAtomIdBTree<boolean>;
2262
2671
  }
2263
2672
 
2264
2673
  function newComposeTable(
2265
2674
  baseChange: ModularChangeset,
2266
2675
  newChange: ModularChangeset,
2267
- composedNodeToParent: ChangeAtomIdBTree<FieldId>,
2676
+ composedRootNodes: RootNodeTable,
2677
+ movedCrossFieldKeys: CrossFieldKeyTable,
2678
+ removedCrossFieldKeys: CrossFieldRangeTable<boolean>,
2679
+ pendingCompositions: PendingCompositions,
2268
2680
  ): ComposeTable {
2269
2681
  return {
2270
- ...newCrossFieldTable<FieldChange>(),
2682
+ rebaseVersion: Math.max(
2683
+ baseChange.rebaseVersion,
2684
+ newChange.rebaseVersion,
2685
+ ) as RebaseVersion,
2686
+ entries: newDetachedEntryMap(),
2271
2687
  baseChange,
2272
2688
  newChange,
2273
2689
  fieldToContext: new Map(),
2274
2690
  newFieldToBaseField: new Map(),
2275
2691
  newToBaseNodeId: newChangeAtomIdBTree(),
2276
2692
  composedNodes: new Set(),
2277
- composedNodeToParent,
2278
- pendingCompositions: {
2279
- nodeIdsToCompose: [],
2280
- affectedBaseFields: newFieldIdKeyBTree(),
2281
- affectedNewFields: newFieldIdKeyBTree(),
2282
- },
2693
+ movedNodeToParent: newChangeAtomIdBTree(),
2694
+ composedRootNodes,
2695
+ movedCrossFieldKeys,
2696
+ removedCrossFieldKeys,
2697
+ renamesToDelete: newChangeAtomIdRangeMap(),
2698
+ pendingCompositions,
2283
2699
  };
2284
2700
  }
2285
2701
 
2286
- interface ComposeTable extends CrossFieldTable<FieldChange> {
2702
+ interface ComposeTable {
2703
+ readonly rebaseVersion: RebaseVersion;
2704
+
2705
+ // Entries are keyed on detach ID
2706
+ readonly entries: ChangeAtomIdRangeMap<DetachedNodeEntry>;
2287
2707
  readonly baseChange: ModularChangeset;
2288
2708
  readonly newChange: ModularChangeset;
2289
2709
 
@@ -2294,7 +2714,11 @@ interface ComposeTable extends CrossFieldTable<FieldChange> {
2294
2714
  readonly newFieldToBaseField: Map<FieldChange, FieldChange>;
2295
2715
  readonly newToBaseNodeId: ChangeAtomIdBTree<NodeId>;
2296
2716
  readonly composedNodes: Set<NodeChangeset>;
2297
- readonly composedNodeToParent: ChangeAtomIdBTree<FieldId>;
2717
+ readonly movedNodeToParent: ChangeAtomIdBTree<NodeLocation>;
2718
+ readonly composedRootNodes: RootNodeTable;
2719
+ readonly movedCrossFieldKeys: CrossFieldKeyTable;
2720
+ readonly removedCrossFieldKeys: CrossFieldRangeTable<boolean>;
2721
+ readonly renamesToDelete: ChangeAtomIdRangeMap<boolean>;
2298
2722
  readonly pendingCompositions: PendingCompositions;
2299
2723
  }
2300
2724
 
@@ -2309,11 +2733,6 @@ interface PendingCompositions {
2309
2733
  * The set of fields in the base changeset which have been affected by a cross field effect.
2310
2734
  */
2311
2735
  readonly affectedBaseFields: BTree<FieldIdKey, true>;
2312
-
2313
- /**
2314
- * The set of fields in the new changeset which have been affected by a cross field effect.
2315
- */
2316
- readonly affectedNewFields: BTree<FieldIdKey, true>;
2317
2736
  }
2318
2737
 
2319
2738
  interface ComposeFieldContext {
@@ -2326,16 +2745,6 @@ interface ComposeFieldContext {
2326
2745
  composedChange: FieldChange;
2327
2746
  }
2328
2747
 
2329
- function newCrossFieldTable<T>(): CrossFieldTable<T> {
2330
- return {
2331
- srcTable: newChangeAtomIdRangeMap(),
2332
- dstTable: newChangeAtomIdRangeMap(),
2333
- srcDependents: newChangeAtomIdRangeMap(),
2334
- dstDependents: newChangeAtomIdRangeMap(),
2335
- invalidatedFields: new Set(),
2336
- };
2337
- }
2338
-
2339
2748
  interface ConstraintState {
2340
2749
  violationCount: number;
2341
2750
  }
@@ -2346,282 +2755,687 @@ function newConstraintState(violationCount: number): ConstraintState {
2346
2755
  };
2347
2756
  }
2348
2757
 
2349
- abstract class CrossFieldManagerI<T> implements CrossFieldManager {
2758
+ class InvertNodeManagerI implements InvertNodeManager {
2350
2759
  public constructor(
2351
- protected readonly crossFieldTable: CrossFieldTable<T>,
2352
- private readonly currentFieldKey: T,
2353
- protected readonly allowInval = true,
2760
+ private readonly table: InvertTable,
2761
+ private readonly fieldId: FieldId,
2354
2762
  ) {}
2355
2763
 
2356
- public set(
2357
- target: CrossFieldTarget,
2358
- revision: RevisionTag | undefined,
2359
- id: ChangesetLocalId,
2764
+ public invertDetach(
2765
+ detachId: ChangeAtomId,
2360
2766
  count: number,
2361
- newValue: unknown,
2362
- invalidateDependents: boolean,
2767
+ nodeChange: NodeId | undefined,
2768
+ newAttachId: ChangeAtomId,
2363
2769
  ): void {
2364
- if (invalidateDependents && this.allowInval) {
2365
- const lastChangedId = (id as number) + count - 1;
2366
- let firstId = id;
2367
- while (firstId <= lastChangedId) {
2368
- const dependentEntry = getFirstFromCrossFieldMap(
2369
- this.getDependents(target),
2370
- revision,
2371
- firstId,
2372
- lastChangedId - firstId + 1,
2373
- );
2374
- if (dependentEntry.value !== undefined) {
2375
- this.crossFieldTable.invalidatedFields.add(dependentEntry.value);
2376
- }
2770
+ if (nodeChange !== undefined) {
2771
+ assert(count === 1, "A node change should only affect one node");
2772
+
2773
+ const attachEntry = firstAttachIdFromDetachId(
2774
+ this.table.change.rootNodes,
2775
+ detachId,
2776
+ count,
2777
+ );
2377
2778
 
2378
- firstId = brand(firstId + dependentEntry.length);
2779
+ const attachFieldEntry = this.table.change.crossFieldKeys.getFirst(
2780
+ { target: NodeMoveType.Attach, ...attachEntry.value },
2781
+ count,
2782
+ );
2783
+
2784
+ if (attachFieldEntry.value === undefined) {
2785
+ assignRootChange(
2786
+ this.table.invertedRoots,
2787
+ this.table.invertedNodeToParent,
2788
+ attachEntry.value,
2789
+ nodeChange,
2790
+ this.fieldId,
2791
+ this.table.change.rebaseVersion,
2792
+ );
2793
+ } else {
2794
+ setInCrossFieldMap(this.table.entries, attachEntry.value, count, nodeChange);
2795
+ this.table.invalidatedFields.add(
2796
+ fieldChangeFromId(this.table.change, attachFieldEntry.value),
2797
+ );
2379
2798
  }
2380
2799
  }
2381
- setInCrossFieldMap(this.getMap(target), revision, id, count, newValue);
2382
- }
2383
2800
 
2384
- public get(
2385
- target: CrossFieldTarget,
2386
- revision: RevisionTag | undefined,
2387
- id: ChangesetLocalId,
2388
- count: number,
2389
- addDependency: boolean,
2390
- ): RangeQueryResult<ChangeAtomId, unknown> {
2391
- if (addDependency) {
2392
- // We assume that if there is already an entry for this ID it is because
2393
- // a field handler has called compose on the same node multiple times.
2394
- // In this case we only want to update the latest version, so we overwrite the dependency.
2395
- setInCrossFieldMap(
2396
- this.getDependents(target),
2397
- revision,
2398
- id,
2801
+ if (!areEqualChangeAtomIds(detachId, newAttachId)) {
2802
+ for (const entry of doesChangeAttachNodes(
2803
+ this.table.change.crossFieldKeys,
2804
+ detachId,
2399
2805
  count,
2400
- this.currentFieldKey,
2401
- );
2806
+ )) {
2807
+ if (!entry.value) {
2808
+ this.table.attachToDetachId.set(newAttachId, count, detachId);
2809
+ this.table.invertedRoots.detachLocations.set(detachId, count, this.fieldId);
2810
+ }
2811
+ }
2402
2812
  }
2403
- return getFirstFromCrossFieldMap(this.getMap(target), revision, id, count);
2404
2813
  }
2405
2814
 
2406
- public abstract onMoveIn(id: NodeId): void;
2407
-
2408
- public abstract moveKey(
2409
- target: CrossFieldTarget,
2410
- revision: RevisionTag | undefined,
2411
- id: ChangesetLocalId,
2815
+ public invertAttach(
2816
+ attachId: ChangeAtomId,
2412
2817
  count: number,
2413
- ): void;
2818
+ ): RangeQueryResult<DetachedNodeEntry> {
2819
+ let countToProcess = count;
2414
2820
 
2415
- private getMap(target: CrossFieldTarget): CrossFieldMap<unknown> {
2416
- return target === CrossFieldTarget.Source
2417
- ? this.crossFieldTable.srcTable
2418
- : this.crossFieldTable.dstTable;
2419
- }
2821
+ const detachIdEntry = firstDetachIdFromAttachId(
2822
+ this.table.change.rootNodes,
2823
+ attachId,
2824
+ countToProcess,
2825
+ );
2420
2826
 
2421
- private getDependents(target: CrossFieldTarget): CrossFieldMap<T> {
2422
- return target === CrossFieldTarget.Source
2423
- ? this.crossFieldTable.srcDependents
2424
- : this.crossFieldTable.dstDependents;
2425
- }
2426
- }
2827
+ countToProcess = detachIdEntry.length;
2427
2828
 
2428
- class InvertManager extends CrossFieldManagerI<FieldChange> {
2429
- public constructor(
2430
- table: InvertTable,
2431
- field: FieldChange,
2432
- private readonly fieldId: FieldId,
2433
- allowInval = true,
2434
- ) {
2435
- super(table, field, allowInval);
2436
- }
2829
+ const detachEntry = getFirstFieldForCrossFieldKey(
2830
+ this.table.change,
2831
+ { target: NodeMoveType.Detach, ...detachIdEntry.value },
2832
+ countToProcess,
2833
+ );
2834
+ countToProcess = detachEntry.length;
2835
+
2836
+ let result: RangeQueryResult<DetachedNodeEntry>;
2837
+ if (detachEntry.value === undefined) {
2838
+ // This node is detached in the input context of the original change.
2839
+ const nodeIdEntry = rangeQueryChangeAtomIdMap(
2840
+ this.table.change.rootNodes.nodeChanges,
2841
+ detachIdEntry.value,
2842
+ countToProcess,
2843
+ );
2844
+ countToProcess = nodeIdEntry.length;
2437
2845
 
2438
- public override onMoveIn(id: ChangeAtomId): void {
2439
- setInChangeAtomIdMap(this.table.invertedNodeToParent, id, this.fieldId);
2440
- }
2846
+ const detachLocationEntry = this.table.change.rootNodes.detachLocations.getFirst(
2847
+ detachIdEntry.value,
2848
+ countToProcess,
2849
+ );
2850
+ countToProcess = detachLocationEntry.length;
2441
2851
 
2442
- public override moveKey(
2443
- target: CrossFieldTarget,
2444
- revision: RevisionTag | undefined,
2445
- id: ChangesetLocalId,
2446
- count: number,
2447
- ): void {
2448
- assert(false, 0x9c5 /* Keys should not be moved manually during invert */);
2449
- }
2852
+ if (
2853
+ this.table.isRollback &&
2854
+ detachLocationEntry.value !== undefined &&
2855
+ !areEqualFieldIds(
2856
+ normalizeFieldId(detachLocationEntry.value, this.table.change.nodeAliases),
2857
+ this.fieldId,
2858
+ )
2859
+ ) {
2860
+ // These nodes are detached in the input context of the original change,
2861
+ // and the change attaches these nodes in a different location from their detach location.
2862
+ // The rollback change should send them back to that prior detach location.
2863
+ this.table.invertedRoots.outputDetachLocations.set(
2864
+ detachIdEntry.value,
2865
+ countToProcess,
2866
+ detachLocationEntry.value,
2867
+ );
2868
+ }
2869
+
2870
+ result = {
2871
+ value: {
2872
+ nodeChange: nodeIdEntry.value,
2873
+ detachId: detachIdEntry.value,
2874
+ },
2875
+ length: countToProcess,
2876
+ };
2877
+ } else {
2878
+ const moveEntry = this.table.entries.getFirst(attachId, countToProcess);
2879
+ result = { ...moveEntry, value: { nodeChange: moveEntry.value } };
2880
+ }
2450
2881
 
2451
- private get table(): InvertTable {
2452
- return this.crossFieldTable as InvertTable;
2882
+ if (result.value?.nodeChange !== undefined) {
2883
+ setInChangeAtomIdMap(this.table.invertedNodeToParent, result.value.nodeChange, {
2884
+ field: this.fieldId,
2885
+ });
2886
+ }
2887
+ return result;
2453
2888
  }
2454
2889
  }
2455
2890
 
2456
- class RebaseManager extends CrossFieldManagerI<FieldChange> {
2891
+ class RebaseNodeManagerI implements RebaseNodeManager {
2457
2892
  public constructor(
2458
- table: RebaseTable,
2459
- currentField: FieldChange,
2893
+ private readonly table: RebaseTable,
2460
2894
  private readonly fieldId: FieldId,
2461
- allowInval = true,
2462
- ) {
2463
- super(table, currentField, allowInval);
2464
- }
2895
+ private readonly allowInval: boolean = true,
2896
+ ) {}
2465
2897
 
2466
- public override set(
2467
- target: CrossFieldTarget,
2468
- revision: RevisionTag | undefined,
2469
- id: ChangesetLocalId,
2898
+ public getNewChangesForBaseAttach(
2899
+ baseAttachId: ChangeAtomId,
2470
2900
  count: number,
2471
- newValue: unknown,
2472
- invalidateDependents: boolean,
2473
- ): void {
2474
- if (invalidateDependents && this.allowInval) {
2475
- const newFieldIds = getFieldsForCrossFieldKey(
2476
- this.table.newChange,
2477
- {
2478
- target,
2479
- revision,
2480
- localId: id,
2481
- },
2482
- count,
2483
- );
2901
+ ): RangeQueryResult<RebaseDetachedNodeEntry | undefined> {
2902
+ let countToProcess = count;
2484
2903
 
2485
- assert(
2486
- newFieldIds.length === 0,
2487
- 0x9c6 /* TODO: Modifying a cross-field key from the new changeset is currently unsupported */,
2488
- );
2904
+ const detachEntry = firstDetachIdFromAttachId(
2905
+ this.table.baseChange.rootNodes,
2906
+ baseAttachId,
2907
+ countToProcess,
2908
+ );
2489
2909
 
2490
- const baseFieldIds = getFieldsForCrossFieldKey(
2491
- this.table.baseChange,
2492
- {
2493
- target,
2494
- revision,
2495
- localId: id,
2496
- },
2497
- count,
2498
- );
2910
+ countToProcess = detachEntry.length;
2499
2911
 
2500
- assert(
2501
- baseFieldIds.length > 0,
2502
- 0x9c7 /* Cross field key not registered in base or new change */,
2503
- );
2912
+ const nodeEntry = rangeQueryChangeAtomIdMap(
2913
+ this.table.newChange.rootNodes.nodeChanges,
2914
+ detachEntry.value,
2915
+ countToProcess,
2916
+ );
2504
2917
 
2505
- for (const baseFieldId of baseFieldIds) {
2506
- this.table.affectedBaseFields.set(
2507
- [baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field],
2508
- true,
2509
- );
2510
- }
2918
+ countToProcess = nodeEntry.length;
2919
+ const newNodeId = nodeEntry.value;
2920
+
2921
+ const newRenameEntry = this.table.newChange.rootNodes.oldToNewId.getFirst(
2922
+ detachEntry.value,
2923
+ countToProcess,
2924
+ );
2925
+
2926
+ countToProcess = newRenameEntry.length;
2927
+
2928
+ let result: RangeQueryResult<DetachedNodeEntry | undefined>;
2929
+ // eslint-disable-next-line unicorn/prefer-ternary
2930
+ if (newNodeId !== undefined || newRenameEntry.value !== undefined) {
2931
+ result = {
2932
+ ...newRenameEntry,
2933
+ value: { detachId: newRenameEntry.value, nodeChange: newNodeId },
2934
+ };
2935
+ } else {
2936
+ // This handles the case where the base changeset has moved these nodes,
2937
+ // meaning they were attached in the input context of the base changeset.
2938
+ result = this.table.entries.getFirst(baseAttachId, countToProcess);
2511
2939
  }
2512
2940
 
2513
- super.set(target, revision, id, count, newValue, invalidateDependents);
2514
- }
2941
+ // TODO: Consider moving these two checks into a separate method so that this function has no side effects.
2942
+ if (result.value?.detachId !== undefined) {
2943
+ this.table.rebasedDetachLocations.set(
2944
+ result.value.detachId,
2945
+ result.length,
2946
+ this.fieldId,
2947
+ );
2948
+ }
2515
2949
 
2516
- public override onMoveIn(id: ChangeAtomId): void {
2517
- setInChangeAtomIdMap(this.table.rebasedNodeToParent, id, this.fieldId);
2950
+ if (result.value?.nodeChange !== undefined) {
2951
+ setInChangeAtomIdMap(this.table.rebasedNodeToParent, result.value.nodeChange, {
2952
+ field: this.fieldId,
2953
+ });
2954
+ }
2955
+
2956
+ return result;
2518
2957
  }
2519
2958
 
2520
- public override moveKey(
2521
- target: CrossFieldTarget,
2522
- revision: RevisionTag | undefined,
2523
- id: ChangesetLocalId,
2959
+ public rebaseOverDetach(
2960
+ baseDetachId: ChangeAtomId,
2524
2961
  count: number,
2962
+ newDetachId: ChangeAtomId | undefined,
2963
+ nodeChange: NodeId | undefined,
2964
+ cellRename?: ChangeAtomId,
2525
2965
  ): void {
2526
- this.table.rebasedCrossFieldKeys.set(
2527
- { target, revision, localId: id },
2528
- count,
2529
- this.fieldId,
2966
+ let countToProcess = count;
2967
+ const attachIdEntry = firstAttachIdFromDetachId(
2968
+ this.table.baseRoots,
2969
+ baseDetachId,
2970
+ countToProcess,
2530
2971
  );
2972
+ const baseAttachId = attachIdEntry.value;
2973
+ countToProcess = attachIdEntry.length;
2974
+
2975
+ const attachFieldEntry = getFirstFieldForCrossFieldKey(
2976
+ this.table.baseChange,
2977
+ { ...baseAttachId, target: NodeMoveType.Attach },
2978
+ countToProcess,
2979
+ );
2980
+ countToProcess = attachFieldEntry.length;
2981
+
2982
+ const detachedMoveEntry = this.table.baseChange.rootNodes.outputDetachLocations.getFirst(
2983
+ baseDetachId,
2984
+ countToProcess,
2985
+ );
2986
+ countToProcess = detachedMoveEntry.length;
2987
+
2988
+ const destinationField = attachFieldEntry.value ?? detachedMoveEntry.value;
2989
+ if (destinationField !== undefined) {
2990
+ // The base detach is part of a move (or move of detach location) in the base changeset.
2991
+ setInCrossFieldMap(this.table.entries, baseAttachId, countToProcess, {
2992
+ nodeChange,
2993
+ detachId: newDetachId,
2994
+ cellRename,
2995
+ });
2996
+
2997
+ if (nodeChange !== undefined || newDetachId !== undefined) {
2998
+ this.invalidateBaseFields([destinationField]);
2999
+ }
3000
+ }
3001
+
3002
+ if (attachFieldEntry.value === undefined) {
3003
+ // These nodes are detached in the output context of the base changeset.
3004
+ if (nodeChange !== undefined) {
3005
+ assignRootChange(
3006
+ this.table.rebasedRootNodes,
3007
+ this.table.rebasedNodeToParent,
3008
+ baseAttachId,
3009
+ nodeChange,
3010
+ this.fieldId,
3011
+ this.table.rebaseVersion,
3012
+ );
3013
+ }
3014
+
3015
+ if (newDetachId !== undefined) {
3016
+ addNodeRename(
3017
+ this.table.rebasedRootNodes,
3018
+ baseAttachId,
3019
+ newDetachId,
3020
+ countToProcess,
3021
+ this.fieldId,
3022
+ );
3023
+ }
3024
+ }
3025
+
3026
+ if (newDetachId !== undefined) {
3027
+ this.table.movedDetaches.set(newDetachId, countToProcess, true);
3028
+ }
3029
+
3030
+ if (countToProcess < count) {
3031
+ const remainingCount = count - countToProcess;
3032
+
3033
+ const nextDetachId =
3034
+ newDetachId === undefined
3035
+ ? undefined
3036
+ : offsetChangeAtomId(newDetachId, countToProcess);
3037
+
3038
+ this.rebaseOverDetach(
3039
+ offsetChangeAtomId(baseDetachId, countToProcess),
3040
+ remainingCount,
3041
+ nextDetachId,
3042
+ nodeChange,
3043
+ );
3044
+ }
3045
+ }
3046
+
3047
+ public addDetach(id: ChangeAtomId, count: number): void {
3048
+ this.table.rebasedDetachLocations.set(id, count, this.fieldId);
3049
+ }
3050
+
3051
+ public removeDetach(id: ChangeAtomId, count: number): void {
3052
+ this.table.movedDetaches.set(id, count, true);
3053
+ }
3054
+
3055
+ public doesBaseAttachNodes(
3056
+ id: ChangeAtomId,
3057
+ count: number,
3058
+ ): RangeQueryEntry<ChangeAtomId, boolean> {
3059
+ let countToProcess = count;
3060
+ const attachEntry = getFirstAttachField(
3061
+ this.table.baseChange.crossFieldKeys,
3062
+ id,
3063
+ countToProcess,
3064
+ );
3065
+
3066
+ countToProcess = attachEntry.length;
3067
+ return { start: id, value: attachEntry.value !== undefined, length: countToProcess };
3068
+ }
3069
+
3070
+ public getBaseRename(
3071
+ id: ChangeAtomId,
3072
+ count: number,
3073
+ ): RangeQueryResult<ChangeAtomId | undefined> {
3074
+ return this.table.baseChange.rootNodes.oldToNewId.getFirst(id, count);
3075
+ }
3076
+
3077
+ public getNewRenameForBaseRename(
3078
+ baseRenameTo: ChangeAtomId,
3079
+ count: number,
3080
+ ): RangeQueryResult<ChangeAtomId | undefined> {
3081
+ let countToProcess = count;
3082
+ const inputEntry = firstDetachIdFromAttachId(
3083
+ this.table.baseChange.rootNodes,
3084
+ baseRenameTo,
3085
+ countToProcess,
3086
+ );
3087
+
3088
+ const attachEntry = getFirstAttachField(
3089
+ this.table.baseChange.crossFieldKeys,
3090
+ baseRenameTo,
3091
+ countToProcess,
3092
+ );
3093
+
3094
+ countToProcess = attachEntry.length;
3095
+ if (attachEntry.value !== undefined) {
3096
+ // These nodes are attached in the output context of the base changeset.
3097
+ return { value: undefined, length: countToProcess };
3098
+ }
3099
+
3100
+ countToProcess = inputEntry.length;
3101
+ const inputId = inputEntry.value;
3102
+
3103
+ const moveEntry = this.table.entries.getFirst(inputId, countToProcess);
3104
+
3105
+ countToProcess = moveEntry.length;
3106
+ if (moveEntry.value !== undefined) {
3107
+ return { ...moveEntry, value: moveEntry.value.cellRename ?? moveEntry.value.detachId };
3108
+ }
3109
+
3110
+ return this.table.newChange.rootNodes.oldToNewId.getFirst(inputId, countToProcess);
2531
3111
  }
2532
3112
 
2533
- private get table(): RebaseTable {
2534
- return this.crossFieldTable as RebaseTable;
3113
+ private invalidateBaseFields(fields: FieldId[]): void {
3114
+ if (this.allowInval) {
3115
+ for (const fieldId of fields) {
3116
+ this.table.affectedBaseFields.set(fieldIdKeyFromFieldId(fieldId), true);
3117
+ }
3118
+ }
2535
3119
  }
2536
3120
  }
2537
3121
 
2538
- // TODO: Deduplicate this with RebaseTable
2539
- class ComposeManager extends CrossFieldManagerI<FieldChange> {
3122
+ function assignRootChange(
3123
+ table: RootNodeTable,
3124
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>,
3125
+ detachId: ChangeAtomId,
3126
+ nodeId: NodeId,
3127
+ detachLocation: FieldId | undefined,
3128
+ rebaseVersion: RebaseVersion,
3129
+ ): void {
3130
+ assert(
3131
+ rebaseVersion >= 2 || detachLocation !== undefined,
3132
+ "All root changes need a detach location to support compatibility with older client versions",
3133
+ );
3134
+
3135
+ setInChangeAtomIdMap(table.nodeChanges, detachId, nodeId);
3136
+ setInChangeAtomIdMap(nodeToParent, nodeId, { root: detachId });
3137
+
3138
+ table.detachLocations.set(detachId, 1, detachLocation);
3139
+ }
3140
+
3141
+ class ComposeNodeManagerI implements ComposeNodeManager {
2540
3142
  public constructor(
2541
- table: ComposeTable,
2542
- currentField: FieldChange,
3143
+ private readonly table: ComposeTable,
2543
3144
  private readonly fieldId: FieldId,
2544
- allowInval = true,
2545
- ) {
2546
- super(table, currentField, allowInval);
3145
+ private readonly allowInval: boolean = true,
3146
+ ) {}
3147
+
3148
+ public getNewChangesForBaseDetach(
3149
+ baseDetachId: ChangeAtomId,
3150
+ count: number,
3151
+ ): RangeQueryResult<DetachedNodeEntry | undefined> {
3152
+ let countToProcess = count;
3153
+
3154
+ const baseAttachEntry = getFirstFieldForCrossFieldKey(
3155
+ this.table.baseChange,
3156
+ { target: NodeMoveType.Attach, ...baseDetachId },
3157
+ countToProcess,
3158
+ );
3159
+
3160
+ countToProcess = baseAttachEntry.length;
3161
+
3162
+ let result: RangeQueryResult<DetachedNodeEntry | undefined>;
3163
+ if (baseAttachEntry.value === undefined) {
3164
+ // The detached nodes are still detached in the new change's input context.
3165
+ const rootEntry = rangeQueryChangeAtomIdMap(
3166
+ this.table.newChange.rootNodes.nodeChanges,
3167
+ baseDetachId,
3168
+ countToProcess,
3169
+ );
3170
+
3171
+ countToProcess = rootEntry.length;
3172
+
3173
+ const newRenameEntry = this.table.newChange.rootNodes.oldToNewId.getFirst(
3174
+ baseDetachId,
3175
+ countToProcess,
3176
+ );
3177
+
3178
+ countToProcess = newRenameEntry.length;
3179
+
3180
+ result = {
3181
+ value: { nodeChange: rootEntry.value, detachId: newRenameEntry.value },
3182
+ length: countToProcess,
3183
+ };
3184
+ } else {
3185
+ // The base detach was part of a move.
3186
+ // We check if we've previously seen a node change at the move destination.
3187
+ const entry = this.table.entries.getFirst(baseDetachId, countToProcess);
3188
+ result = { value: entry.value, length: entry.length };
3189
+ }
3190
+
3191
+ // TODO: Consider moving this to a separate method so that this method can be side-effect free.
3192
+ if (result.value?.nodeChange !== undefined) {
3193
+ setInChangeAtomIdMap(this.table.movedNodeToParent, result.value.nodeChange, {
3194
+ field: this.fieldId,
3195
+ });
3196
+ }
3197
+
3198
+ return result;
2547
3199
  }
2548
3200
 
2549
- public override set(
2550
- target: CrossFieldTarget,
2551
- revision: RevisionTag | undefined,
2552
- id: ChangesetLocalId,
3201
+ public composeAttachDetach(
3202
+ baseAttachId: ChangeAtomId,
3203
+ newDetachId: ChangeAtomId,
2553
3204
  count: number,
2554
- newValue: unknown,
2555
- invalidateDependents: boolean,
2556
3205
  ): void {
2557
- if (invalidateDependents && this.allowInval) {
2558
- const newFieldIds = getFieldsForCrossFieldKey(
2559
- this.table.newChange,
2560
- {
2561
- target,
2562
- revision,
2563
- localId: id,
2564
- },
2565
- count,
3206
+ let countToProcess = count;
3207
+
3208
+ const newAttachEntry = getFirstAttachField(
3209
+ this.table.newChange.crossFieldKeys,
3210
+ newDetachId,
3211
+ countToProcess,
3212
+ );
3213
+
3214
+ countToProcess = newAttachEntry.length;
3215
+
3216
+ // Both changes can have the same ID if they came from inverse changesets.
3217
+ // If the new detach is part of a move,
3218
+ // then both input changesets contain the attach cross-field key for this ID.
3219
+ // The new attach may still exist in the composed changeset so we do not remove it here.
3220
+ // The new attach will typically cancel with a base detach,
3221
+ // in which case the cross-field key will be removed in `composeDetachAttach`.
3222
+ const hasNewAttachWithBaseAttachId =
3223
+ areEqualChangeAtomIds(baseAttachId, newDetachId) && newAttachEntry.value !== undefined;
3224
+
3225
+ if (!hasNewAttachWithBaseAttachId) {
3226
+ this.table.removedCrossFieldKeys.set(
3227
+ { ...baseAttachId, target: NodeMoveType.Attach },
3228
+ countToProcess,
3229
+ true,
2566
3230
  );
3231
+ }
2567
3232
 
2568
- if (newFieldIds.length > 0) {
2569
- for (const newFieldId of newFieldIds) {
2570
- this.table.pendingCompositions.affectedNewFields.set(
2571
- [newFieldId.nodeId?.revision, newFieldId.nodeId?.localId, newFieldId.field],
2572
- true,
2573
- );
2574
- }
2575
- } else {
2576
- const baseFieldIds = getFieldsForCrossFieldKey(
2577
- this.table.baseChange,
2578
- {
2579
- target,
2580
- revision,
2581
- localId: id,
2582
- },
2583
- count,
3233
+ const baseDetachEntry = getFirstDetachField(
3234
+ this.table.baseChange.crossFieldKeys,
3235
+ baseAttachId,
3236
+ countToProcess,
3237
+ );
3238
+
3239
+ countToProcess = baseDetachEntry.length;
3240
+
3241
+ const baseRootIdEntry = firstDetachIdFromAttachId(
3242
+ this.table.baseChange.rootNodes,
3243
+ baseAttachId,
3244
+ countToProcess,
3245
+ );
3246
+ countToProcess = baseRootIdEntry.length;
3247
+
3248
+ const baseDetachId = baseRootIdEntry.value;
3249
+
3250
+ if (baseDetachEntry.value === undefined) {
3251
+ const baseDetachLocationEntry = this.table.baseChange.rootNodes.detachLocations.getFirst(
3252
+ baseDetachId,
3253
+ countToProcess,
3254
+ );
3255
+ countToProcess = baseDetachLocationEntry.length;
3256
+
3257
+ // These nodes were detached in the base change's input context,
3258
+ // so the net effect of the two changes is a rename.
3259
+ appendNodeRename(
3260
+ this.table.composedRootNodes,
3261
+ baseAttachId,
3262
+ newDetachId,
3263
+ baseDetachEntry.length,
3264
+ this.table.baseChange.rootNodes,
3265
+ baseDetachLocationEntry.value ?? this.fieldId,
3266
+ );
3267
+
3268
+ this.table.removedCrossFieldKeys.set(
3269
+ { ...newDetachId, target: NodeMoveType.Detach },
3270
+ countToProcess,
3271
+ true,
3272
+ );
3273
+ } else {
3274
+ // The base change moves these nodes.
3275
+ const prevEntry =
3276
+ this.table.entries.getFirst(baseAttachId, baseDetachEntry.length).value ?? {};
3277
+
3278
+ this.table.entries.set(baseAttachId, baseDetachEntry.length, {
3279
+ ...prevEntry,
3280
+ detachId: newDetachId,
3281
+ });
3282
+
3283
+ // The new detach will replace the base detach, so we remove the key for the base detach, unless they have the same ID.
3284
+ if (!areEqualChangeAtomIds(baseAttachId, newDetachId)) {
3285
+ this.table.removedCrossFieldKeys.set(
3286
+ { ...baseAttachId, target: NodeMoveType.Detach },
3287
+ countToProcess,
3288
+ true,
2584
3289
  );
3290
+ }
2585
3291
 
2586
- assert(
2587
- baseFieldIds.length > 0,
2588
- 0x9c8 /* Cross field key not registered in base or new change */,
3292
+ this.table.movedCrossFieldKeys.set(
3293
+ { ...newDetachId, target: NodeMoveType.Detach },
3294
+ countToProcess,
3295
+ baseDetachEntry.value,
3296
+ );
3297
+
3298
+ this.invalidateBaseFields([baseDetachEntry.value]);
3299
+ }
3300
+
3301
+ if (newAttachEntry.value === undefined) {
3302
+ const newOutputDetachLocationEntry =
3303
+ this.table.newChange.rootNodes.outputDetachLocations.getFirst(
3304
+ newDetachId,
3305
+ countToProcess,
2589
3306
  );
2590
3307
 
2591
- for (const baseFieldId of baseFieldIds) {
2592
- this.table.pendingCompositions.affectedBaseFields.set(
2593
- [baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field],
2594
- true,
2595
- );
2596
- }
2597
- }
3308
+ countToProcess = newOutputDetachLocationEntry.length;
3309
+
3310
+ this.table.composedRootNodes.outputDetachLocations.set(
3311
+ newDetachId,
3312
+ countToProcess,
3313
+ newOutputDetachLocationEntry.value ?? this.fieldId,
3314
+ );
2598
3315
  }
2599
3316
 
2600
- super.set(target, revision, id, count, newValue, invalidateDependents);
3317
+ if (countToProcess < count) {
3318
+ const remainingCount = count - countToProcess;
3319
+ this.composeAttachDetach(
3320
+ offsetChangeAtomId(baseAttachId, countToProcess),
3321
+ offsetChangeAtomId(newDetachId, countToProcess),
3322
+ remainingCount,
3323
+ );
3324
+ }
2601
3325
  }
2602
3326
 
2603
- public override onMoveIn(id: ChangeAtomId): void {
2604
- setInChangeAtomIdMap(this.table.composedNodeToParent, id, this.fieldId);
3327
+ public sendNewChangesToBaseSourceLocation(
3328
+ baseAttachId: ChangeAtomId,
3329
+ newChanges: NodeId,
3330
+ ): void {
3331
+ const { value: baseDetachId } = firstDetachIdFromAttachId(
3332
+ this.table.baseChange.rootNodes,
3333
+ baseAttachId,
3334
+ 1,
3335
+ );
3336
+
3337
+ const detachFields = getFieldsForCrossFieldKey(
3338
+ this.table.baseChange,
3339
+ {
3340
+ ...baseDetachId,
3341
+ target: NodeMoveType.Detach,
3342
+ },
3343
+ 1,
3344
+ );
3345
+
3346
+ if (detachFields.length > 0) {
3347
+ // The base attach is part of a move in the base changeset.
3348
+ const prevEntry = this.table.entries.getFirst(baseDetachId, 1).value ?? {};
3349
+ this.table.entries.set(baseDetachId, 1, { ...prevEntry, nodeChange: newChanges });
3350
+
3351
+ if (newChanges !== undefined) {
3352
+ this.invalidateBaseFields(detachFields);
3353
+ }
3354
+ } else {
3355
+ const baseNodeId = getFromChangeAtomIdMap(
3356
+ this.table.baseChange.rootNodes.nodeChanges,
3357
+ baseDetachId,
3358
+ );
3359
+
3360
+ if (baseNodeId === undefined) {
3361
+ assignRootChange(
3362
+ this.table.composedRootNodes,
3363
+ this.table.movedNodeToParent,
3364
+ baseDetachId,
3365
+ newChanges,
3366
+ this.fieldId,
3367
+ this.table.rebaseVersion,
3368
+ );
3369
+ } else {
3370
+ addNodesToCompose(this.table, baseNodeId, newChanges);
3371
+ }
3372
+ }
2605
3373
  }
2606
3374
 
2607
- public override moveKey(
2608
- target: CrossFieldTarget,
2609
- revision: RevisionTag | undefined,
2610
- id: ChangesetLocalId,
3375
+ public composeDetachAttach(
3376
+ baseDetachId: ChangeAtomId,
3377
+ newAttachId: ChangeAtomId,
2611
3378
  count: number,
3379
+ composeToPin: boolean,
2612
3380
  ): void {
2613
- throw new Error("Moving cross-field keys during compose is currently unsupported");
3381
+ if (composeToPin) {
3382
+ this.table.movedCrossFieldKeys.set(
3383
+ { target: NodeMoveType.Detach, ...newAttachId },
3384
+ count,
3385
+ this.fieldId,
3386
+ );
3387
+
3388
+ if (!areEqualChangeAtomIds(baseDetachId, newAttachId)) {
3389
+ // The pin will have `newAttachId` as both its detach and attach ID.
3390
+ // So we remove `baseDetachId` unless that is equal to the pin's detach ID.
3391
+ this.table.removedCrossFieldKeys.set(
3392
+ { target: NodeMoveType.Detach, ...baseDetachId },
3393
+ count,
3394
+ true,
3395
+ );
3396
+ }
3397
+
3398
+ // Note that while change2 should already have this key, change1 may have a rollback for the same ID in a different location.
3399
+ // In that case, change1's attach should be canceled out by a detach from change2.
3400
+ // Here we make sure that the composed change has the correct location (this field) for the attach ID.
3401
+ this.table.movedCrossFieldKeys.set(
3402
+ { target: NodeMoveType.Attach, ...newAttachId },
3403
+ count,
3404
+ this.fieldId,
3405
+ );
3406
+ } else {
3407
+ this.table.removedCrossFieldKeys.set(
3408
+ { target: NodeMoveType.Detach, ...baseDetachId },
3409
+ count,
3410
+ true,
3411
+ );
3412
+
3413
+ this.table.removedCrossFieldKeys.set(
3414
+ { target: NodeMoveType.Attach, ...newAttachId },
3415
+ count,
3416
+ true,
3417
+ );
3418
+ }
2614
3419
  }
2615
3420
 
2616
- private get table(): ComposeTable {
2617
- return this.crossFieldTable as ComposeTable;
3421
+ private invalidateBaseFields(fields: FieldId[]): void {
3422
+ if (this.allowInval) {
3423
+ for (const fieldId of fields) {
3424
+ this.table.pendingCompositions.affectedBaseFields.set(
3425
+ fieldIdKeyFromFieldId(fieldId),
3426
+ true,
3427
+ );
3428
+ }
3429
+ }
2618
3430
  }
2619
3431
  }
2620
3432
 
2621
3433
  function makeModularChangeset(props?: {
3434
+ rebaseVersion: RebaseVersion;
2622
3435
  fieldChanges?: FieldChangeMap;
2623
3436
  nodeChanges?: ChangeAtomIdBTree<NodeChangeset>;
2624
- nodeToParent?: ChangeAtomIdBTree<FieldId>;
3437
+ rootNodes?: RootNodeTable;
3438
+ nodeToParent?: ChangeAtomIdBTree<NodeLocation>;
2625
3439
  nodeAliases?: ChangeAtomIdBTree<NodeId>;
2626
3440
  crossFieldKeys?: CrossFieldKeyTable;
2627
3441
  maxId: number;
@@ -2634,13 +3448,15 @@ function makeModularChangeset(props?: {
2634
3448
  destroys?: ChangeAtomIdBTree<number>;
2635
3449
  refreshers?: ChangeAtomIdBTree<TreeChunk>;
2636
3450
  }): ModularChangeset {
2637
- const p = props ?? { maxId: -1 };
3451
+ const p = props ?? { maxId: -1, rebaseVersion: 1 };
2638
3452
  const changeset: Mutable<ModularChangeset> = {
3453
+ rebaseVersion: p.rebaseVersion,
2639
3454
  fieldChanges: p.fieldChanges ?? new Map<FieldKey, FieldChange>(),
2640
3455
  nodeChanges: p.nodeChanges ?? newChangeAtomIdBTree(),
3456
+ rootNodes: p.rootNodes ?? newRootTable(),
2641
3457
  nodeToParent: p.nodeToParent ?? newChangeAtomIdBTree(),
2642
3458
  nodeAliases: p.nodeAliases ?? newChangeAtomIdBTree(),
2643
- crossFieldKeys: p.crossFieldKeys ?? newCrossFieldKeyTable(),
3459
+ crossFieldKeys: p.crossFieldKeys ?? newCrossFieldRangeTable(),
2644
3460
  };
2645
3461
 
2646
3462
  if (p.revisions !== undefined && p.revisions.length > 0) {
@@ -2673,6 +3489,7 @@ function makeModularChangeset(props?: {
2673
3489
  if (p.refreshers !== undefined && p.refreshers.size > 0) {
2674
3490
  changeset.refreshers = p.refreshers;
2675
3491
  }
3492
+
2676
3493
  return changeset;
2677
3494
  }
2678
3495
 
@@ -2680,16 +3497,26 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2680
3497
  private transactionDepth: number = 0;
2681
3498
  private idAllocator: IdAllocator;
2682
3499
  private readonly codecOptions: CodecWriteOptions;
3500
+ public readonly rebaseVersion: RebaseVersion;
2683
3501
 
2684
3502
  public constructor(
2685
3503
  family: ChangeFamily<ChangeFamilyEditor, ModularChangeset>,
2686
3504
  private readonly fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
2687
3505
  changeReceiver: (change: TaggedChange<ModularChangeset>) => void,
2688
3506
  codecOptions: CodecWriteOptions,
3507
+ editorOptions?: ModularEditorOptions,
2689
3508
  ) {
2690
3509
  super(family, changeReceiver);
2691
3510
  this.idAllocator = idAllocatorFromMaxId();
2692
3511
  this.codecOptions = codecOptions;
3512
+ // TODO: make this dependent on the CodecWriteOptions once there is an FF version that supports RebaseVersion 2
3513
+ this.rebaseVersion =
3514
+ editorOptions?.rebaseVersionOverride ??
3515
+ (editorOptions?.enableDetachedRootEditing === true ? 2 : 1);
3516
+ }
3517
+
3518
+ public isInTransaction(): boolean {
3519
+ return this.transactionDepth > 0;
2693
3520
  }
2694
3521
 
2695
3522
  public override enterTransaction(): void {
@@ -2746,7 +3573,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2746
3573
  * @param revision - the revision of the change
2747
3574
  */
2748
3575
  public submitChange(
2749
- field: FieldUpPath,
3576
+ field: NormalizedFieldUpPath,
2750
3577
  fieldKind: FieldKindIdentifier,
2751
3578
  change: FieldChangeset,
2752
3579
  revision: RevisionTag,
@@ -2756,11 +3583,13 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2756
3583
  );
2757
3584
 
2758
3585
  const modularChange = buildModularChangesetFromField({
3586
+ rebaseVersion: this.rebaseVersion,
2759
3587
  path: field,
2760
3588
  fieldChange: { fieldKind, change },
2761
3589
  nodeChanges: newChangeAtomIdBTree(),
2762
3590
  nodeToParent: newChangeAtomIdBTree(),
2763
- crossFieldKeys: newCrossFieldKeyTable(),
3591
+ crossFieldKeys: newCrossFieldRangeTable(),
3592
+ rootNodes: newRootTable(),
2764
3593
  idAllocator: this.idAllocator,
2765
3594
  localCrossFieldKeys,
2766
3595
  revision,
@@ -2780,11 +3609,14 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2780
3609
  return makeAnonChange(
2781
3610
  change.type === "global"
2782
3611
  ? makeModularChangeset({
3612
+ rebaseVersion: this.rebaseVersion,
2783
3613
  maxId: this.idAllocator.getMaxId(),
2784
3614
  builds: change.builds,
3615
+ rootNodes: renameTableFromRenameDescriptions(change.renames ?? []),
2785
3616
  revisions: [{ revision: change.revision }],
2786
3617
  })
2787
3618
  : buildModularChangesetFromField({
3619
+ rebaseVersion: this.rebaseVersion,
2788
3620
  path: change.field,
2789
3621
  fieldChange: {
2790
3622
  fieldKind: change.fieldKind,
@@ -2792,7 +3624,8 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2792
3624
  },
2793
3625
  nodeChanges: newChangeAtomIdBTree(),
2794
3626
  nodeToParent: newChangeAtomIdBTree(),
2795
- crossFieldKeys: newCrossFieldKeyTable(),
3627
+ crossFieldKeys: newCrossFieldRangeTable(),
3628
+ rootNodes: newRootTable(),
2796
3629
  idAllocator: this.idAllocator,
2797
3630
  localCrossFieldKeys: getChangeHandler(
2798
3631
  this.fieldKinds,
@@ -2819,7 +3652,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2819
3652
  return brand(this.idAllocator.allocate(count));
2820
3653
  }
2821
3654
 
2822
- public addNodeExistsConstraint(path: UpPath, revision: RevisionTag): void {
3655
+ public addNodeExistsConstraint(path: NormalizedUpPath, revision: RevisionTag): void {
2823
3656
  const nodeChange: NodeChangeset = {
2824
3657
  nodeExistsConstraint: { violated: false },
2825
3658
  };
@@ -2827,11 +3660,13 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2827
3660
  this.applyChange(
2828
3661
  tagChange(
2829
3662
  buildModularChangesetFromNode({
3663
+ rebaseVersion: this.rebaseVersion,
2830
3664
  path,
2831
3665
  nodeChange,
2832
3666
  nodeChanges: newChangeAtomIdBTree(),
2833
3667
  nodeToParent: newChangeAtomIdBTree(),
2834
- crossFieldKeys: newCrossFieldKeyTable(),
3668
+ crossFieldKeys: newCrossFieldRangeTable(),
3669
+ rootNodes: newRootTable(),
2835
3670
  idAllocator: this.idAllocator,
2836
3671
  revision,
2837
3672
  }),
@@ -2840,7 +3675,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2840
3675
  );
2841
3676
  }
2842
3677
 
2843
- public addNodeExistsConstraintOnRevert(path: UpPath, revision: RevisionTag): void {
3678
+ public addNodeExistsConstraintOnRevert(path: NormalizedUpPath, revision: RevisionTag): void {
2844
3679
  const nodeChange: NodeChangeset = {
2845
3680
  nodeExistsConstraintOnRevert: { violated: false },
2846
3681
  };
@@ -2848,11 +3683,13 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2848
3683
  this.applyChange(
2849
3684
  tagChange(
2850
3685
  buildModularChangesetFromNode({
3686
+ rebaseVersion: this.rebaseVersion,
2851
3687
  path,
2852
3688
  nodeChange,
2853
3689
  nodeChanges: newChangeAtomIdBTree(),
2854
3690
  nodeToParent: newChangeAtomIdBTree(),
2855
- crossFieldKeys: newCrossFieldKeyTable(),
3691
+ crossFieldKeys: newCrossFieldRangeTable(),
3692
+ rootNodes: newRootTable(),
2856
3693
  idAllocator: this.idAllocator,
2857
3694
  revision,
2858
3695
  }),
@@ -2869,6 +3706,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2869
3706
  }
2870
3707
 
2871
3708
  const changeset = makeModularChangeset({
3709
+ rebaseVersion: this.rebaseVersion,
2872
3710
  maxId: -1,
2873
3711
  noChangeConstraint: { violated: false },
2874
3712
  });
@@ -2884,6 +3722,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2884
3722
  }
2885
3723
 
2886
3724
  const changeset = makeModularChangeset({
3725
+ rebaseVersion: this.rebaseVersion,
2887
3726
  maxId: -1,
2888
3727
  noChangeConstraintOnRevert: { violated: false },
2889
3728
  });
@@ -2892,23 +3731,27 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
2892
3731
  }
2893
3732
  }
2894
3733
 
2895
- function buildModularChangesetFromField(props: {
2896
- path: FieldUpPath;
3734
+ export function buildModularChangesetFromField(props: {
3735
+ rebaseVersion: RebaseVersion;
3736
+ path: NormalizedFieldUpPath;
2897
3737
  fieldChange: FieldChange;
2898
3738
  nodeChanges: ChangeAtomIdBTree<NodeChangeset>;
2899
- nodeToParent: ChangeAtomIdBTree<FieldId>;
3739
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>;
2900
3740
  crossFieldKeys: CrossFieldKeyTable;
3741
+ rootNodes: RootNodeTable;
2901
3742
  localCrossFieldKeys?: CrossFieldKeyRange[];
2902
3743
  revision: RevisionTag;
2903
3744
  idAllocator?: IdAllocator;
2904
3745
  childId?: NodeId;
2905
3746
  }): ModularChangeset {
2906
3747
  const {
3748
+ rebaseVersion,
2907
3749
  path,
2908
3750
  fieldChange,
2909
3751
  nodeChanges,
2910
3752
  nodeToParent,
2911
3753
  crossFieldKeys,
3754
+ rootNodes,
2912
3755
  idAllocator = idAllocatorFromMaxId(),
2913
3756
  localCrossFieldKeys = [],
2914
3757
  childId,
@@ -2917,22 +3760,27 @@ function buildModularChangesetFromField(props: {
2917
3760
  const fieldChanges: FieldChangeMap = new Map([[path.field, fieldChange]]);
2918
3761
 
2919
3762
  if (path.parent === undefined) {
3763
+ const field = { nodeId: undefined, field: path.field };
2920
3764
  for (const { key, count } of localCrossFieldKeys) {
2921
- crossFieldKeys.set(key, count, { nodeId: undefined, field: path.field });
3765
+ crossFieldKeys.set(key, count, field);
2922
3766
  }
2923
3767
 
2924
3768
  if (childId !== undefined) {
2925
3769
  setInChangeAtomIdMap(nodeToParent, childId, {
2926
- nodeId: undefined,
2927
- field: path.field,
3770
+ field: {
3771
+ nodeId: undefined,
3772
+ field: path.field,
3773
+ },
2928
3774
  });
2929
3775
  }
2930
3776
 
2931
3777
  return makeModularChangeset({
3778
+ rebaseVersion,
2932
3779
  fieldChanges,
2933
3780
  nodeChanges,
2934
3781
  nodeToParent,
2935
3782
  crossFieldKeys,
3783
+ rootNodes,
2936
3784
  maxId: idAllocator.getMaxId(),
2937
3785
  revisions: [{ revision }],
2938
3786
  });
@@ -2943,6 +3791,7 @@ function buildModularChangesetFromField(props: {
2943
3791
  };
2944
3792
 
2945
3793
  const parentId: NodeId = { localId: brand(idAllocator.allocate()), revision };
3794
+ const fieldId = { nodeId: parentId, field: path.field };
2946
3795
 
2947
3796
  for (const { key, count } of localCrossFieldKeys) {
2948
3797
  crossFieldKeys.set(key, count, { nodeId: parentId, field: path.field });
@@ -2950,17 +3799,18 @@ function buildModularChangesetFromField(props: {
2950
3799
 
2951
3800
  if (childId !== undefined) {
2952
3801
  setInChangeAtomIdMap(nodeToParent, childId, {
2953
- nodeId: parentId,
2954
- field: path.field,
3802
+ field: fieldId,
2955
3803
  });
2956
3804
  }
2957
3805
 
2958
3806
  return buildModularChangesetFromNode({
3807
+ rebaseVersion,
2959
3808
  path: path.parent,
2960
3809
  nodeChange: nodeChangeset,
2961
3810
  nodeChanges,
2962
3811
  nodeToParent,
2963
3812
  crossFieldKeys,
3813
+ rootNodes,
2964
3814
  idAllocator,
2965
3815
  revision,
2966
3816
  nodeId: parentId,
@@ -2968,11 +3818,13 @@ function buildModularChangesetFromField(props: {
2968
3818
  }
2969
3819
 
2970
3820
  function buildModularChangesetFromNode(props: {
2971
- path: UpPath;
3821
+ rebaseVersion: RebaseVersion;
3822
+ path: NormalizedUpPath;
2972
3823
  nodeChange: NodeChangeset;
2973
3824
  nodeChanges: ChangeAtomIdBTree<NodeChangeset>;
2974
- nodeToParent: ChangeAtomIdBTree<FieldId>;
3825
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>;
2975
3826
  crossFieldKeys: CrossFieldKeyTable;
3827
+ rootNodes: RootNodeTable;
2976
3828
  idAllocator: IdAllocator;
2977
3829
  revision: RevisionTag;
2978
3830
  nodeId?: NodeId;
@@ -2986,27 +3838,44 @@ function buildModularChangesetFromNode(props: {
2986
3838
  nodeId = { localId: brand(idAllocator.allocate()), revision },
2987
3839
  } = props;
2988
3840
  setInChangeAtomIdMap(nodeChanges, nodeId, nodeChange);
2989
- const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChanges([
2990
- [path.parentIndex, nodeId],
2991
- ]);
2992
3841
 
2993
- const fieldChange: FieldChange = {
2994
- fieldKind: genericFieldKind.identifier,
2995
- change: fieldChangeset,
2996
- };
3842
+ if (isDetachedUpPathRoot(path)) {
3843
+ props.rootNodes.nodeChanges.set(
3844
+ [path.detachedNodeId.major, brand(path.detachedNodeId.minor)],
3845
+ nodeId,
3846
+ );
3847
+ return makeModularChangeset({
3848
+ rebaseVersion: props.rebaseVersion,
3849
+ rootNodes: props.rootNodes,
3850
+ nodeChanges: props.nodeChanges,
3851
+ nodeToParent: props.nodeToParent,
3852
+ crossFieldKeys: props.crossFieldKeys,
3853
+ maxId: props.idAllocator.getMaxId(),
3854
+ revisions: [{ revision: props.revision }],
3855
+ });
3856
+ } else {
3857
+ const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChanges([
3858
+ [path.parentIndex, nodeId],
3859
+ ]);
2997
3860
 
2998
- return buildModularChangesetFromField({
2999
- ...props,
3000
- path: { parent: path.parent, field: path.parentField },
3001
- fieldChange,
3002
- localCrossFieldKeys: [],
3003
- childId: nodeId,
3004
- });
3861
+ const fieldChange: FieldChange = {
3862
+ fieldKind: genericFieldKind.identifier,
3863
+ change: fieldChangeset,
3864
+ };
3865
+
3866
+ return buildModularChangesetFromField({
3867
+ ...props,
3868
+ path: { parent: path.parent, field: path.parentField },
3869
+ fieldChange,
3870
+ localCrossFieldKeys: [],
3871
+ childId: nodeId,
3872
+ });
3873
+ }
3005
3874
  }
3006
3875
 
3007
3876
  export interface FieldEditDescription {
3008
3877
  type: "field";
3009
- field: FieldUpPath;
3878
+ field: NormalizedFieldUpPath;
3010
3879
  fieldKind: FieldKindIdentifier;
3011
3880
  change: FieldChangeset;
3012
3881
  revision: RevisionTag;
@@ -3016,6 +3885,23 @@ export interface GlobalEditDescription {
3016
3885
  type: "global";
3017
3886
  revision: RevisionTag;
3018
3887
  builds?: ChangeAtomIdBTree<TreeChunk>;
3888
+ renames?: RenameDescription[];
3889
+ }
3890
+
3891
+ export interface RenameDescription {
3892
+ count: number;
3893
+ oldId: ChangeAtomId;
3894
+ newId: ChangeAtomId;
3895
+ detachLocation: FieldId | undefined;
3896
+ }
3897
+
3898
+ function renameTableFromRenameDescriptions(renames: RenameDescription[]): RootNodeTable {
3899
+ const table = newRootTable();
3900
+ for (const rename of renames) {
3901
+ addNodeRename(table, rename.oldId, rename.newId, rename.count, rename.detachLocation);
3902
+ }
3903
+
3904
+ return table;
3019
3905
  }
3020
3906
 
3021
3907
  export type EditDescription = FieldEditDescription | GlobalEditDescription;
@@ -3039,20 +3925,6 @@ function getRevInfoFromTaggedChanges(changes: TaggedChange<ModularChangeset>[]):
3039
3925
  }
3040
3926
  }
3041
3927
 
3042
- const rolledBackRevisions: RevisionTag[] = [];
3043
- for (const info of revInfos) {
3044
- if (info.rollbackOf !== undefined) {
3045
- rolledBackRevisions.push(info.rollbackOf);
3046
- }
3047
- }
3048
-
3049
- rolledBackRevisions.reverse();
3050
- for (const revision of rolledBackRevisions) {
3051
- if (!revisions.has(revision)) {
3052
- revInfos.push({ revision });
3053
- }
3054
- }
3055
-
3056
3928
  return { maxId: brand(maxId), revInfos };
3057
3929
  }
3058
3930
 
@@ -3072,25 +3944,28 @@ function revisionInfoFromTaggedChange(
3072
3944
  return revInfos;
3073
3945
  }
3074
3946
 
3075
- function fieldChangeFromId(
3076
- fields: FieldChangeMap,
3077
- nodes: ChangeAtomIdBTree<NodeChangeset>,
3078
- id: FieldId,
3079
- ): FieldChange {
3080
- const fieldMap = fieldMapFromNodeId(fields, nodes, id.nodeId);
3947
+ function fieldChangeFromId(change: ModularChangeset, id: FieldId): FieldChange {
3948
+ const fieldId = normalizeFieldId(id, change.nodeAliases);
3949
+ const fieldMap = fieldMapFromNodeId(
3950
+ change.fieldChanges,
3951
+ change.nodeChanges,
3952
+ change.nodeAliases,
3953
+ fieldId.nodeId,
3954
+ );
3081
3955
  return fieldMap.get(id.field) ?? fail(0xb25 /* No field exists for the given ID */);
3082
3956
  }
3083
3957
 
3084
3958
  function fieldMapFromNodeId(
3085
3959
  rootFieldMap: FieldChangeMap,
3086
3960
  nodes: ChangeAtomIdBTree<NodeChangeset>,
3961
+ aliases: ChangeAtomIdBTree<NodeId>,
3087
3962
  nodeId: NodeId | undefined,
3088
3963
  ): FieldChangeMap {
3089
3964
  if (nodeId === undefined) {
3090
3965
  return rootFieldMap;
3091
3966
  }
3092
3967
 
3093
- const node = nodeChangeFromId(nodes, nodeId);
3968
+ const node = nodeChangeFromId(nodes, aliases, nodeId);
3094
3969
  assert(node.fieldChanges !== undefined, 0x9c9 /* Expected node to have field changes */);
3095
3970
  return node.fieldChanges;
3096
3971
  }
@@ -3107,8 +3982,13 @@ function rebasedNodeIdFromBaseNodeId(table: RebaseTable, baseId: NodeId): NodeId
3107
3982
  return getFromChangeAtomIdMap(table.baseToRebasedNodeId, baseId) ?? baseId;
3108
3983
  }
3109
3984
 
3110
- function nodeChangeFromId(nodes: ChangeAtomIdBTree<NodeChangeset>, id: NodeId): NodeChangeset {
3111
- const node = getFromChangeAtomIdMap(nodes, id);
3985
+ function nodeChangeFromId(
3986
+ nodes: ChangeAtomIdBTree<NodeChangeset>,
3987
+ aliases: ChangeAtomIdBTree<NodeId>,
3988
+ id: NodeId,
3989
+ ): NodeChangeset {
3990
+ const normalizedId = normalizeNodeId(id, aliases);
3991
+ const node = getFromChangeAtomIdMap(nodes, normalizedId);
3112
3992
  assert(node !== undefined, 0x9ca /* Unknown node ID */);
3113
3993
  return node;
3114
3994
  }
@@ -3118,6 +3998,10 @@ function fieldIdFromFieldIdKey([revision, localId, field]: FieldIdKey): FieldId
3118
3998
  return { nodeId, field };
3119
3999
  }
3120
4000
 
4001
+ function fieldIdKeyFromFieldId(fieldId: FieldId): FieldIdKey {
4002
+ return [fieldId.nodeId?.revision, fieldId.nodeId?.localId, fieldId.field];
4003
+ }
4004
+
3121
4005
  function cloneNodeChangeset(nodeChangeset: NodeChangeset): NodeChangeset {
3122
4006
  if (nodeChangeset.fieldChanges !== undefined) {
3123
4007
  return { ...nodeChangeset, fieldChanges: new Map(nodeChangeset.fieldChanges) };
@@ -3126,6 +4010,15 @@ function cloneNodeChangeset(nodeChangeset: NodeChangeset): NodeChangeset {
3126
4010
  return { ...nodeChangeset };
3127
4011
  }
3128
4012
 
4013
+ function replaceNodeLocationRevision(
4014
+ location: NodeLocation,
4015
+ replacer: RevisionReplacer,
4016
+ ): NodeLocation {
4017
+ return location.field === undefined
4018
+ ? { root: replacer.getUpdatedAtomId(location.root) }
4019
+ : { field: replaceFieldIdRevision(location.field, replacer) };
4020
+ }
4021
+
3129
4022
  function replaceFieldIdRevision(fieldId: FieldId, replacer: RevisionReplacer): FieldId {
3130
4023
  if (fieldId.nodeId === undefined) {
3131
4024
  return fieldId;
@@ -3137,10 +4030,16 @@ function replaceFieldIdRevision(fieldId: FieldId, replacer: RevisionReplacer): F
3137
4030
  };
3138
4031
  }
3139
4032
 
3140
- export function getParentFieldId(changeset: ModularChangeset, nodeId: NodeId): FieldId {
3141
- const parentId = getFromChangeAtomIdMap(changeset.nodeToParent, nodeId);
3142
- assert(parentId !== undefined, 0x9cb /* Parent field should be defined */);
3143
- return normalizeFieldId(parentId, changeset.nodeAliases);
4033
+ export function getNodeParent(changeset: ModularChangeset, nodeId: NodeId): NodeLocation {
4034
+ const normalizedNodeId = normalizeNodeId(nodeId, changeset.nodeAliases);
4035
+ const location = getFromChangeAtomIdMap(changeset.nodeToParent, normalizedNodeId);
4036
+ assert(location !== undefined, 0x9cb /* Parent field should be defined */);
4037
+
4038
+ if (location.field !== undefined) {
4039
+ return { field: normalizeFieldId(location.field, changeset.nodeAliases) };
4040
+ }
4041
+
4042
+ return location;
3144
4043
  }
3145
4044
 
3146
4045
  function getFieldsForCrossFieldKey(
@@ -3153,6 +4052,30 @@ function getFieldsForCrossFieldKey(
3153
4052
  .map(({ value: fieldId }) => normalizeFieldId(fieldId, changeset.nodeAliases));
3154
4053
  }
3155
4054
 
4055
+ function getFirstFieldForCrossFieldKey(
4056
+ changeset: ModularChangeset,
4057
+ key: CrossFieldKey,
4058
+ count: number,
4059
+ ): RangeQueryResult<FieldId | undefined> {
4060
+ const result = changeset.crossFieldKeys.getFirst(key, count);
4061
+ if (result.value === undefined) {
4062
+ return result;
4063
+ }
4064
+
4065
+ return { ...result, value: normalizeFieldId(result.value, changeset.nodeAliases) };
4066
+ }
4067
+
4068
+ function normalizeNodeLocation(
4069
+ location: NodeLocation,
4070
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
4071
+ ): NodeLocation {
4072
+ if (location.field !== undefined) {
4073
+ return { field: normalizeFieldId(location.field, nodeAliases) };
4074
+ }
4075
+
4076
+ return location;
4077
+ }
4078
+
3156
4079
  // This is only exported for use in test utilities.
3157
4080
  export function normalizeFieldId(
3158
4081
  fieldId: FieldId,
@@ -3166,8 +4089,12 @@ export function normalizeFieldId(
3166
4089
  /**
3167
4090
  * @returns The canonical form of nodeId, according to nodeAliases
3168
4091
  */
3169
- function normalizeNodeId(nodeId: NodeId, nodeAliases: ChangeAtomIdBTree<NodeId>): NodeId {
4092
+ export function normalizeNodeId(
4093
+ nodeId: NodeId,
4094
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
4095
+ ): NodeId {
3170
4096
  let currentId = nodeId;
4097
+ let cycleProbeId: NodeId | undefined = nodeId;
3171
4098
 
3172
4099
  // eslint-disable-next-line no-constant-condition
3173
4100
  while (true) {
@@ -3177,6 +4104,16 @@ function normalizeNodeId(nodeId: NodeId, nodeAliases: ChangeAtomIdBTree<NodeId>)
3177
4104
  }
3178
4105
 
3179
4106
  currentId = dealiased;
4107
+
4108
+ if (cycleProbeId !== undefined) {
4109
+ cycleProbeId = getFromChangeAtomIdMap(nodeAliases, cycleProbeId);
4110
+ }
4111
+
4112
+ if (cycleProbeId !== undefined) {
4113
+ cycleProbeId = getFromChangeAtomIdMap(nodeAliases, cycleProbeId);
4114
+ }
4115
+
4116
+ assert(!areEqualChangeAtomIdOpts(cycleProbeId, currentId), "Alias cycle detected");
3180
4117
  }
3181
4118
  }
3182
4119
 
@@ -3184,23 +4121,11 @@ function hasConflicts(change: ModularChangeset): boolean {
3184
4121
  return (change.constraintViolationCount ?? 0) > 0;
3185
4122
  }
3186
4123
 
3187
- /**
3188
- * A rebaseChild callback for fields with no new changes.
3189
- * Asserts that there are no new changes and returns undefined.
3190
- */
3191
- function noNewChangesRebaseChild(
3192
- child: NodeId | undefined,
3193
- _baseChild: NodeId | undefined,
3194
- _stateChange: NodeAttachState | undefined,
3195
- ): NodeId | undefined {
3196
- assert(child === undefined, 0x9c3 /* There should be no new changes in this field */);
3197
- return undefined;
3198
- }
3199
-
3200
4124
  interface ModularChangesetContent {
3201
4125
  fieldChanges: FieldChangeMap;
3202
4126
  nodeChanges: ChangeAtomIdBTree<NodeChangeset>;
3203
- nodeToParent: ChangeAtomIdBTree<FieldId>;
4127
+ nodeToParent: ChangeAtomIdBTree<NodeLocation>;
4128
+ rootNodes: RootNodeTable;
3204
4129
  nodeAliases: ChangeAtomIdBTree<NodeId>;
3205
4130
  crossFieldKeys: CrossFieldKeyTable;
3206
4131
  }
@@ -3209,7 +4134,1028 @@ function areEqualFieldIds(a: FieldId, b: FieldId): boolean {
3209
4134
  return areEqualChangeAtomIdOpts(a.nodeId, b.nodeId) && a.field === b.field;
3210
4135
  }
3211
4136
 
3212
- function newFieldIdKeyBTree<V>(): TupleBTree<FieldIdKey, V> {
4137
+ function firstAttachIdFromDetachId(
4138
+ roots: RootNodeTable,
4139
+ detachId: ChangeAtomId,
4140
+ count: number,
4141
+ ): RangeQueryResult<ChangeAtomId> {
4142
+ const result = roots.oldToNewId.getFirst(detachId, count);
4143
+ return { ...result, value: result.value ?? detachId };
4144
+ }
4145
+
4146
+ function firstDetachIdFromAttachId(
4147
+ roots: RootNodeTable,
4148
+ attachId: ChangeAtomId,
4149
+ count: number,
4150
+ ): RangeQueryEntry<ChangeAtomId, ChangeAtomId> {
4151
+ const result = roots.newToOldId.getFirst(attachId, count);
4152
+ return { ...result, start: attachId, value: result.value ?? attachId };
4153
+ }
4154
+
4155
+ function rebaseCrossFieldKeys(
4156
+ sourceTable: CrossFieldKeyTable,
4157
+ movedDetaches: ChangeAtomIdRangeMap<boolean>,
4158
+ newDetachLocations: ChangeAtomIdRangeMap<FieldId>,
4159
+ ): CrossFieldKeyTable {
4160
+ const rebasedTable = sourceTable.clone();
4161
+ for (const entry of movedDetaches.entries()) {
4162
+ rebasedTable.delete({ ...entry.start, target: NodeMoveType.Detach }, entry.length);
4163
+ }
4164
+
4165
+ for (const entry of newDetachLocations.entries()) {
4166
+ rebasedTable.set(
4167
+ { ...entry.start, target: NodeMoveType.Detach },
4168
+ entry.length,
4169
+ entry.value,
4170
+ );
4171
+ }
4172
+
4173
+ return rebasedTable;
4174
+ }
4175
+
4176
+ export function newRootTable(): RootNodeTable {
4177
+ return {
4178
+ newToOldId: newChangeAtomIdTransform(),
4179
+ oldToNewId: newChangeAtomIdTransform(),
4180
+ nodeChanges: newChangeAtomIdBTree(),
4181
+ detachLocations: newChangeAtomIdRangeMap(),
4182
+ outputDetachLocations: newChangeAtomIdRangeMap(),
4183
+ };
4184
+ }
4185
+
4186
+ function rebaseRoots(
4187
+ change: ModularChangeset,
4188
+ base: ModularChangeset,
4189
+ affectedBaseFields: TupleBTree<FieldIdKey, boolean>,
4190
+ nodesToRebase: [newChangeset: NodeId, baseChangeset: NodeId][],
4191
+ rebasedNodeToParent: ChangeAtomIdBTree<NodeLocation>,
4192
+ rebaseVersion: RebaseVersion,
4193
+ ): RootNodeTable {
4194
+ const rebasedRoots = newRootTable();
4195
+ for (const renameEntry of change.rootNodes.oldToNewId.entries()) {
4196
+ rebaseRename(change.rootNodes, rebasedRoots, renameEntry, base, affectedBaseFields);
4197
+ }
4198
+
4199
+ for (const [detachIdKey, nodeId] of change.rootNodes.nodeChanges.entries()) {
4200
+ const changes = base.rootNodes.nodeChanges.get(detachIdKey);
4201
+ if (changes !== undefined) {
4202
+ nodesToRebase.push([nodeId, changes]);
4203
+ }
4204
+
4205
+ const detachId = makeChangeAtomId(detachIdKey[1], detachIdKey[0]);
4206
+ const attachId = firstAttachIdFromDetachId(base.rootNodes, detachId, 1).value;
4207
+ const baseAttachEntry = base.crossFieldKeys.getFirst(
4208
+ { target: NodeMoveType.Attach, ...attachId },
4209
+ 1,
4210
+ );
4211
+ if (baseAttachEntry.value === undefined) {
4212
+ const renamedDetachId = firstAttachIdFromDetachId(base.rootNodes, detachId, 1).value;
4213
+ const baseOutputDetachLocation = base.rootNodes.outputDetachLocations.getFirst(
4214
+ renamedDetachId,
4215
+ 1,
4216
+ ).value;
4217
+
4218
+ if (baseOutputDetachLocation !== undefined) {
4219
+ affectedBaseFields.set(fieldIdKeyFromFieldId(baseOutputDetachLocation), true);
4220
+ }
4221
+
4222
+ const detachLocation =
4223
+ baseOutputDetachLocation ??
4224
+ change.rootNodes.detachLocations.getFirst(detachId, 1).value;
4225
+
4226
+ // Note that `baseOutputDetachLocation` may contain a node ID from the base changeset.
4227
+ // We will replace the detach location entry with the node ID from the rebased changeset in `fixupRebasedDetachLocations`
4228
+ assignRootChange(
4229
+ rebasedRoots,
4230
+ rebasedNodeToParent,
4231
+ renamedDetachId,
4232
+ nodeId,
4233
+ detachLocation,
4234
+ rebaseVersion,
4235
+ );
4236
+ } else {
4237
+ affectedBaseFields.set(fieldIdKeyFromFieldId(baseAttachEntry.value), true);
4238
+ rebasedNodeToParent.delete(detachIdKey);
4239
+ }
4240
+ }
4241
+
4242
+ for (const entry of change.rootNodes.outputDetachLocations.entries()) {
4243
+ rebasedRoots.outputDetachLocations.set(entry.start, entry.length, entry.value);
4244
+ }
4245
+
4246
+ return rebasedRoots;
4247
+ }
4248
+
4249
+ function rebaseRename(
4250
+ newRoots: RootNodeTable,
4251
+ rebasedRoots: RootNodeTable,
4252
+ renameEntry: RangeQueryEntry<ChangeAtomId, ChangeAtomId>,
4253
+ base: ModularChangeset,
4254
+ affectedBaseFields: TupleBTree<FieldIdKey, boolean>,
4255
+ ): void {
4256
+ let count = renameEntry.length;
4257
+ const baseRenameEntry = firstAttachIdFromDetachId(base.rootNodes, renameEntry.start, count);
4258
+ count = baseRenameEntry.length;
4259
+
4260
+ const baseAttachEntry = base.crossFieldKeys.getFirst(
4261
+ {
4262
+ ...baseRenameEntry.value,
4263
+ target: NodeMoveType.Attach,
4264
+ },
4265
+ count,
4266
+ );
4267
+
4268
+ count = baseAttachEntry.length;
4269
+
4270
+ if (baseAttachEntry.value === undefined) {
4271
+ const baseOutputDetachLocation = base.rootNodes.outputDetachLocations.getFirst(
4272
+ baseRenameEntry.value,
4273
+ 1,
4274
+ ).value;
4275
+
4276
+ if (baseOutputDetachLocation !== undefined) {
4277
+ affectedBaseFields.set(fieldIdKeyFromFieldId(baseOutputDetachLocation), true);
4278
+ }
4279
+
4280
+ const detachEntry = newRoots.detachLocations.getFirst(renameEntry.start, count);
4281
+ count = detachEntry.length;
4282
+
4283
+ const detachLocation = baseOutputDetachLocation ?? detachEntry.value;
4284
+
4285
+ // Note that `baseOutputDetachLocation` may contain a node ID from the base changeset.
4286
+ // We will replace the detach location entry with the node ID from the rebased changeset in `fixupRebasedDetachLocations`
4287
+ addNodeRename(
4288
+ rebasedRoots,
4289
+ baseRenameEntry.value,
4290
+ renameEntry.value,
4291
+ count,
4292
+ detachLocation,
4293
+ );
4294
+ } else {
4295
+ // This rename represents an intention to detach these nodes.
4296
+ // The rebased change should have a detach in the field where the base change attaches the nodes,
4297
+ // so we need to ensure that field is processed.
4298
+ affectedBaseFields.set(
4299
+ fieldIdKeyFromFieldId(normalizeFieldId(baseAttachEntry.value, base.nodeAliases)),
4300
+ true,
4301
+ );
4302
+ }
4303
+
4304
+ const countRemaining = renameEntry.length - count;
4305
+ if (countRemaining > 0) {
4306
+ rebaseRename(
4307
+ newRoots,
4308
+ rebasedRoots,
4309
+ {
4310
+ start: offsetChangeAtomId(renameEntry.start, count),
4311
+ value: offsetChangeAtomId(renameEntry.value, count),
4312
+ length: countRemaining,
4313
+ },
4314
+ base,
4315
+ affectedBaseFields,
4316
+ );
4317
+ }
4318
+ }
4319
+
4320
+ /**
4321
+ * For each root detach location, replaces any node ID from the base changeset
4322
+ * with the corresponding ID in the new changeset.
4323
+ */
4324
+ function fixupRebasedDetachLocations(table: RebaseTable): void {
4325
+ for (const {
4326
+ start,
4327
+ length,
4328
+ value: detachLocation,
4329
+ } of table.rebasedRootNodes.detachLocations.entries()) {
4330
+ const normalizedDetachLocation = normalizeFieldId(
4331
+ detachLocation,
4332
+ table.baseChange.nodeAliases,
4333
+ );
4334
+
4335
+ if (normalizedDetachLocation.nodeId !== undefined) {
4336
+ const rebasedNodeId = getFromChangeAtomIdMap(
4337
+ table.baseToRebasedNodeId,
4338
+ normalizedDetachLocation.nodeId,
4339
+ );
4340
+
4341
+ if (rebasedNodeId !== undefined) {
4342
+ table.rebasedRootNodes.detachLocations.set(start, length, {
4343
+ ...normalizedDetachLocation,
4344
+ nodeId: rebasedNodeId,
4345
+ });
4346
+ }
4347
+ }
4348
+ }
4349
+ }
4350
+
4351
+ function addNodesToCompose(table: ComposeTable, id1: NodeId, id2: NodeId): void {
4352
+ const normalizedId1 = normalizeNodeId(id1, table.baseChange.nodeAliases);
4353
+ const normalizedId2 = normalizeNodeId(id2, table.newChange.nodeAliases);
4354
+ if (getFromChangeAtomIdMap(table.newToBaseNodeId, normalizedId2) === undefined) {
4355
+ setInChangeAtomIdMap(table.newToBaseNodeId, normalizedId2, normalizedId1);
4356
+ table.pendingCompositions.nodeIdsToCompose.push([normalizedId1, normalizedId2]);
4357
+ }
4358
+ }
4359
+
4360
+ function composeRevInfos(
4361
+ revisions1: readonly RevisionInfo[] | undefined,
4362
+ revisions2: readonly RevisionInfo[] | undefined,
4363
+ ): readonly RevisionInfo[] {
4364
+ if (
4365
+ revisions1?.length === 1 &&
4366
+ revisions2?.length === 1 &&
4367
+ revisions1[0]?.revision === revisions2[0]?.revision
4368
+ ) {
4369
+ // This is a special case where we are composing two changesets from the same transaction.
4370
+ // We return one of the input arrays to avoid duplicating revision entries.
4371
+ return revisions1;
4372
+ }
4373
+ const result: RevisionInfo[] = [...(revisions1 ?? []), ...(revisions2 ?? [])];
4374
+ return result;
4375
+ }
4376
+
4377
+ function composeCrossFieldKeyTables(
4378
+ table1: CrossFieldKeyTable,
4379
+ table2: CrossFieldKeyTable,
4380
+ movedCrossFieldKeys: CrossFieldKeyTable,
4381
+ removedCrossFieldKeys: CrossFieldRangeTable<boolean>,
4382
+ ): CrossFieldKeyTable {
4383
+ const composedTable = RangeMap.union(table1, table2);
4384
+ for (const entry of movedCrossFieldKeys.entries()) {
4385
+ composedTable.set(entry.start, entry.length, entry.value);
4386
+ }
4387
+
4388
+ for (const entry of removedCrossFieldKeys.entries()) {
4389
+ composedTable.delete(entry.start, entry.length);
4390
+ }
4391
+
4392
+ return composedTable;
4393
+ }
4394
+
4395
+ function composeRootTables(
4396
+ change1: ModularChangeset,
4397
+ change2: ModularChangeset,
4398
+ composedNodeToParent: ChangeAtomIdBTree<NodeLocation>,
4399
+ movedCrossFieldKeys: CrossFieldKeyTable,
4400
+ removedCrossFieldKeys: CrossFieldRangeTable<boolean>,
4401
+ pendingCompositions: PendingCompositions,
4402
+ ): RootNodeTable {
4403
+ const composedTable = cloneRootTable(change1.rootNodes);
4404
+
4405
+ for (const renameEntry of change2.rootNodes.oldToNewId.entries()) {
4406
+ composeRename(
4407
+ change1,
4408
+ change2,
4409
+ composedTable,
4410
+ renameEntry.start,
4411
+ renameEntry.value,
4412
+ renameEntry.length,
4413
+ movedCrossFieldKeys,
4414
+ removedCrossFieldKeys,
4415
+ pendingCompositions,
4416
+ );
4417
+ }
4418
+
4419
+ for (const [[revision2, id2], nodeId2] of change2.rootNodes.nodeChanges.entries()) {
4420
+ const detachId2 = { revision: revision2, localId: id2 };
4421
+ const detachId1 = firstDetachIdFromAttachId(change1.rootNodes, detachId2, 1).value;
4422
+ const nodeId1 = getFromChangeAtomIdMap(change1.rootNodes.nodeChanges, detachId1);
4423
+
4424
+ if (nodeId1 === undefined) {
4425
+ const fieldId = getFieldsForCrossFieldKey(
4426
+ change1,
4427
+ { ...detachId1, target: NodeMoveType.Detach },
4428
+ 1,
4429
+ )[0];
4430
+
4431
+ if (fieldId === undefined) {
4432
+ assignRootChange(
4433
+ composedTable,
4434
+ composedNodeToParent,
4435
+ detachId1,
4436
+ nodeId2,
4437
+ change1.rootNodes.detachLocations.getFirst(detachId1, 1).value ??
4438
+ change2.rootNodes.detachLocations.getFirst(detachId2, 1).value,
4439
+ Math.max(change1.rebaseVersion, change2.rebaseVersion) as RebaseVersion,
4440
+ );
4441
+ } else {
4442
+ // In this case, this node is attached in the input context of change1,
4443
+ // and is represented in detachFieldId.
4444
+ pendingCompositions.affectedBaseFields.set(
4445
+ [fieldId.nodeId?.revision, fieldId.nodeId?.localId, fieldId.field],
4446
+ true,
4447
+ );
4448
+ }
4449
+ } else {
4450
+ pendingCompositions.nodeIdsToCompose.push([nodeId1, nodeId2]);
4451
+ }
4452
+ }
4453
+
4454
+ for (const outputDetachEntry of change1.rootNodes.outputDetachLocations.entries()) {
4455
+ composeOutputDetachLocation(
4456
+ outputDetachEntry.start,
4457
+ outputDetachEntry.length,
4458
+ outputDetachEntry.value,
4459
+ change2,
4460
+ composedTable,
4461
+ );
4462
+ }
4463
+
4464
+ for (const entry of change2.rootNodes.outputDetachLocations.entries()) {
4465
+ composedTable.outputDetachLocations.set(entry.start, entry.length, entry.value);
4466
+ }
4467
+
4468
+ return composedTable;
4469
+ }
4470
+
4471
+ function composeOutputDetachLocation(
4472
+ outputDetachId1: ChangeAtomId,
4473
+ count: number,
4474
+ detachLocation: FieldId,
4475
+ change2: ModularChangeset,
4476
+ composedTable: RootNodeTable,
4477
+ ): void {
4478
+ let countToProcess = count;
4479
+ const renameEntry = firstAttachIdFromDetachId(
4480
+ change2.rootNodes,
4481
+ outputDetachId1,
4482
+ countToProcess,
4483
+ );
4484
+ countToProcess = renameEntry.length;
4485
+
4486
+ const attachEntry = getFirstAttachField(
4487
+ change2.crossFieldKeys,
4488
+ renameEntry.value,
4489
+ countToProcess,
4490
+ );
4491
+ countToProcess = attachEntry.length;
4492
+
4493
+ composedTable.outputDetachLocations.delete(outputDetachId1, countToProcess);
4494
+
4495
+ if (attachEntry.value === undefined) {
4496
+ // We update the key for the detach location to the renamed ID of the root in the composed output context.
4497
+ composedTable.outputDetachLocations.set(renameEntry.value, countToProcess, detachLocation);
4498
+ } else {
4499
+ // These nodes are attached by `change2` and thus attached in the composed output context,
4500
+ // so there should be no output detach location.
4501
+ }
4502
+
4503
+ const countRemaining = count - countToProcess;
4504
+ if (countRemaining > 0) {
4505
+ composeOutputDetachLocation(
4506
+ offsetChangeAtomId(outputDetachId1, countToProcess),
4507
+ countRemaining,
4508
+ detachLocation,
4509
+ change2,
4510
+ composedTable,
4511
+ );
4512
+ }
4513
+ }
4514
+
4515
+ function composeRename(
4516
+ change1: ModularChangeset,
4517
+ change2: ModularChangeset,
4518
+ mergedTable: RootNodeTable,
4519
+ oldId: ChangeAtomId,
4520
+ newId: ChangeAtomId,
4521
+ count: number,
4522
+ movedCrossFieldKeys: CrossFieldKeyTable,
4523
+ removedCrossFieldKeys: CrossFieldRangeTable<boolean>,
4524
+ pendingCompositions: PendingCompositions,
4525
+ ): void {
4526
+ let countToProcess = count;
4527
+ const detachEntry = getFirstDetachField(change1.crossFieldKeys, oldId, countToProcess);
4528
+ countToProcess = detachEntry.length;
4529
+
4530
+ if (detachEntry.value === undefined) {
4531
+ // `change1` may also have a rename to `renameEntry.value`, in which case it must refer to a different node.
4532
+ // That node must have been attached by `change1` and detached by `change2`.
4533
+ // The final rename for that node will be created in `composeAttachDetach`.
4534
+ // We delete any such rename for now to avoid colliding with the rename currently being processed.
4535
+ deleteNodeRenameTo(mergedTable, newId, countToProcess);
4536
+
4537
+ // The nodes were detached before `change`, so we append this rename.
4538
+ appendNodeRename(
4539
+ mergedTable,
4540
+ oldId,
4541
+ newId,
4542
+ countToProcess,
4543
+ change1.rootNodes,
4544
+ change2.rootNodes.detachLocations.getFirst(oldId, countToProcess).value,
4545
+ );
4546
+ } else {
4547
+ // `change1` detached these nodes,
4548
+ // so we invalidate the detach location so that the detach's ID can be replaced with the new ID.
4549
+ pendingCompositions.affectedBaseFields.set(fieldIdKeyFromFieldId(detachEntry.value), true);
4550
+
4551
+ if (!areEqualChangeAtomIds(oldId, newId)) {
4552
+ // `change1`'s detach will be replaced by `change2`'s detach, so we update the cross-field keys.
4553
+ removedCrossFieldKeys.set(
4554
+ { ...oldId, target: NodeMoveType.Detach },
4555
+ countToProcess,
4556
+ true,
4557
+ );
4558
+ }
4559
+
4560
+ movedCrossFieldKeys.set(
4561
+ { ...newId, target: NodeMoveType.Detach },
4562
+ countToProcess,
4563
+ detachEntry.value,
4564
+ );
4565
+ }
4566
+
4567
+ if (countToProcess < count) {
4568
+ composeRename(
4569
+ change1,
4570
+ change2,
4571
+ mergedTable,
4572
+ offsetChangeAtomId(oldId, countToProcess),
4573
+ offsetChangeAtomId(newId, countToProcess),
4574
+ count - countToProcess,
4575
+ movedCrossFieldKeys,
4576
+ removedCrossFieldKeys,
4577
+ pendingCompositions,
4578
+ );
4579
+ }
4580
+ }
4581
+
4582
+ export function cloneRootTable(table: RootNodeTable): RootNodeTable {
4583
+ return {
4584
+ oldToNewId: table.oldToNewId.clone(),
4585
+ newToOldId: table.newToOldId.clone(),
4586
+ nodeChanges: brand(table.nodeChanges.clone()),
4587
+ detachLocations: table.detachLocations.clone(),
4588
+ outputDetachLocations: table.outputDetachLocations.clone(),
4589
+ };
4590
+ }
4591
+
4592
+ function invertRootTable(
4593
+ change: ModularChangeset,
4594
+ invertedNodeToParent: ChangeAtomIdBTree<NodeLocation>,
4595
+ isRollback: boolean,
4596
+ ): RootNodeTable {
4597
+ const invertedRoots: RootNodeTable = newRootTable();
4598
+
4599
+ if (isRollback) {
4600
+ // We only invert renames of nodes which are not attached or detached by this changeset.
4601
+ // When we invert an attach we will create a detach which incorporates the rename.
4602
+ for (const {
4603
+ start: oldId,
4604
+ value: newId,
4605
+ length,
4606
+ } of change.rootNodes.oldToNewId.entries()) {
4607
+ invertRename(change, invertedRoots, oldId, newId, length);
4608
+ }
4609
+ }
4610
+
4611
+ for (const [[revision, localId], nodeId] of change.rootNodes.nodeChanges.entries()) {
4612
+ const detachId: ChangeAtomId = { revision, localId };
4613
+ const renamedId = firstAttachIdFromDetachId(change.rootNodes, detachId, 1).value;
4614
+
4615
+ // This checks whether `change` attaches this node.
4616
+ // 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.
4617
+ if (
4618
+ change.crossFieldKeys.getFirst({ ...renamedId, target: NodeMoveType.Attach }, 1)
4619
+ .value === undefined
4620
+ ) {
4621
+ assignRootChange(
4622
+ invertedRoots,
4623
+ invertedNodeToParent,
4624
+ renamedId,
4625
+ nodeId,
4626
+ change.rootNodes.detachLocations.getFirst(detachId, 1).value,
4627
+ change.rebaseVersion,
4628
+ );
4629
+ }
4630
+ }
4631
+
4632
+ return invertedRoots;
4633
+ }
4634
+
4635
+ function invertRename(
4636
+ change: ModularChangeset,
4637
+ invertedRoots: RootNodeTable,
4638
+ oldId: ChangeAtomId,
4639
+ newId: ChangeAtomId,
4640
+ length: number,
4641
+ ): void {
4642
+ for (const detachEntry of doesChangeDetachNodes(change.crossFieldKeys, newId, length)) {
4643
+ assert(
4644
+ !detachEntry.value,
4645
+ "A changeset should not have a rename and detach for the same node.",
4646
+ );
4647
+ }
4648
+
4649
+ let countProcessed = length;
4650
+ const outputDetachEntry = change.rootNodes.outputDetachLocations.getFirst(
4651
+ newId,
4652
+ countProcessed,
4653
+ );
4654
+ countProcessed = outputDetachEntry.length;
4655
+
4656
+ const inputDetachEntry = change.rootNodes.detachLocations.getFirst(oldId, countProcessed);
4657
+ countProcessed = inputDetachEntry.length;
4658
+
4659
+ const attachEntry = getFirstAttachField(change.crossFieldKeys, newId, countProcessed);
4660
+ countProcessed = attachEntry.length;
4661
+ if (attachEntry.value === undefined) {
4662
+ addNodeRename(
4663
+ invertedRoots,
4664
+ newId,
4665
+ oldId,
4666
+ countProcessed,
4667
+ outputDetachEntry.value ?? inputDetachEntry.value,
4668
+ );
4669
+
4670
+ // The original change moves the detached node, so the inverse should also record a move back to the original location.
4671
+ if (outputDetachEntry.value !== undefined && inputDetachEntry.value !== undefined) {
4672
+ invertedRoots.outputDetachLocations.set(oldId, countProcessed, inputDetachEntry.value);
4673
+ }
4674
+ }
4675
+
4676
+ if (countProcessed < length) {
4677
+ invertRename(
4678
+ change,
4679
+ invertedRoots,
4680
+ offsetChangeAtomId(oldId, countProcessed),
4681
+ offsetChangeAtomId(newId, countProcessed),
4682
+ length - countProcessed,
4683
+ );
4684
+ }
4685
+ }
4686
+
4687
+ function doesChangeAttachNodes(
4688
+ table: CrossFieldKeyTable,
4689
+ id: ChangeAtomId,
4690
+ count: number,
4691
+ ): RangeQueryResultFragment<boolean>[] {
4692
+ return table
4693
+ .getAll2({ ...id, target: NodeMoveType.Attach }, count)
4694
+ .map((entry) => ({ ...entry, value: entry.value !== undefined }));
4695
+ }
4696
+
4697
+ function doesChangeDetachNodes(
4698
+ table: CrossFieldKeyTable,
4699
+ id: ChangeAtomId,
4700
+ count: number,
4701
+ ): RangeQueryResultFragment<boolean>[] {
4702
+ return table
4703
+ .getAll2({ ...id, target: NodeMoveType.Detach }, count)
4704
+ .map((entry) => ({ ...entry, value: entry.value !== undefined }));
4705
+ }
4706
+
4707
+ export function getFirstDetachField(
4708
+ table: CrossFieldKeyTable,
4709
+ id: ChangeAtomId,
4710
+ count: number,
4711
+ ): RangeQueryResult<FieldId | undefined> {
4712
+ return table.getFirst({ target: NodeMoveType.Detach, ...id }, count);
4713
+ }
4714
+
4715
+ export function getFirstAttachField(
4716
+ table: CrossFieldKeyTable,
4717
+ id: ChangeAtomId,
4718
+ count: number,
4719
+ ): RangeQueryResult<FieldId | undefined> {
4720
+ return table.getFirst({ target: NodeMoveType.Attach, ...id }, count);
4721
+ }
4722
+
4723
+ export function addNodeRename(
4724
+ table: RootNodeTable,
4725
+ oldId: ChangeAtomId,
4726
+ newId: ChangeAtomId,
4727
+ count: number,
4728
+ detachLocation: FieldId | undefined,
4729
+ ): void {
4730
+ if (areEqualChangeAtomIds(oldId, newId)) {
4731
+ return;
4732
+ }
4733
+
4734
+ for (const entry of table.oldToNewId.getAll2(oldId, count)) {
4735
+ assert(
4736
+ entry.value === undefined ||
4737
+ areEqualChangeAtomIds(entry.value, offsetChangeAtomId(newId, entry.offset)),
4738
+ "Rename collision detected",
4739
+ );
4740
+ }
4741
+
4742
+ for (const entry of table.newToOldId.getAll2(newId, count)) {
4743
+ assert(
4744
+ entry.value === undefined ||
4745
+ areEqualChangeAtomIds(entry.value, offsetChangeAtomId(oldId, entry.offset)),
4746
+ "Rename collision detected",
4747
+ );
4748
+ }
4749
+
4750
+ table.oldToNewId.set(oldId, count, newId);
4751
+ table.newToOldId.set(newId, count, oldId);
4752
+
4753
+ if (detachLocation !== undefined) {
4754
+ table.detachLocations.set(oldId, count, detachLocation);
4755
+ }
4756
+ }
4757
+
4758
+ /**
4759
+ * Deletes any renames from `id`.
4760
+ */
4761
+ function deleteNodeRenameFrom(roots: RootNodeTable, id: ChangeAtomId, count: number): void {
4762
+ for (const entry of roots.oldToNewId.getAll(id, count)) {
4763
+ deleteNodeRenameEntry(roots, entry.start, entry.value, entry.length);
4764
+ }
4765
+ }
4766
+
4767
+ /**
4768
+ * Deletes any renames to `id`.
4769
+ */
4770
+ function deleteNodeRenameTo(roots: RootNodeTable, id: ChangeAtomId, count: number): void {
4771
+ for (const entry of roots.newToOldId.getAll(id, count)) {
4772
+ deleteNodeRenameEntry(roots, entry.value, entry.start, entry.length);
4773
+ }
4774
+ }
4775
+
4776
+ function appendNodeRename(
4777
+ composedTable: RootNodeTable,
4778
+ oldId: ChangeAtomId,
4779
+ newId: ChangeAtomId,
4780
+ count: number,
4781
+ change1Table: RootNodeTable,
4782
+ detachLocation: FieldId | undefined,
4783
+ ): void {
4784
+ let countToProcess = count;
4785
+ const rename1Entry = change1Table.newToOldId.getFirst(oldId, countToProcess);
4786
+ countToProcess = rename1Entry.length;
4787
+
4788
+ if (rename1Entry.value !== undefined) {
4789
+ deleteNodeRenameFrom(composedTable, rename1Entry.value, countToProcess);
4790
+ }
4791
+
4792
+ addNodeRename(
4793
+ composedTable,
4794
+ rename1Entry.value ?? oldId,
4795
+ newId,
4796
+ countToProcess,
4797
+ detachLocation,
4798
+ );
4799
+
4800
+ tryRemoveDetachLocation(composedTable, newId, countToProcess);
4801
+
4802
+ if (countToProcess < count) {
4803
+ const countRemaining = count - countToProcess;
4804
+ appendNodeRename(
4805
+ composedTable,
4806
+ offsetChangeAtomId(oldId, countToProcess),
4807
+ offsetChangeAtomId(newId, countToProcess),
4808
+ countRemaining,
4809
+ change1Table,
4810
+ detachLocation,
4811
+ );
4812
+ }
4813
+ }
4814
+
4815
+ function tryRemoveDetachLocation(
4816
+ roots: RootNodeTable,
4817
+ rootId: ChangeAtomId,
4818
+ count: number,
4819
+ ): void {
4820
+ let countProcessed = count;
4821
+ const renameEntry = roots.oldToNewId.getFirst(rootId, countProcessed);
4822
+ countProcessed = renameEntry.length;
4823
+
4824
+ const outputDetachEntry = roots.outputDetachLocations.getFirst(rootId, countProcessed);
4825
+ countProcessed = outputDetachEntry.length;
4826
+
4827
+ const nodeChangeEntry = rangeQueryChangeAtomIdMap(roots.nodeChanges, rootId, countProcessed);
4828
+ countProcessed = nodeChangeEntry.length;
4829
+
4830
+ if (
4831
+ nodeChangeEntry.value === undefined &&
4832
+ renameEntry.value === undefined &&
4833
+ outputDetachEntry.value === undefined
4834
+ ) {
4835
+ roots.detachLocations.delete(rootId, countProcessed);
4836
+ }
4837
+
4838
+ const countRemaining = count - countProcessed;
4839
+ if (countRemaining > 0) {
4840
+ tryRemoveDetachLocation(roots, offsetChangeAtomId(rootId, countProcessed), countRemaining);
4841
+ }
4842
+ }
4843
+
4844
+ /**
4845
+ * Deletes the entry renaming the ID range of length `count` from `oldId` to `newId`.
4846
+ * This function assumes that such an entry exists.
4847
+ */
4848
+ function deleteNodeRenameEntry(
4849
+ roots: RootNodeTable,
4850
+ oldId: ChangeAtomId,
4851
+ newId: ChangeAtomId,
4852
+ count: number,
4853
+ ): void {
4854
+ roots.oldToNewId.delete(oldId, count);
4855
+ roots.newToOldId.delete(newId, count);
4856
+ }
4857
+
4858
+ function replaceRootTableRevision(
4859
+ table: RootNodeTable,
4860
+ replacer: RevisionReplacer,
4861
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
4862
+ ): RootNodeTable {
4863
+ const oldToNewId = table.oldToNewId.mapEntries(
4864
+ (id) => replacer.getUpdatedAtomId(id),
4865
+ (id) => replacer.getUpdatedAtomId(id),
4866
+ );
4867
+
4868
+ const newToOldId = table.newToOldId.mapEntries(
4869
+ (id) => replacer.getUpdatedAtomId(id),
4870
+ (id) => replacer.getUpdatedAtomId(id),
4871
+ );
4872
+
4873
+ const nodeChanges: ChangeAtomIdBTree<NodeId> = replaceIdMapRevisions(
4874
+ table.nodeChanges,
4875
+ replacer,
4876
+ (nodeId) => replacer.getUpdatedAtomId(normalizeNodeId(nodeId, nodeAliases)),
4877
+ );
4878
+
4879
+ const detachLocations = table.detachLocations.mapEntries(
4880
+ (id) => replacer.getUpdatedAtomId(id),
4881
+ (fieldId) => replaceFieldIdRevision(normalizeFieldId(fieldId, nodeAliases), replacer),
4882
+ );
4883
+
4884
+ const outputDetachLocations = table.outputDetachLocations.mapEntries(
4885
+ (id) => replacer.getUpdatedAtomId(id),
4886
+ (fieldId) => replaceFieldIdRevision(normalizeFieldId(fieldId, nodeAliases), replacer),
4887
+ );
4888
+
4889
+ return { oldToNewId, newToOldId, nodeChanges, detachLocations, outputDetachLocations };
4890
+ }
4891
+
4892
+ function newDetachedEntryMap(): ChangeAtomIdRangeMap<DetachedNodeEntry> {
4893
+ return new RangeMap(offsetChangeAtomId, subtractChangeAtomIds, offsetDetachedNodeEntry);
4894
+ }
4895
+
4896
+ function offsetDetachedNodeEntry(entry: DetachedNodeEntry, count: number): DetachedNodeEntry {
4897
+ assert(
4898
+ count <= 1 || entry.nodeChange === undefined,
4899
+ "Cannot split an entry with a node change",
4900
+ );
4901
+
4902
+ return entry.detachId === undefined
4903
+ ? entry
4904
+ : { ...entry, detachId: offsetChangeAtomId(entry.detachId, count) };
4905
+ }
4906
+
4907
+ function getFieldsWithRootMoves(
4908
+ roots: RootNodeTable,
4909
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
4910
+ ): TupleBTree<FieldIdKey, boolean> {
4911
+ const fields: TupleBTree<FieldIdKey, boolean> = newFieldIdKeyBTree();
4912
+ for (const { start: rootId, value: fieldId, length } of roots.detachLocations.entries()) {
4913
+ let isRootMoved = false;
4914
+ for (const renameEntry of roots.oldToNewId.getAll2(rootId, length)) {
4915
+ if (renameEntry.value !== undefined) {
4916
+ isRootMoved = true;
4917
+ }
4918
+ }
4919
+
4920
+ for (const outputDetachEntry of roots.outputDetachLocations.getAll2(rootId, length)) {
4921
+ if (outputDetachEntry.value !== undefined) {
4922
+ isRootMoved = true;
4923
+ }
4924
+ }
4925
+
4926
+ if (isRootMoved) {
4927
+ fields.set(fieldIdKeyFromFieldId(normalizeFieldId(fieldId, nodeAliases)), true);
4928
+ }
4929
+ }
4930
+
4931
+ return fields;
4932
+ }
4933
+
4934
+ function getFieldToRootChanges(
4935
+ roots: RootNodeTable,
4936
+ nodeAliases: ChangeAtomIdBTree<NodeId>,
4937
+ ): TupleBTree<FieldIdKey, ChangeAtomId[]> {
4938
+ const fields: TupleBTree<FieldIdKey, ChangeAtomId[]> = newFieldIdKeyBTree();
4939
+ for (const rootIdKey of roots.nodeChanges.keys()) {
4940
+ const rootId: ChangeAtomId = { revision: rootIdKey[0], localId: rootIdKey[1] };
4941
+ const detachLocation = roots.detachLocations.getFirst(rootId, 1).value;
4942
+ if (detachLocation !== undefined) {
4943
+ const fieldIdKey = fieldIdKeyFromFieldId(normalizeFieldId(detachLocation, nodeAliases));
4944
+ let rootsInField = fields.get(fieldIdKey);
4945
+ if (rootsInField === undefined) {
4946
+ rootsInField = [];
4947
+ fields.set(fieldIdKey, rootsInField);
4948
+ }
4949
+
4950
+ rootsInField.push(rootId);
4951
+ }
4952
+ }
4953
+
4954
+ return fields;
4955
+ }
4956
+
4957
+ function muteRootChanges(roots: RootNodeTable): RootNodeTable {
4958
+ return {
4959
+ oldToNewId: newChangeAtomIdTransform(),
4960
+ newToOldId: newChangeAtomIdTransform(),
4961
+ nodeChanges: brand(roots.nodeChanges.clone()),
4962
+ detachLocations: roots.detachLocations.clone(),
4963
+ outputDetachLocations: newChangeAtomIdRangeMap(),
4964
+ };
4965
+ }
4966
+
4967
+ export function validateChangeset(
4968
+ change: ModularChangeset,
4969
+ fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
4970
+ ): void {
4971
+ const unreachableNodes: ChangeAtomIdBTree<NodeLocation> = brand(change.nodeToParent.clone());
4972
+
4973
+ const unreachableCFKs = change.crossFieldKeys.clone();
4974
+
4975
+ validateFieldChanges(
4976
+ fieldKinds,
4977
+ change,
4978
+ change.fieldChanges,
4979
+ undefined,
4980
+ unreachableNodes,
4981
+ unreachableCFKs,
4982
+ );
4983
+
4984
+ for (const [[revision, localId], node] of change.nodeChanges.entries()) {
4985
+ if (node.fieldChanges === undefined) {
4986
+ continue;
4987
+ }
4988
+
4989
+ const nodeId = normalizeNodeId({ revision, localId }, change.nodeAliases);
4990
+ validateFieldChanges(
4991
+ fieldKinds,
4992
+ change,
4993
+ node.fieldChanges,
4994
+ nodeId,
4995
+ unreachableNodes,
4996
+ unreachableCFKs,
4997
+ );
4998
+ }
4999
+
5000
+ for (const [detachIdKey, nodeId] of change.rootNodes.nodeChanges.entries()) {
5001
+ const detachId: ChangeAtomId = { revision: detachIdKey[0], localId: detachIdKey[1] };
5002
+ const location = getNodeParent(change, nodeId);
5003
+ assert(areEqualChangeAtomIdOpts(location.root, detachId), "Inconsistent node location");
5004
+
5005
+ const normalizedNodeId = normalizeNodeId(nodeId, change.nodeAliases);
5006
+ unreachableNodes.delete([normalizedNodeId.revision, normalizedNodeId.localId]);
5007
+
5008
+ const fieldChanges = nodeChangeFromId(
5009
+ change.nodeChanges,
5010
+ change.nodeAliases,
5011
+ nodeId,
5012
+ ).fieldChanges;
5013
+
5014
+ if (fieldChanges !== undefined) {
5015
+ validateFieldChanges(
5016
+ fieldKinds,
5017
+ change,
5018
+ fieldChanges,
5019
+ normalizedNodeId,
5020
+ unreachableNodes,
5021
+ unreachableCFKs,
5022
+ );
5023
+ }
5024
+ }
5025
+
5026
+ if (!containsRollbacks(change)) {
5027
+ for (const entry of change.crossFieldKeys.entries()) {
5028
+ if (entry.start.target !== NodeMoveType.Attach) {
5029
+ continue;
5030
+ }
5031
+
5032
+ validateAttach(change, entry.start, entry.length);
5033
+ }
5034
+ }
5035
+
5036
+ assert(unreachableNodes.size === 0, "Unreachable nodes found");
5037
+ assert(unreachableCFKs.entries().length === 0, "Unreachable cross-field keys found");
5038
+ }
5039
+
5040
+ function containsRollbacks(change: ModularChangeset): boolean {
5041
+ if (change.revisions === undefined) {
5042
+ return false;
5043
+ }
5044
+
5045
+ for (const revInfo of change.revisions) {
5046
+ if (revInfo.rollbackOf !== undefined) {
5047
+ return true;
5048
+ }
5049
+ }
5050
+ return false;
5051
+ }
5052
+
5053
+ function validateAttach(
5054
+ changeset: ModularChangeset,
5055
+ attachId: ChangeAtomId,
5056
+ count: number,
5057
+ ): void {
5058
+ let countProcessed = count;
5059
+ const buildEntry = hasBuildForIdRange(changeset.builds, attachId, count);
5060
+ countProcessed = buildEntry.length;
5061
+
5062
+ const detachEntry = changeset.crossFieldKeys.getFirst(
5063
+ { ...attachId, target: NodeMoveType.Detach },
5064
+ countProcessed,
5065
+ );
5066
+ countProcessed = detachEntry.length;
5067
+
5068
+ const renameEntry = changeset.rootNodes.newToOldId.getFirst(attachId, countProcessed);
5069
+ countProcessed = renameEntry.length;
5070
+
5071
+ // assert(
5072
+ // buildEntry.value || detachEntry.value !== undefined || renameEntry.value !== undefined,
5073
+ // "No build, detach, or rename found for attach",
5074
+ // );
5075
+
5076
+ if (countProcessed < count) {
5077
+ validateAttach(
5078
+ changeset,
5079
+ offsetChangeAtomId(attachId, countProcessed),
5080
+ count - countProcessed,
5081
+ );
5082
+ }
5083
+ }
5084
+
5085
+ function hasBuildForIdRange(
5086
+ builds: ChangeAtomIdBTree<TreeChunk> | undefined,
5087
+ id: ChangeAtomId,
5088
+ count: number,
5089
+ ): RangeQueryResult<boolean> {
5090
+ if (builds === undefined) {
5091
+ return { value: false, length: count };
5092
+ }
5093
+
5094
+ const prevBuildEntry = builds.nextLowerPair([id.revision, id.localId]);
5095
+
5096
+ if (prevBuildEntry !== undefined) {
5097
+ const prevBuildKey: ChangeAtomId = {
5098
+ revision: prevBuildEntry[0][0],
5099
+ localId: prevBuildEntry[0][1],
5100
+ };
5101
+
5102
+ const prevBuildLength = prevBuildEntry[1].topLevelLength;
5103
+ const lastLocalId = prevBuildKey.localId + prevBuildLength - 1;
5104
+ if (prevBuildKey.revision === id.revision && lastLocalId >= id.localId) {
5105
+ return { value: true, length: Math.min(count, lastLocalId - id.localId + 1) };
5106
+ }
5107
+ }
5108
+
5109
+ const buildEntry = rangeQueryChangeAtomIdMap(builds, id, count);
5110
+ const length =
5111
+ buildEntry.value === undefined ? buildEntry.length : buildEntry.value.topLevelLength;
5112
+
5113
+ const hasBuild = buildEntry.value !== undefined;
5114
+ return { value: hasBuild, length };
5115
+ }
5116
+
5117
+ /**
5118
+ * Asserts that each node has a correct entry in `change.nodeToParent`,
5119
+ * and each cross field key has a correct entry in `change.crossFieldKeys`.
5120
+ * @returns the number of children found.
5121
+ */
5122
+ function validateFieldChanges(
5123
+ fieldKinds: ReadonlyMap<FieldKindIdentifier, FlexFieldKind>,
5124
+ change: ModularChangeset,
5125
+ fieldChanges: FieldChangeMap,
5126
+ nodeParent: NodeId | undefined,
5127
+ unreachableNodes: ChangeAtomIdBTree<NodeLocation>,
5128
+ unreachableCFKs: CrossFieldRangeTable<FieldId>,
5129
+ ): void {
5130
+ for (const [field, fieldChange] of fieldChanges.entries()) {
5131
+ const fieldId = { nodeId: nodeParent, field };
5132
+ const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
5133
+ for (const [child, _index] of handler.getNestedChanges(fieldChange.change)) {
5134
+ const parentFieldId = getNodeParent(change, child);
5135
+ assert(
5136
+ parentFieldId.field !== undefined && areEqualFieldIds(parentFieldId.field, fieldId),
5137
+ 0xa4e /* Inconsistent node parentage */,
5138
+ );
5139
+
5140
+ unreachableNodes.delete([child.revision, child.localId]);
5141
+ }
5142
+
5143
+ for (const keyRange of handler.getCrossFieldKeys(fieldChange.change)) {
5144
+ const fields = getFieldsForCrossFieldKey(change, keyRange.key, keyRange.count);
5145
+ assert(fields.length > 0, "Unregistered cross-field key");
5146
+ for (const fieldFromLookup of fields) {
5147
+ assert(
5148
+ areEqualFieldIds(fieldFromLookup, fieldId),
5149
+ 0xa4f /* Inconsistent cross field keys */,
5150
+ );
5151
+ }
5152
+
5153
+ unreachableCFKs.delete(keyRange.key, keyRange.count);
5154
+ }
5155
+ }
5156
+ }
5157
+
5158
+ export function newFieldIdKeyBTree<V>(): TupleBTree<FieldIdKey, V> {
3213
5159
  return newTupleBTree(compareFieldIdKeys);
3214
5160
  }
3215
5161