@fluidframework/tree 2.80.0 → 2.81.0-374083

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