@fluidframework/tree 2.1.0-276985 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (583) hide show
  1. package/.eslintrc.cjs +7 -0
  2. package/.vscode/Tree.code-workspace +9 -2
  3. package/CHANGELOG.md +38 -0
  4. package/README.md +55 -12
  5. package/api-report/tree.alpha.api.md +2 -1
  6. package/api-report/tree.beta.api.md +2 -1
  7. package/api-report/tree.public.api.md +2 -1
  8. package/beta.d.ts +1 -1
  9. package/dist/beta.d.ts +1 -1
  10. package/dist/core/forest/editableForest.d.ts +6 -3
  11. package/dist/core/forest/editableForest.d.ts.map +1 -1
  12. package/dist/core/forest/editableForest.js +16 -4
  13. package/dist/core/forest/editableForest.js.map +1 -1
  14. package/dist/core/index.d.ts +1 -1
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/index.js +3 -1
  17. package/dist/core/index.js.map +1 -1
  18. package/dist/core/rebase/index.d.ts +1 -1
  19. package/dist/core/rebase/index.d.ts.map +1 -1
  20. package/dist/core/rebase/index.js +3 -1
  21. package/dist/core/rebase/index.js.map +1 -1
  22. package/dist/core/rebase/types.d.ts +2 -0
  23. package/dist/core/rebase/types.d.ts.map +1 -1
  24. package/dist/core/rebase/types.js +9 -1
  25. package/dist/core/rebase/types.js.map +1 -1
  26. package/dist/core/tree/anchorSet.d.ts +1 -0
  27. package/dist/core/tree/anchorSet.d.ts.map +1 -1
  28. package/dist/core/tree/anchorSet.js +13 -0
  29. package/dist/core/tree/anchorSet.js.map +1 -1
  30. package/dist/core/tree/detachedFieldIndex.d.ts +48 -11
  31. package/dist/core/tree/detachedFieldIndex.d.ts.map +1 -1
  32. package/dist/core/tree/detachedFieldIndex.js +144 -20
  33. package/dist/core/tree/detachedFieldIndex.js.map +1 -1
  34. package/dist/core/tree/detachedFieldIndexCodec.d.ts.map +1 -1
  35. package/dist/core/tree/detachedFieldIndexCodec.js +13 -4
  36. package/dist/core/tree/detachedFieldIndexCodec.js.map +1 -1
  37. package/dist/core/tree/detachedFieldIndexFormat.d.ts +1 -1
  38. package/dist/core/tree/detachedFieldIndexFormat.d.ts.map +1 -1
  39. package/dist/core/tree/detachedFieldIndexFormat.js.map +1 -1
  40. package/dist/core/tree/detachedFieldIndexTypes.d.ts +39 -4
  41. package/dist/core/tree/detachedFieldIndexTypes.d.ts.map +1 -1
  42. package/dist/core/tree/detachedFieldIndexTypes.js.map +1 -1
  43. package/dist/core/tree/index.d.ts +2 -1
  44. package/dist/core/tree/index.d.ts.map +1 -1
  45. package/dist/core/tree/index.js.map +1 -1
  46. package/dist/core/tree/visitDelta.d.ts +3 -1
  47. package/dist/core/tree/visitDelta.d.ts.map +1 -1
  48. package/dist/core/tree/visitDelta.js +31 -15
  49. package/dist/core/tree/visitDelta.js.map +1 -1
  50. package/dist/core/tree/visitorUtils.d.ts +3 -3
  51. package/dist/core/tree/visitorUtils.d.ts.map +1 -1
  52. package/dist/core/tree/visitorUtils.js +4 -4
  53. package/dist/core/tree/visitorUtils.js.map +1 -1
  54. package/dist/events/events.d.ts +4 -1
  55. package/dist/events/events.d.ts.map +1 -1
  56. package/dist/events/events.js.map +1 -1
  57. package/dist/feature-libraries/default-schema/defaultEditBuilder.js +1 -1
  58. package/dist/feature-libraries/default-schema/defaultEditBuilder.js.map +1 -1
  59. package/dist/feature-libraries/default-schema/defaultFieldKinds.d.ts.map +1 -1
  60. package/dist/feature-libraries/default-schema/defaultFieldKinds.js +1 -0
  61. package/dist/feature-libraries/default-schema/defaultFieldKinds.js.map +1 -1
  62. package/dist/feature-libraries/editableTreeBinder.js +1 -1
  63. package/dist/feature-libraries/editableTreeBinder.js.map +1 -1
  64. package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts +1 -10
  65. package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
  66. package/dist/feature-libraries/flex-map-tree/mapTreeNode.js +0 -72
  67. package/dist/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
  68. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +1 -51
  69. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  70. package/dist/feature-libraries/flex-tree/flexTreeTypes.js +0 -2
  71. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  72. package/dist/feature-libraries/flex-tree/index.d.ts +3 -2
  73. package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
  74. package/dist/feature-libraries/flex-tree/index.js +5 -1
  75. package/dist/feature-libraries/flex-tree/index.js.map +1 -1
  76. package/dist/feature-libraries/flex-tree/lazyEntity.d.ts +1 -2
  77. package/dist/feature-libraries/flex-tree/lazyEntity.d.ts.map +1 -1
  78. package/dist/feature-libraries/flex-tree/lazyEntity.js.map +1 -1
  79. package/dist/feature-libraries/flex-tree/lazyField.d.ts +1 -6
  80. package/dist/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  81. package/dist/feature-libraries/flex-tree/lazyField.js +11 -32
  82. package/dist/feature-libraries/flex-tree/lazyField.js.map +1 -1
  83. package/dist/feature-libraries/flex-tree/lazyNode.d.ts +1 -5
  84. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  85. package/dist/feature-libraries/flex-tree/lazyNode.js +0 -30
  86. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  87. package/dist/feature-libraries/forest-summary/forestSummarizer.d.ts.map +1 -1
  88. package/dist/feature-libraries/forest-summary/forestSummarizer.js +1 -1
  89. package/dist/feature-libraries/forest-summary/forestSummarizer.js.map +1 -1
  90. package/dist/feature-libraries/index.d.ts +3 -3
  91. package/dist/feature-libraries/index.d.ts.map +1 -1
  92. package/dist/feature-libraries/index.js +6 -3
  93. package/dist/feature-libraries/index.js.map +1 -1
  94. package/dist/feature-libraries/modular-schema/crossFieldQueries.d.ts +11 -0
  95. package/dist/feature-libraries/modular-schema/crossFieldQueries.d.ts.map +1 -1
  96. package/dist/feature-libraries/modular-schema/crossFieldQueries.js.map +1 -1
  97. package/dist/feature-libraries/modular-schema/discrepancies.d.ts +96 -0
  98. package/dist/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -0
  99. package/dist/feature-libraries/modular-schema/discrepancies.js +264 -0
  100. package/dist/feature-libraries/modular-schema/discrepancies.js.map +1 -0
  101. package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts +9 -2
  102. package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
  103. package/dist/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
  104. package/dist/feature-libraries/modular-schema/genericFieldKind.d.ts.map +1 -1
  105. package/dist/feature-libraries/modular-schema/genericFieldKind.js +3 -0
  106. package/dist/feature-libraries/modular-schema/genericFieldKind.js.map +1 -1
  107. package/dist/feature-libraries/modular-schema/index.d.ts +2 -1
  108. package/dist/feature-libraries/modular-schema/index.d.ts.map +1 -1
  109. package/dist/feature-libraries/modular-schema/index.js +3 -1
  110. package/dist/feature-libraries/modular-schema/index.js.map +1 -1
  111. package/dist/feature-libraries/modular-schema/modularChangeCodecs.d.ts.map +1 -1
  112. package/dist/feature-libraries/modular-schema/modularChangeCodecs.js +42 -26
  113. package/dist/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
  114. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts +51 -2
  115. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  116. package/dist/feature-libraries/modular-schema/modularChangeFamily.js +827 -245
  117. package/dist/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  118. package/dist/feature-libraries/modular-schema/modularChangeFormat.d.ts.map +1 -1
  119. package/dist/feature-libraries/modular-schema/modularChangeFormat.js +2 -0
  120. package/dist/feature-libraries/modular-schema/modularChangeFormat.js.map +1 -1
  121. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts +44 -1
  122. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  123. package/dist/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  124. package/dist/feature-libraries/node-key/index.d.ts +0 -1
  125. package/dist/feature-libraries/node-key/index.d.ts.map +1 -1
  126. package/dist/feature-libraries/node-key/index.js +1 -3
  127. package/dist/feature-libraries/node-key/index.js.map +1 -1
  128. package/dist/feature-libraries/object-forest/objectForest.d.ts +3 -2
  129. package/dist/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
  130. package/dist/feature-libraries/object-forest/objectForest.js +5 -4
  131. package/dist/feature-libraries/object-forest/objectForest.js.map +1 -1
  132. package/dist/feature-libraries/optional-field/optionalField.d.ts.map +1 -1
  133. package/dist/feature-libraries/optional-field/optionalField.js +1 -0
  134. package/dist/feature-libraries/optional-field/optionalField.js.map +1 -1
  135. package/dist/feature-libraries/sequence-field/index.d.ts +1 -1
  136. package/dist/feature-libraries/sequence-field/index.d.ts.map +1 -1
  137. package/dist/feature-libraries/sequence-field/index.js +1 -2
  138. package/dist/feature-libraries/sequence-field/index.js.map +1 -1
  139. package/dist/feature-libraries/sequence-field/invert.js +1 -1
  140. package/dist/feature-libraries/sequence-field/invert.js.map +1 -1
  141. package/dist/feature-libraries/sequence-field/rebase.js +6 -1
  142. package/dist/feature-libraries/sequence-field/rebase.js.map +1 -1
  143. package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.d.ts.map +1 -1
  144. package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.js +1 -0
  145. package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.js.map +1 -1
  146. package/dist/feature-libraries/sequence-field/utils.d.ts +2 -17
  147. package/dist/feature-libraries/sequence-field/utils.d.ts.map +1 -1
  148. package/dist/feature-libraries/sequence-field/utils.js +31 -39
  149. package/dist/feature-libraries/sequence-field/utils.js.map +1 -1
  150. package/dist/feature-libraries/typed-schema/typedTreeSchema.d.ts +1 -0
  151. package/dist/feature-libraries/typed-schema/typedTreeSchema.d.ts.map +1 -1
  152. package/dist/feature-libraries/typed-schema/typedTreeSchema.js +2 -0
  153. package/dist/feature-libraries/typed-schema/typedTreeSchema.js.map +1 -1
  154. package/dist/index.d.ts +1 -1
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js.map +1 -1
  157. package/dist/packageVersion.d.ts +1 -1
  158. package/dist/packageVersion.d.ts.map +1 -1
  159. package/dist/packageVersion.js +1 -1
  160. package/dist/packageVersion.js.map +1 -1
  161. package/dist/public.d.ts +1 -1
  162. package/dist/shared-tree/schematizingTreeView.d.ts +4 -2
  163. package/dist/shared-tree/schematizingTreeView.d.ts.map +1 -1
  164. package/dist/shared-tree/schematizingTreeView.js +240 -184
  165. package/dist/shared-tree/schematizingTreeView.js.map +1 -1
  166. package/dist/shared-tree/sharedTree.d.ts +5 -1
  167. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  168. package/dist/shared-tree/sharedTree.js +157 -90
  169. package/dist/shared-tree/sharedTree.js.map +1 -1
  170. package/dist/shared-tree/sharedTreeChangeEnricher.js +1 -1
  171. package/dist/shared-tree/sharedTreeChangeEnricher.js.map +1 -1
  172. package/dist/shared-tree/treeApi.js +1 -1
  173. package/dist/shared-tree/treeApi.js.map +1 -1
  174. package/dist/shared-tree/treeCheckout.d.ts +10 -1
  175. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  176. package/dist/shared-tree/treeCheckout.js +47 -3
  177. package/dist/shared-tree/treeCheckout.js.map +1 -1
  178. package/dist/shared-tree/treeView.d.ts.map +1 -1
  179. package/dist/shared-tree/treeView.js +7 -3
  180. package/dist/shared-tree/treeView.js.map +1 -1
  181. package/dist/shared-tree-core/branch.d.ts +6 -0
  182. package/dist/shared-tree-core/branch.d.ts.map +1 -1
  183. package/dist/shared-tree-core/branch.js +3 -0
  184. package/dist/shared-tree-core/branch.js.map +1 -1
  185. package/dist/shared-tree-core/sharedTreeCore.d.ts +8 -6
  186. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  187. package/dist/shared-tree-core/sharedTreeCore.js +271 -209
  188. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  189. package/dist/simple-tree/arrayNode.d.ts +4 -0
  190. package/dist/simple-tree/arrayNode.d.ts.map +1 -1
  191. package/dist/simple-tree/arrayNode.js +36 -19
  192. package/dist/simple-tree/arrayNode.js.map +1 -1
  193. package/dist/simple-tree/index.d.ts +3 -3
  194. package/dist/simple-tree/index.d.ts.map +1 -1
  195. package/dist/simple-tree/index.js +2 -1
  196. package/dist/simple-tree/index.js.map +1 -1
  197. package/dist/simple-tree/leafNodeSchema.d.ts +22 -1
  198. package/dist/simple-tree/leafNodeSchema.d.ts.map +1 -1
  199. package/dist/simple-tree/leafNodeSchema.js +2 -1
  200. package/dist/simple-tree/leafNodeSchema.js.map +1 -1
  201. package/dist/simple-tree/mapNode.d.ts.map +1 -1
  202. package/dist/simple-tree/mapNode.js.map +1 -1
  203. package/dist/simple-tree/objectNode.d.ts.map +1 -1
  204. package/dist/simple-tree/objectNode.js +2 -1
  205. package/dist/simple-tree/objectNode.js.map +1 -1
  206. package/dist/simple-tree/proxies.d.ts.map +1 -1
  207. package/dist/simple-tree/proxies.js +9 -25
  208. package/dist/simple-tree/proxies.js.map +1 -1
  209. package/dist/simple-tree/proxyBinding.d.ts +4 -0
  210. package/dist/simple-tree/proxyBinding.d.ts.map +1 -1
  211. package/dist/simple-tree/proxyBinding.js +23 -1
  212. package/dist/simple-tree/proxyBinding.js.map +1 -1
  213. package/dist/simple-tree/schemaFactory.d.ts +16 -1
  214. package/dist/simple-tree/schemaFactory.d.ts.map +1 -1
  215. package/dist/simple-tree/schemaFactory.js +32 -4
  216. package/dist/simple-tree/schemaFactory.js.map +1 -1
  217. package/dist/simple-tree/schemaTypes.d.ts +36 -1
  218. package/dist/simple-tree/schemaTypes.d.ts.map +1 -1
  219. package/dist/simple-tree/schemaTypes.js.map +1 -1
  220. package/dist/simple-tree/toFlexSchema.d.ts +2 -2
  221. package/dist/simple-tree/toFlexSchema.d.ts.map +1 -1
  222. package/dist/simple-tree/toFlexSchema.js +3 -2
  223. package/dist/simple-tree/toFlexSchema.js.map +1 -1
  224. package/dist/simple-tree/tree.d.ts +4 -1
  225. package/dist/simple-tree/tree.d.ts.map +1 -1
  226. package/dist/simple-tree/tree.js +48 -1
  227. package/dist/simple-tree/tree.js.map +1 -1
  228. package/dist/simple-tree/treeNodeApi.d.ts +2 -75
  229. package/dist/simple-tree/treeNodeApi.d.ts.map +1 -1
  230. package/dist/simple-tree/treeNodeApi.js +17 -25
  231. package/dist/simple-tree/treeNodeApi.js.map +1 -1
  232. package/dist/simple-tree/treeNodeKernel.d.ts +26 -0
  233. package/dist/simple-tree/treeNodeKernel.d.ts.map +1 -0
  234. package/dist/simple-tree/treeNodeKernel.js +83 -0
  235. package/dist/simple-tree/treeNodeKernel.js.map +1 -0
  236. package/dist/simple-tree/types.d.ts +95 -3
  237. package/dist/simple-tree/types.d.ts.map +1 -1
  238. package/dist/simple-tree/types.js +120 -21
  239. package/dist/simple-tree/types.js.map +1 -1
  240. package/dist/util/breakable.d.ts +83 -0
  241. package/dist/util/breakable.d.ts.map +1 -0
  242. package/dist/util/breakable.js +178 -0
  243. package/dist/util/breakable.js.map +1 -0
  244. package/dist/util/index.d.ts +3 -2
  245. package/dist/util/index.d.ts.map +1 -1
  246. package/dist/util/index.js +9 -2
  247. package/dist/util/index.js.map +1 -1
  248. package/dist/util/nestedMap.d.ts +17 -3
  249. package/dist/util/nestedMap.d.ts.map +1 -1
  250. package/dist/util/nestedMap.js +21 -1
  251. package/dist/util/nestedMap.js.map +1 -1
  252. package/dist/util/utils.d.ts +7 -0
  253. package/dist/util/utils.d.ts.map +1 -1
  254. package/dist/util/utils.js +15 -1
  255. package/dist/util/utils.js.map +1 -1
  256. package/internal.d.ts +1 -1
  257. package/lib/beta.d.ts +1 -1
  258. package/lib/core/forest/editableForest.d.ts +6 -3
  259. package/lib/core/forest/editableForest.d.ts.map +1 -1
  260. package/lib/core/forest/editableForest.js +17 -5
  261. package/lib/core/forest/editableForest.js.map +1 -1
  262. package/lib/core/index.d.ts +1 -1
  263. package/lib/core/index.d.ts.map +1 -1
  264. package/lib/core/index.js +1 -1
  265. package/lib/core/index.js.map +1 -1
  266. package/lib/core/rebase/index.d.ts +1 -1
  267. package/lib/core/rebase/index.d.ts.map +1 -1
  268. package/lib/core/rebase/index.js +1 -1
  269. package/lib/core/rebase/index.js.map +1 -1
  270. package/lib/core/rebase/types.d.ts +2 -0
  271. package/lib/core/rebase/types.d.ts.map +1 -1
  272. package/lib/core/rebase/types.js +7 -1
  273. package/lib/core/rebase/types.js.map +1 -1
  274. package/lib/core/tree/anchorSet.d.ts +1 -0
  275. package/lib/core/tree/anchorSet.d.ts.map +1 -1
  276. package/lib/core/tree/anchorSet.js +13 -0
  277. package/lib/core/tree/anchorSet.js.map +1 -1
  278. package/lib/core/tree/detachedFieldIndex.d.ts +48 -11
  279. package/lib/core/tree/detachedFieldIndex.d.ts.map +1 -1
  280. package/lib/core/tree/detachedFieldIndex.js +145 -21
  281. package/lib/core/tree/detachedFieldIndex.js.map +1 -1
  282. package/lib/core/tree/detachedFieldIndexCodec.d.ts.map +1 -1
  283. package/lib/core/tree/detachedFieldIndexCodec.js +13 -4
  284. package/lib/core/tree/detachedFieldIndexCodec.js.map +1 -1
  285. package/lib/core/tree/detachedFieldIndexFormat.d.ts +1 -1
  286. package/lib/core/tree/detachedFieldIndexFormat.d.ts.map +1 -1
  287. package/lib/core/tree/detachedFieldIndexFormat.js.map +1 -1
  288. package/lib/core/tree/detachedFieldIndexTypes.d.ts +39 -4
  289. package/lib/core/tree/detachedFieldIndexTypes.d.ts.map +1 -1
  290. package/lib/core/tree/detachedFieldIndexTypes.js.map +1 -1
  291. package/lib/core/tree/index.d.ts +2 -1
  292. package/lib/core/tree/index.d.ts.map +1 -1
  293. package/lib/core/tree/index.js.map +1 -1
  294. package/lib/core/tree/visitDelta.d.ts +3 -1
  295. package/lib/core/tree/visitDelta.d.ts.map +1 -1
  296. package/lib/core/tree/visitDelta.js +31 -15
  297. package/lib/core/tree/visitDelta.js.map +1 -1
  298. package/lib/core/tree/visitorUtils.d.ts +3 -3
  299. package/lib/core/tree/visitorUtils.d.ts.map +1 -1
  300. package/lib/core/tree/visitorUtils.js +4 -4
  301. package/lib/core/tree/visitorUtils.js.map +1 -1
  302. package/lib/events/events.d.ts +4 -1
  303. package/lib/events/events.d.ts.map +1 -1
  304. package/lib/events/events.js.map +1 -1
  305. package/lib/feature-libraries/default-schema/defaultEditBuilder.js +1 -1
  306. package/lib/feature-libraries/default-schema/defaultEditBuilder.js.map +1 -1
  307. package/lib/feature-libraries/default-schema/defaultFieldKinds.d.ts.map +1 -1
  308. package/lib/feature-libraries/default-schema/defaultFieldKinds.js +1 -0
  309. package/lib/feature-libraries/default-schema/defaultFieldKinds.js.map +1 -1
  310. package/lib/feature-libraries/editableTreeBinder.js +1 -1
  311. package/lib/feature-libraries/editableTreeBinder.js.map +1 -1
  312. package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts +1 -10
  313. package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
  314. package/lib/feature-libraries/flex-map-tree/mapTreeNode.js +2 -74
  315. package/lib/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
  316. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +1 -51
  317. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  318. package/lib/feature-libraries/flex-tree/flexTreeTypes.js +0 -2
  319. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  320. package/lib/feature-libraries/flex-tree/index.d.ts +3 -2
  321. package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
  322. package/lib/feature-libraries/flex-tree/index.js +2 -1
  323. package/lib/feature-libraries/flex-tree/index.js.map +1 -1
  324. package/lib/feature-libraries/flex-tree/lazyEntity.d.ts +1 -2
  325. package/lib/feature-libraries/flex-tree/lazyEntity.d.ts.map +1 -1
  326. package/lib/feature-libraries/flex-tree/lazyEntity.js.map +1 -1
  327. package/lib/feature-libraries/flex-tree/lazyField.d.ts +1 -6
  328. package/lib/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  329. package/lib/feature-libraries/flex-tree/lazyField.js +13 -34
  330. package/lib/feature-libraries/flex-tree/lazyField.js.map +1 -1
  331. package/lib/feature-libraries/flex-tree/lazyNode.d.ts +1 -5
  332. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  333. package/lib/feature-libraries/flex-tree/lazyNode.js +3 -33
  334. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  335. package/lib/feature-libraries/forest-summary/forestSummarizer.d.ts.map +1 -1
  336. package/lib/feature-libraries/forest-summary/forestSummarizer.js +1 -1
  337. package/lib/feature-libraries/forest-summary/forestSummarizer.js.map +1 -1
  338. package/lib/feature-libraries/index.d.ts +3 -3
  339. package/lib/feature-libraries/index.d.ts.map +1 -1
  340. package/lib/feature-libraries/index.js +3 -3
  341. package/lib/feature-libraries/index.js.map +1 -1
  342. package/lib/feature-libraries/modular-schema/crossFieldQueries.d.ts +11 -0
  343. package/lib/feature-libraries/modular-schema/crossFieldQueries.d.ts.map +1 -1
  344. package/lib/feature-libraries/modular-schema/crossFieldQueries.js.map +1 -1
  345. package/lib/feature-libraries/modular-schema/discrepancies.d.ts +96 -0
  346. package/lib/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -0
  347. package/lib/feature-libraries/modular-schema/discrepancies.js +260 -0
  348. package/lib/feature-libraries/modular-schema/discrepancies.js.map +1 -0
  349. package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts +9 -2
  350. package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
  351. package/lib/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
  352. package/lib/feature-libraries/modular-schema/genericFieldKind.d.ts.map +1 -1
  353. package/lib/feature-libraries/modular-schema/genericFieldKind.js +3 -0
  354. package/lib/feature-libraries/modular-schema/genericFieldKind.js.map +1 -1
  355. package/lib/feature-libraries/modular-schema/index.d.ts +2 -1
  356. package/lib/feature-libraries/modular-schema/index.d.ts.map +1 -1
  357. package/lib/feature-libraries/modular-schema/index.js +1 -0
  358. package/lib/feature-libraries/modular-schema/index.js.map +1 -1
  359. package/lib/feature-libraries/modular-schema/modularChangeCodecs.d.ts.map +1 -1
  360. package/lib/feature-libraries/modular-schema/modularChangeCodecs.js +42 -26
  361. package/lib/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
  362. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts +51 -2
  363. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  364. package/lib/feature-libraries/modular-schema/modularChangeFamily.js +826 -247
  365. package/lib/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  366. package/lib/feature-libraries/modular-schema/modularChangeFormat.d.ts.map +1 -1
  367. package/lib/feature-libraries/modular-schema/modularChangeFormat.js +2 -0
  368. package/lib/feature-libraries/modular-schema/modularChangeFormat.js.map +1 -1
  369. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts +44 -1
  370. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  371. package/lib/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  372. package/lib/feature-libraries/node-key/index.d.ts +0 -1
  373. package/lib/feature-libraries/node-key/index.d.ts.map +1 -1
  374. package/lib/feature-libraries/node-key/index.js +0 -1
  375. package/lib/feature-libraries/node-key/index.js.map +1 -1
  376. package/lib/feature-libraries/object-forest/objectForest.d.ts +3 -2
  377. package/lib/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
  378. package/lib/feature-libraries/object-forest/objectForest.js +5 -4
  379. package/lib/feature-libraries/object-forest/objectForest.js.map +1 -1
  380. package/lib/feature-libraries/optional-field/optionalField.d.ts.map +1 -1
  381. package/lib/feature-libraries/optional-field/optionalField.js +1 -0
  382. package/lib/feature-libraries/optional-field/optionalField.js.map +1 -1
  383. package/lib/feature-libraries/sequence-field/index.d.ts +1 -1
  384. package/lib/feature-libraries/sequence-field/index.d.ts.map +1 -1
  385. package/lib/feature-libraries/sequence-field/index.js +1 -1
  386. package/lib/feature-libraries/sequence-field/index.js.map +1 -1
  387. package/lib/feature-libraries/sequence-field/invert.js +1 -1
  388. package/lib/feature-libraries/sequence-field/invert.js.map +1 -1
  389. package/lib/feature-libraries/sequence-field/rebase.js +6 -1
  390. package/lib/feature-libraries/sequence-field/rebase.js.map +1 -1
  391. package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.d.ts.map +1 -1
  392. package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.js +2 -1
  393. package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.js.map +1 -1
  394. package/lib/feature-libraries/sequence-field/utils.d.ts +2 -17
  395. package/lib/feature-libraries/sequence-field/utils.d.ts.map +1 -1
  396. package/lib/feature-libraries/sequence-field/utils.js +31 -39
  397. package/lib/feature-libraries/sequence-field/utils.js.map +1 -1
  398. package/lib/feature-libraries/typed-schema/typedTreeSchema.d.ts +1 -0
  399. package/lib/feature-libraries/typed-schema/typedTreeSchema.d.ts.map +1 -1
  400. package/lib/feature-libraries/typed-schema/typedTreeSchema.js +4 -2
  401. package/lib/feature-libraries/typed-schema/typedTreeSchema.js.map +1 -1
  402. package/lib/index.d.ts +1 -1
  403. package/lib/index.d.ts.map +1 -1
  404. package/lib/index.js.map +1 -1
  405. package/lib/packageVersion.d.ts +1 -1
  406. package/lib/packageVersion.d.ts.map +1 -1
  407. package/lib/packageVersion.js +1 -1
  408. package/lib/packageVersion.js.map +1 -1
  409. package/lib/public.d.ts +1 -1
  410. package/lib/shared-tree/schematizingTreeView.d.ts +4 -2
  411. package/lib/shared-tree/schematizingTreeView.d.ts.map +1 -1
  412. package/lib/shared-tree/schematizingTreeView.js +242 -185
  413. package/lib/shared-tree/schematizingTreeView.js.map +1 -1
  414. package/lib/shared-tree/sharedTree.d.ts +5 -1
  415. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  416. package/lib/shared-tree/sharedTree.js +158 -90
  417. package/lib/shared-tree/sharedTree.js.map +1 -1
  418. package/lib/shared-tree/sharedTreeChangeEnricher.js +1 -1
  419. package/lib/shared-tree/sharedTreeChangeEnricher.js.map +1 -1
  420. package/lib/shared-tree/treeApi.js +1 -1
  421. package/lib/shared-tree/treeApi.js.map +1 -1
  422. package/lib/shared-tree/treeCheckout.d.ts +10 -1
  423. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  424. package/lib/shared-tree/treeCheckout.js +47 -3
  425. package/lib/shared-tree/treeCheckout.js.map +1 -1
  426. package/lib/shared-tree/treeView.d.ts.map +1 -1
  427. package/lib/shared-tree/treeView.js +4 -0
  428. package/lib/shared-tree/treeView.js.map +1 -1
  429. package/lib/shared-tree-core/branch.d.ts +6 -0
  430. package/lib/shared-tree-core/branch.d.ts.map +1 -1
  431. package/lib/shared-tree-core/branch.js +3 -0
  432. package/lib/shared-tree-core/branch.js.map +1 -1
  433. package/lib/shared-tree-core/sharedTreeCore.d.ts +8 -6
  434. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  435. package/lib/shared-tree-core/sharedTreeCore.js +273 -210
  436. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  437. package/lib/simple-tree/arrayNode.d.ts +4 -0
  438. package/lib/simple-tree/arrayNode.d.ts.map +1 -1
  439. package/lib/simple-tree/arrayNode.js +39 -22
  440. package/lib/simple-tree/arrayNode.js.map +1 -1
  441. package/lib/simple-tree/index.d.ts +3 -3
  442. package/lib/simple-tree/index.d.ts.map +1 -1
  443. package/lib/simple-tree/index.js +1 -1
  444. package/lib/simple-tree/index.js.map +1 -1
  445. package/lib/simple-tree/leafNodeSchema.d.ts +22 -1
  446. package/lib/simple-tree/leafNodeSchema.d.ts.map +1 -1
  447. package/lib/simple-tree/leafNodeSchema.js +1 -1
  448. package/lib/simple-tree/leafNodeSchema.js.map +1 -1
  449. package/lib/simple-tree/mapNode.d.ts.map +1 -1
  450. package/lib/simple-tree/mapNode.js.map +1 -1
  451. package/lib/simple-tree/objectNode.d.ts.map +1 -1
  452. package/lib/simple-tree/objectNode.js +3 -2
  453. package/lib/simple-tree/objectNode.js.map +1 -1
  454. package/lib/simple-tree/proxies.d.ts.map +1 -1
  455. package/lib/simple-tree/proxies.js +9 -25
  456. package/lib/simple-tree/proxies.js.map +1 -1
  457. package/lib/simple-tree/proxyBinding.d.ts +4 -0
  458. package/lib/simple-tree/proxyBinding.d.ts.map +1 -1
  459. package/lib/simple-tree/proxyBinding.js +19 -0
  460. package/lib/simple-tree/proxyBinding.js.map +1 -1
  461. package/lib/simple-tree/schemaFactory.d.ts +16 -1
  462. package/lib/simple-tree/schemaFactory.d.ts.map +1 -1
  463. package/lib/simple-tree/schemaFactory.js +30 -3
  464. package/lib/simple-tree/schemaFactory.js.map +1 -1
  465. package/lib/simple-tree/schemaTypes.d.ts +36 -1
  466. package/lib/simple-tree/schemaTypes.d.ts.map +1 -1
  467. package/lib/simple-tree/schemaTypes.js.map +1 -1
  468. package/lib/simple-tree/toFlexSchema.d.ts +2 -2
  469. package/lib/simple-tree/toFlexSchema.d.ts.map +1 -1
  470. package/lib/simple-tree/toFlexSchema.js +3 -2
  471. package/lib/simple-tree/toFlexSchema.js.map +1 -1
  472. package/lib/simple-tree/tree.d.ts +4 -1
  473. package/lib/simple-tree/tree.d.ts.map +1 -1
  474. package/lib/simple-tree/tree.js +44 -0
  475. package/lib/simple-tree/tree.js.map +1 -1
  476. package/lib/simple-tree/treeNodeApi.d.ts +2 -75
  477. package/lib/simple-tree/treeNodeApi.d.ts.map +1 -1
  478. package/lib/simple-tree/treeNodeApi.js +20 -28
  479. package/lib/simple-tree/treeNodeApi.js.map +1 -1
  480. package/lib/simple-tree/treeNodeKernel.d.ts +26 -0
  481. package/lib/simple-tree/treeNodeKernel.d.ts.map +1 -0
  482. package/lib/simple-tree/treeNodeKernel.js +79 -0
  483. package/lib/simple-tree/treeNodeKernel.js.map +1 -0
  484. package/lib/simple-tree/types.d.ts +95 -3
  485. package/lib/simple-tree/types.d.ts.map +1 -1
  486. package/lib/simple-tree/types.js +121 -22
  487. package/lib/simple-tree/types.js.map +1 -1
  488. package/lib/util/breakable.d.ts +83 -0
  489. package/lib/util/breakable.d.ts.map +1 -0
  490. package/lib/util/breakable.js +171 -0
  491. package/lib/util/breakable.js.map +1 -0
  492. package/lib/util/index.d.ts +3 -2
  493. package/lib/util/index.d.ts.map +1 -1
  494. package/lib/util/index.js +3 -2
  495. package/lib/util/index.js.map +1 -1
  496. package/lib/util/nestedMap.d.ts +17 -3
  497. package/lib/util/nestedMap.d.ts.map +1 -1
  498. package/lib/util/nestedMap.js +19 -0
  499. package/lib/util/nestedMap.js.map +1 -1
  500. package/lib/util/utils.d.ts +7 -0
  501. package/lib/util/utils.d.ts.map +1 -1
  502. package/lib/util/utils.js +13 -0
  503. package/lib/util/utils.js.map +1 -1
  504. package/package.json +29 -27
  505. package/src/core/forest/editableForest.ts +25 -4
  506. package/src/core/index.ts +2 -0
  507. package/src/core/rebase/index.ts +2 -0
  508. package/src/core/rebase/types.ts +17 -0
  509. package/src/core/tree/anchorSet.ts +14 -0
  510. package/src/core/tree/detachedFieldIndex.ts +217 -35
  511. package/src/core/tree/detachedFieldIndexCodec.ts +17 -8
  512. package/src/core/tree/detachedFieldIndexFormat.ts +1 -1
  513. package/src/core/tree/detachedFieldIndexTypes.ts +41 -5
  514. package/src/core/tree/index.ts +2 -1
  515. package/src/core/tree/visitDelta.ts +58 -16
  516. package/src/core/tree/visitorUtils.ts +7 -4
  517. package/src/events/events.ts +4 -2
  518. package/src/feature-libraries/default-schema/defaultEditBuilder.ts +1 -1
  519. package/src/feature-libraries/default-schema/defaultFieldKinds.ts +1 -0
  520. package/src/feature-libraries/editableTreeBinder.ts +1 -1
  521. package/src/feature-libraries/flex-map-tree/mapTreeNode.ts +1 -95
  522. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +0 -62
  523. package/src/feature-libraries/flex-tree/index.ts +7 -2
  524. package/src/feature-libraries/flex-tree/lazyEntity.ts +0 -3
  525. package/src/feature-libraries/flex-tree/lazyField.ts +15 -47
  526. package/src/feature-libraries/flex-tree/lazyNode.ts +1 -48
  527. package/src/feature-libraries/forest-summary/forestSummarizer.ts +1 -0
  528. package/src/feature-libraries/index.ts +4 -2
  529. package/src/feature-libraries/modular-schema/crossFieldQueries.ts +18 -0
  530. package/src/feature-libraries/modular-schema/discrepancies.ts +395 -0
  531. package/src/feature-libraries/modular-schema/fieldChangeHandler.ts +10 -2
  532. package/src/feature-libraries/modular-schema/genericFieldKind.ts +3 -0
  533. package/src/feature-libraries/modular-schema/index.ts +2 -0
  534. package/src/feature-libraries/modular-schema/modularChangeCodecs.ts +81 -35
  535. package/src/feature-libraries/modular-schema/modularChangeFamily.ts +1521 -444
  536. package/src/feature-libraries/modular-schema/modularChangeFormat.ts +2 -0
  537. package/src/feature-libraries/modular-schema/modularChangeTypes.ts +51 -0
  538. package/src/feature-libraries/node-key/index.ts +0 -1
  539. package/src/feature-libraries/object-forest/objectForest.ts +7 -3
  540. package/src/feature-libraries/optional-field/optionalField.ts +1 -0
  541. package/src/feature-libraries/sequence-field/index.ts +0 -2
  542. package/src/feature-libraries/sequence-field/invert.ts +1 -1
  543. package/src/feature-libraries/sequence-field/rebase.ts +7 -1
  544. package/src/feature-libraries/sequence-field/sequenceFieldChangeHandler.ts +2 -1
  545. package/src/feature-libraries/sequence-field/utils.ts +37 -85
  546. package/src/feature-libraries/typed-schema/typedTreeSchema.ts +10 -0
  547. package/src/index.ts +0 -1
  548. package/src/packageVersion.ts +1 -1
  549. package/src/shared-tree/schematizingTreeView.ts +6 -2
  550. package/src/shared-tree/sharedTree.ts +12 -1
  551. package/src/shared-tree/sharedTreeChangeEnricher.ts +1 -1
  552. package/src/shared-tree/treeApi.ts +1 -1
  553. package/src/shared-tree/treeCheckout.ts +60 -5
  554. package/src/shared-tree/treeView.ts +5 -0
  555. package/src/shared-tree-core/branch.ts +10 -0
  556. package/src/shared-tree-core/sharedTreeCore.ts +25 -6
  557. package/src/simple-tree/arrayNode.ts +50 -23
  558. package/src/simple-tree/index.ts +3 -3
  559. package/src/simple-tree/leafNodeSchema.ts +1 -1
  560. package/src/simple-tree/mapNode.ts +2 -2
  561. package/src/simple-tree/objectNode.ts +9 -3
  562. package/src/simple-tree/proxies.ts +10 -33
  563. package/src/simple-tree/proxyBinding.ts +23 -0
  564. package/src/simple-tree/schemaFactory.ts +37 -2
  565. package/src/simple-tree/schemaTypes.ts +36 -1
  566. package/src/simple-tree/toFlexSchema.ts +5 -4
  567. package/src/simple-tree/tree.ts +68 -4
  568. package/src/simple-tree/treeNodeApi.ts +29 -111
  569. package/src/simple-tree/treeNodeKernel.ts +91 -0
  570. package/src/simple-tree/types.ts +292 -31
  571. package/src/util/breakable.ts +214 -0
  572. package/src/util/index.ts +11 -0
  573. package/src/util/nestedMap.ts +33 -3
  574. package/src/util/utils.ts +17 -0
  575. package/dist/feature-libraries/node-key/nodeKeyIndex.d.ts +0 -41
  576. package/dist/feature-libraries/node-key/nodeKeyIndex.d.ts.map +0 -1
  577. package/dist/feature-libraries/node-key/nodeKeyIndex.js +0 -101
  578. package/dist/feature-libraries/node-key/nodeKeyIndex.js.map +0 -1
  579. package/lib/feature-libraries/node-key/nodeKeyIndex.d.ts +0 -41
  580. package/lib/feature-libraries/node-key/nodeKeyIndex.d.ts.map +0 -1
  581. package/lib/feature-libraries/node-key/nodeKeyIndex.js +0 -97
  582. package/lib/feature-libraries/node-key/nodeKeyIndex.js.map +0 -1
  583. package/src/feature-libraries/node-key/nodeKeyIndex.ts +0 -132
@@ -35,15 +35,17 @@ import {
35
35
  makeAnonChange,
36
36
  makeDetachedNodeId,
37
37
  mapCursorField,
38
+ replaceAtomRevisions,
38
39
  revisionMetadataSourceFromInfo,
40
+ setInChangeAtomIdMap,
41
+ areEqualChangeAtomIds,
42
+ getFromChangeAtomIdMap,
39
43
  type ChangeAtomId,
40
44
  } from "../../core/index.js";
41
45
  import {
42
46
  type IdAllocationState,
43
47
  type IdAllocator,
44
48
  type Mutable,
45
- type NestedSet,
46
- addToNestedSet,
47
49
  brand,
48
50
  deleteFromNestedMap,
49
51
  fail,
@@ -53,10 +55,11 @@ import {
53
55
  idAllocatorFromState,
54
56
  nestedMapFromFlatList,
55
57
  nestedMapToFlatList,
56
- nestedSetContains,
57
58
  populateNestedMap,
58
59
  setInNestedMap,
59
60
  tryGetFromNestedMap,
61
+ type NestedMap,
62
+ type RangeQueryResult,
60
63
  } from "../../util/index.js";
61
64
  import {
62
65
  type TreeChunk,
@@ -80,19 +83,19 @@ import {
80
83
  type RebaseRevisionMetadata,
81
84
  } from "./fieldChangeHandler.js";
82
85
  import { type FieldKindWithEditor, withEditor } from "./fieldKindWithEditor.js";
83
- import {
84
- convertGenericChange,
85
- genericFieldKind,
86
- newGenericChangeset,
87
- } from "./genericFieldKind.js";
86
+ import { convertGenericChange, genericFieldKind } from "./genericFieldKind.js";
88
87
  import type { GenericChangeset } from "./genericFieldKindTypes.js";
89
88
  import type {
89
+ CrossFieldKeyRange,
90
+ CrossFieldKeyTable,
90
91
  FieldChange,
91
92
  FieldChangeMap,
92
93
  FieldChangeset,
94
+ FieldId,
93
95
  ModularChangeset,
94
96
  NodeChangeset,
95
97
  NodeId,
98
+ TupleBTree,
96
99
  } from "./modularChangeTypes.js";
97
100
 
98
101
  /**
@@ -127,56 +130,59 @@ export class ModularChangeFamily
127
130
  * The returned `FieldChangeset`s may be a shallow copy of the input `FieldChange`s.
128
131
  */
129
132
  private normalizeFieldChanges(
130
- change1: FieldChange | undefined,
131
- change2: FieldChange | undefined,
133
+ change1: FieldChange,
134
+ change2: FieldChange,
132
135
  genId: IdAllocator,
133
136
  revisionMetadata: RevisionMetadataSource,
134
137
  ): {
135
- fieldKind: FieldKindWithEditor;
136
- change1: FieldChangeset | undefined;
137
- change2: FieldChangeset | undefined;
138
+ fieldKind: FieldKindIdentifier;
139
+ changeHandler: FieldChangeHandler<unknown>;
140
+ change1: FieldChangeset;
141
+ change2: FieldChangeset;
138
142
  } {
139
143
  // TODO: Handle the case where changes have conflicting field kinds
140
144
  const kind =
141
- change1 !== undefined && change1.fieldKind !== genericFieldKind.identifier
145
+ change1.fieldKind !== genericFieldKind.identifier
142
146
  ? change1.fieldKind
143
- : change2?.fieldKind ?? genericFieldKind.identifier;
147
+ : change2.fieldKind;
144
148
 
145
149
  if (kind === genericFieldKind.identifier) {
146
- // All the changes are generic
150
+ // Both changes are generic
147
151
  return {
148
- fieldKind: genericFieldKind,
149
- change1: change1?.change,
150
- change2: change2?.change,
152
+ fieldKind: genericFieldKind.identifier,
153
+ changeHandler: genericFieldKind.changeHandler,
154
+ change1: change1.change,
155
+ change2: change2.change,
151
156
  };
152
157
  }
153
158
  const fieldKind = getFieldKind(this.fieldKinds, kind);
154
- const handler = fieldKind.changeHandler;
159
+ const changeHandler = fieldKind.changeHandler;
155
160
  const normalizedChange1 = this.normalizeFieldChange(
156
161
  change1,
157
- handler,
162
+ changeHandler,
158
163
  genId,
159
164
  revisionMetadata,
160
165
  );
161
166
  const normalizedChange2 = this.normalizeFieldChange(
162
167
  change2,
163
- handler,
168
+ changeHandler,
164
169
  genId,
165
170
  revisionMetadata,
166
171
  );
167
- return { fieldKind, change1: normalizedChange1, change2: normalizedChange2 };
172
+ return {
173
+ fieldKind: kind,
174
+ changeHandler,
175
+ change1: normalizedChange1,
176
+ change2: normalizedChange2,
177
+ };
168
178
  }
169
179
 
170
180
  private normalizeFieldChange<T>(
171
- fieldChange: FieldChange | undefined,
181
+ fieldChange: FieldChange,
172
182
  handler: FieldChangeHandler<T>,
173
183
  genId: IdAllocator,
174
184
  revisionMetadata: RevisionMetadataSource,
175
- ): FieldChangeset | undefined {
176
- if (fieldChange === undefined) {
177
- return undefined;
178
- }
179
-
185
+ ): FieldChangeset {
180
186
  if (fieldChange.fieldKind !== genericFieldKind.identifier) {
181
187
  return fieldChange.change;
182
188
  }
@@ -205,10 +211,12 @@ export class ModularChangeFamily
205
211
  const { revInfos, maxId } = getRevInfoFromTaggedChanges(changes);
206
212
  const idState: IdAllocationState = { maxId };
207
213
 
208
- return changes.reduce(
209
- (change1, change2) =>
210
- makeAnonChange(this.composePair(change1, change2, revInfos, idState)),
211
- makeAnonChange({ fieldChanges: new Map(), nodeChanges: new Map() }),
214
+ if (changes.length === 0) {
215
+ return makeModularChangeset();
216
+ }
217
+
218
+ return changes.reduce((change1, change2) =>
219
+ makeAnonChange(this.composePair(change1, change2, revInfos, idState)),
212
220
  ).change;
213
221
  }
214
222
 
@@ -218,106 +226,283 @@ export class ModularChangeFamily
218
226
  revInfos: RevisionInfo[],
219
227
  idState: IdAllocationState,
220
228
  ): ModularChangeset {
229
+ const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys } =
230
+ this.composeAllFields(change1.change, change2.change, revInfos, idState);
231
+
232
+ const { allBuilds, allDestroys, allRefreshers } = composeBuildsDestroysAndRefreshers([
233
+ change1,
234
+ change2,
235
+ ]);
236
+
237
+ return makeModularChangeset(
238
+ this.pruneFieldMap(fieldChanges, nodeChanges),
239
+ nodeChanges,
240
+ nodeToParent,
241
+ nodeAliases,
242
+ crossFieldKeys,
243
+ idState.maxId,
244
+ revInfos,
245
+ undefined,
246
+ allBuilds,
247
+ allDestroys,
248
+ allRefreshers,
249
+ );
250
+ }
251
+
252
+ private composeAllFields(
253
+ change1: ModularChangeset,
254
+ change2: ModularChangeset,
255
+ revInfos: RevisionInfo[],
256
+ idState: IdAllocationState,
257
+ ): ModularChangesetContent {
258
+ if (hasConflicts(change1) && hasConflicts(change2)) {
259
+ return {
260
+ fieldChanges: new Map(),
261
+ nodeChanges: new Map(),
262
+ nodeToParent: new Map(),
263
+ nodeAliases: new Map(),
264
+ crossFieldKeys: newBTree(),
265
+ };
266
+ } else if (hasConflicts(change1)) {
267
+ return change2;
268
+ } else if (hasConflicts(change2)) {
269
+ return change1;
270
+ }
271
+
221
272
  const genId: IdAllocator = idAllocatorFromState(idState);
222
273
  const revisionMetadata: RevisionMetadataSource = revisionMetadataSourceFromInfo(revInfos);
223
274
 
224
- const crossFieldTable = newComposeTable();
275
+ const crossFieldTable = newComposeTable(change1, change2);
276
+
277
+ // We merge nodeChanges, nodeToParent, and nodeAliases from the two changesets.
278
+ // The merged tables will have correct entries for all nodes which are only referenced in one of the input changesets.
279
+ // During composeFieldMaps and processInvalidatedElements we will find all nodes referenced in both input changesets
280
+ // and adjust these tables as necessary.
281
+ // Note that when merging these tables we may encounter key collisions and will arbitrarily drop values in that case.
282
+ // A collision for a node ID means that that node is referenced in both changesets
283
+ // (since we assume that if two changesets use the same node ID they are referring to the same node),
284
+ // therefore all collisions will be addressed when processing the intersection of the changesets.
285
+ const composedNodeChanges: ChangeAtomIdMap<NodeChangeset> = mergeNestedMaps(
286
+ change1.nodeChanges,
287
+ change2.nodeChanges,
288
+ );
289
+
290
+ const composedNodeToParent = mergeNestedMaps(change1.nodeToParent, change2.nodeToParent);
291
+ const composedNodeAliases: ChangeAtomIdMap<NodeId> = mergeNestedMaps(
292
+ change1.nodeAliases,
293
+ change2.nodeAliases,
294
+ );
225
295
 
226
296
  const composedFields = this.composeFieldMaps(
227
- getActiveFieldChanges(change1.change),
228
- getActiveFieldChanges(change2.change),
297
+ change1.fieldChanges,
298
+ change2.fieldChanges,
229
299
  genId,
230
300
  crossFieldTable,
231
301
  revisionMetadata,
232
302
  );
233
303
 
234
- const composedNodeChanges: ChangeAtomIdMap<NodeChangeset> = new Map();
235
- for (const [id1, id2] of crossFieldTable.nodeIdPairs) {
236
- this.composeNodesById(
237
- change1.change.nodeChanges,
238
- change2.change.nodeChanges,
239
- composedNodeChanges,
240
- id1,
241
- id2,
304
+ this.processInvalidatedElements(
305
+ crossFieldTable,
306
+ composedFields,
307
+ composedNodeChanges,
308
+ composedNodeToParent,
309
+ composedNodeAliases,
310
+ genId,
311
+ revisionMetadata,
312
+ );
313
+
314
+ // Currently no field kinds require making changes to cross-field keys during composition, so we can just merge the two tables.
315
+ const composedCrossFieldKeys = mergeBTrees(change1.crossFieldKeys, change2.crossFieldKeys);
316
+ return {
317
+ fieldChanges: composedFields,
318
+ nodeChanges: composedNodeChanges,
319
+ nodeToParent: composedNodeToParent,
320
+ nodeAliases: composedNodeAliases,
321
+ crossFieldKeys: brand(composedCrossFieldKeys),
322
+ };
323
+ }
324
+
325
+ private composeInvalidatedField(
326
+ fieldChange: FieldChange,
327
+ crossFieldTable: ComposeTable,
328
+ genId: IdAllocator,
329
+ revisionMetadata: RevisionMetadataSource,
330
+ ): void {
331
+ const context = crossFieldTable.fieldToContext.get(fieldChange);
332
+ assert(context !== undefined, 0x8cc /* Should have context for every invalidated field */);
333
+ const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
334
+
335
+ const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
336
+ const composeNodes = (child1: NodeId | undefined, child2: NodeId | undefined): NodeId => {
337
+ if (
338
+ child1 !== undefined &&
339
+ child2 !== undefined &&
340
+ getFromChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2) === undefined
341
+ ) {
342
+ setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
343
+ crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
344
+ }
345
+
346
+ return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
347
+ };
348
+
349
+ const amendedChange = rebaser.compose(
350
+ fieldChange1,
351
+ fieldChange2,
352
+ composeNodes,
353
+ genId,
354
+ new ComposeManager(crossFieldTable, fieldChange, false),
355
+ revisionMetadata,
356
+ );
357
+ composedChange.change = brand(amendedChange);
358
+ }
359
+
360
+ /**
361
+ * Updates everything in the composed output which may no longer be valid.
362
+ * This could be due to
363
+ * - discovering that two node changesets refer to the same node (`nodeIdsToCompose`)
364
+ * - a previously composed field being invalidated by a cross field effect (`invalidatedFields`)
365
+ * - a field which was copied directly from an input changeset being invalidated by a cross field effect
366
+ * (`affectedBaseFields` and `affectedNewFields`)
367
+ *
368
+ * Updating an element may invalidate further elements. This function runs until there is no more invalidation.
369
+ */
370
+ private processInvalidatedElements(
371
+ table: ComposeTable,
372
+ composedFields: FieldChangeMap,
373
+ composedNodes: ChangeAtomIdMap<NodeChangeset>,
374
+ composedNodeToParent: ChangeAtomIdMap<FieldId>,
375
+ nodeAliases: ChangeAtomIdMap<NodeId>,
376
+ genId: IdAllocator,
377
+ metadata: RevisionMetadataSource,
378
+ ): void {
379
+ const pending = table.pendingCompositions;
380
+ while (
381
+ table.invalidatedFields.size > 0 ||
382
+ pending.nodeIdsToCompose.length > 0 ||
383
+ pending.affectedBaseFields.length > 0 ||
384
+ pending.affectedNewFields.length > 0
385
+ ) {
386
+ // Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
387
+ for (const [id1, id2] of pending.nodeIdsToCompose) {
388
+ this.composeNodesById(
389
+ table.baseChange.nodeChanges,
390
+ table.newChange.nodeChanges,
391
+ composedNodes,
392
+ composedNodeToParent,
393
+ nodeAliases,
394
+ id1,
395
+ id2,
396
+ genId,
397
+ table,
398
+ metadata,
399
+ );
400
+ }
401
+
402
+ pending.nodeIdsToCompose.length = 0;
403
+
404
+ this.composeAffectedFields(
405
+ table,
406
+ table.baseChange,
407
+ true,
408
+ pending.affectedBaseFields,
409
+ composedFields,
410
+ composedNodes,
242
411
  genId,
243
- crossFieldTable,
244
- revisionMetadata,
412
+ metadata,
413
+ );
414
+
415
+ this.composeAffectedFields(
416
+ table,
417
+ table.newChange,
418
+ false,
419
+ pending.affectedNewFields,
420
+ composedFields,
421
+ composedNodes,
422
+ genId,
423
+ metadata,
245
424
  );
425
+
426
+ this.processInvalidatedCompositions(table, genId, metadata);
246
427
  }
428
+ }
247
429
 
248
- crossFieldTable.nodeIdPairs.length = 0;
430
+ private processInvalidatedCompositions(
431
+ table: ComposeTable,
432
+ genId: IdAllocator,
433
+ metadata: RevisionMetadataSource,
434
+ ): void {
435
+ const fieldsToUpdate = table.invalidatedFields;
436
+ table.invalidatedFields = new Set();
437
+ for (const fieldChange of fieldsToUpdate) {
438
+ this.composeInvalidatedField(fieldChange, table, genId, metadata);
439
+ }
440
+ }
249
441
 
250
- while (crossFieldTable.invalidatedFields.size > 0) {
251
- const fieldsToUpdate = crossFieldTable.invalidatedFields;
252
- crossFieldTable.invalidatedFields = new Set();
253
- for (const fieldChange of fieldsToUpdate) {
254
- const context = crossFieldTable.fieldToContext.get(fieldChange);
255
- assert(
256
- context !== undefined,
257
- 0x8cc /* Should have context for every invalidated field */,
258
- );
259
- const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
260
-
261
- const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
262
- const composeNodes = (
263
- child1: NodeId | undefined,
264
- child2: NodeId | undefined,
265
- ): NodeId => {
266
- if (
267
- child2 !== undefined &&
268
- !nestedSetContains(crossFieldTable.nodeIds, child2.revision, child2.localId)
269
- ) {
270
- crossFieldTable.nodeIdPairs.push([child1, child2]);
271
- if (child1 !== undefined && child2 !== undefined) {
272
- addToNestedSet(crossFieldTable.nodeIds, child2.revision, child2.localId);
273
- }
274
- }
275
- return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
276
- };
442
+ /**
443
+ * Ensures that each field in `affectedFields` has been updated in the composition output.
444
+ * Any field which has already been composed is ignored.
445
+ * All other fields are optimistically assumed to not have any changes in the other input changeset.
446
+ *
447
+ * @param change - The changeset which contains the affected fields.
448
+ * This should be one of the two changesets being composed.
449
+ * @param areBaseFields - Whether the affected fields are part of the base changeset.
450
+ * If not, they are assumed to be part of the new changeset.
451
+ * @param affectedFields - The set of fields to process.
452
+ */
453
+ private composeAffectedFields(
454
+ table: ComposeTable,
455
+ change: ModularChangeset,
456
+ areBaseFields: boolean,
457
+ affectedFields: BTree<FieldIdKey, true>,
458
+ composedFields: FieldChangeMap,
459
+ composedNodes: ChangeAtomIdMap<NodeChangeset>,
460
+ genId: IdAllocator,
461
+ metadata: RevisionMetadataSource,
462
+ ): void {
463
+ for (const fieldIdKey of affectedFields.keys()) {
464
+ const fieldId = normalizeFieldId(fieldIdFromFieldIdKey(fieldIdKey), change.nodeAliases);
465
+ const fieldChange = fieldChangeFromId(change.fieldChanges, change.nodeChanges, fieldId);
466
+
467
+ if (
468
+ table.fieldToContext.has(fieldChange) ||
469
+ table.newFieldToBaseField.has(fieldChange)
470
+ ) {
471
+ // This function handles fields which were not part of the intersection of the two changesets but which need to be updated anyway.
472
+ // If we've already processed this field then either it is up to date
473
+ // or there is pending inval which will be handled in processInvalidatedCompositions.
474
+ continue;
475
+ }
277
476
 
278
- const amendedChange = rebaser.compose(
279
- fieldChange1,
280
- fieldChange2,
281
- composeNodes,
282
- genId,
283
- newCrossFieldManager(crossFieldTable, fieldChange, false),
284
- revisionMetadata,
285
- );
286
- composedChange.change = brand(amendedChange);
287
-
288
- // Process any newly discovered nodes.
289
- for (const [taggedId1, taggedId2] of crossFieldTable.nodeIdPairs) {
290
- this.composeNodesById(
291
- change1.change.nodeChanges,
292
- change2.change.nodeChanges,
293
- composedNodeChanges,
294
- taggedId1,
295
- taggedId2,
296
- genId,
297
- crossFieldTable,
298
- revisionMetadata,
299
- );
300
- }
477
+ const emptyChange = this.createEmptyFieldChange(fieldChange.fieldKind);
478
+ const [change1, change2] = areBaseFields
479
+ ? [fieldChange, emptyChange]
480
+ : [emptyChange, fieldChange];
301
481
 
302
- crossFieldTable.nodeIdPairs.length = 0;
482
+ const composedField = this.composeFieldChanges(change1, change2, genId, table, metadata);
483
+
484
+ if (fieldId.nodeId === undefined) {
485
+ composedFields.set(fieldId.field, composedField);
486
+ continue;
303
487
  }
304
- }
305
488
 
306
- const { allBuilds, allDestroys, allRefreshers } = composeBuildsDestroysAndRefreshers([
307
- change1,
308
- change2,
309
- ]);
489
+ const nodeId =
490
+ getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId;
310
491
 
311
- return makeModularChangeset(
312
- this.pruneFieldMap(composedFields, composedNodeChanges),
313
- composedNodeChanges,
314
- idState.maxId,
315
- revInfos,
316
- undefined,
317
- allBuilds,
318
- allDestroys,
319
- allRefreshers,
320
- );
492
+ let nodeChangeset = nodeChangeFromId(composedNodes, nodeId);
493
+ if (!table.composedNodes.has(nodeChangeset)) {
494
+ nodeChangeset = cloneNodeChangeset(nodeChangeset);
495
+ setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
496
+ }
497
+
498
+ if (nodeChangeset.fieldChanges === undefined) {
499
+ nodeChangeset.fieldChanges = new Map();
500
+ }
501
+
502
+ nodeChangeset.fieldChanges.set(fieldId.field, composedField);
503
+ }
504
+
505
+ affectedFields.clear();
321
506
  }
322
507
 
323
508
  private composeFieldMaps(
@@ -328,89 +513,106 @@ export class ModularChangeFamily
328
513
  revisionMetadata: RevisionMetadataSource,
329
514
  ): FieldChangeMap {
330
515
  const composedFields: FieldChangeMap = new Map();
331
- const fields = new Set<FieldKey>();
332
- for (const field of change1?.keys() ?? []) {
333
- fields.add(field);
516
+ if (change1 === undefined || change2 === undefined) {
517
+ return change1 ?? change2 ?? composedFields;
334
518
  }
335
519
 
336
- for (const field of change2?.keys() ?? []) {
337
- fields.add(field);
338
- }
520
+ for (const [field, fieldChange1] of change1) {
521
+ const fieldChange2 = change2.get(field);
522
+ const composedField =
523
+ fieldChange2 !== undefined
524
+ ? this.composeFieldChanges(
525
+ fieldChange1,
526
+ fieldChange2,
527
+ genId,
528
+ crossFieldTable,
529
+ revisionMetadata,
530
+ )
531
+ : fieldChange1;
339
532
 
340
- for (const field of fields) {
341
- const fieldChange1 = change1?.get(field);
342
- const fieldChange2 = change2?.get(field);
533
+ composedFields.set(field, composedField);
534
+ }
343
535
 
344
- const {
345
- fieldKind,
346
- change1: normalizedFieldChange1,
347
- change2: normalizedFieldChange2,
348
- } = this.normalizeFieldChanges(fieldChange1, fieldChange2, genId, revisionMetadata);
349
-
350
- const manager = newCrossFieldManager(crossFieldTable, fieldChange1 ?? fieldChange2);
351
- const change1Normalized =
352
- normalizedFieldChange1 ?? fieldKind.changeHandler.createEmpty();
353
- const change2Normalized =
354
- normalizedFieldChange2 ?? fieldKind.changeHandler.createEmpty();
355
-
356
- const composedChange = fieldKind.changeHandler.rebaser.compose(
357
- change1Normalized,
358
- change2Normalized,
359
- (child1, child2) => {
360
- crossFieldTable.nodeIdPairs.push([child1, child2]);
361
- if (child2 !== undefined) {
362
- addToNestedSet(crossFieldTable.nodeIds, child2.revision, child2.localId);
363
- }
364
- return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
365
- },
366
- genId,
367
- manager,
368
- revisionMetadata,
369
- );
536
+ for (const [field, fieldChange2] of change2) {
537
+ if (change1 === undefined || !change1.has(field)) {
538
+ composedFields.set(field, fieldChange2);
539
+ }
540
+ }
370
541
 
371
- const composedField: FieldChange = {
372
- fieldKind: fieldKind.identifier,
373
- change: brand(composedChange),
374
- };
542
+ return composedFields;
543
+ }
375
544
 
376
- const fieldKey =
377
- fieldChange1 ?? fieldChange2 ?? fail("At least one field should have changes");
545
+ /**
546
+ * Returns the composition of the two input fields.
547
+ *
548
+ * Any nodes in this field which were modified by both changesets
549
+ * will be added to `crossFieldTable.pendingCompositions.nodeIdsToCompose`.
550
+ *
551
+ * Any fields which had cross-field information sent to them as part of this field composition
552
+ * will be added to either `affectedBaseFields` or `affectedNewFields` in `crossFieldTable.pendingCompositions`.
553
+ *
554
+ * Any composed `FieldChange` which is invalidated by new cross-field information will be added to `crossFieldTable.invalidatedFields`.
555
+ */
556
+ private composeFieldChanges(
557
+ change1: FieldChange,
558
+ change2: FieldChange,
559
+ idAllocator: IdAllocator,
560
+ crossFieldTable: ComposeTable,
561
+ revisionMetadata: RevisionMetadataSource,
562
+ ): FieldChange {
563
+ const {
564
+ fieldKind,
565
+ changeHandler,
566
+ change1: change1Normalized,
567
+ change2: change2Normalized,
568
+ } = this.normalizeFieldChanges(change1, change2, idAllocator, revisionMetadata);
569
+
570
+ const manager = new ComposeManager(crossFieldTable, change1);
571
+
572
+ const composedChange = changeHandler.rebaser.compose(
573
+ change1Normalized,
574
+ change2Normalized,
575
+ (child1, child2) => {
576
+ if (child1 !== undefined && child2 !== undefined) {
577
+ setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
578
+ crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
579
+ }
580
+ return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
581
+ },
582
+ idAllocator,
583
+ manager,
584
+ revisionMetadata,
585
+ );
378
586
 
379
- crossFieldTable.fieldToContext.set(fieldKey, {
380
- change1: change1Normalized,
381
- change2: change2Normalized,
382
- composedChange: composedField,
383
- });
587
+ const composedField: FieldChange = {
588
+ fieldKind,
589
+ change: brand(composedChange),
590
+ };
384
591
 
385
- // TODO: Could optimize by checking that composedField is non-empty
386
- composedFields.set(field, composedField);
387
- }
592
+ crossFieldTable.fieldToContext.set(change1, {
593
+ change1: change1Normalized,
594
+ change2: change2Normalized,
595
+ composedChange: composedField,
596
+ });
388
597
 
389
- return composedFields;
598
+ crossFieldTable.newFieldToBaseField.set(change2, change1);
599
+ return composedField;
390
600
  }
391
601
 
392
602
  private composeNodesById(
393
603
  nodeChanges1: ChangeAtomIdMap<NodeChangeset>,
394
604
  nodeChanges2: ChangeAtomIdMap<NodeChangeset>,
395
- composedNodeChanges: ChangeAtomIdMap<NodeChangeset>,
396
- id1: NodeId | undefined,
397
- id2: NodeId | undefined,
605
+ composedNodes: ChangeAtomIdMap<NodeChangeset>,
606
+ composedNodeToParent: ChangeAtomIdMap<FieldId>,
607
+ nodeAliases: ChangeAtomIdMap<NodeId>,
608
+ id1: NodeId,
609
+ id2: NodeId,
398
610
  idAllocator: IdAllocator,
399
611
  crossFieldTable: ComposeTable,
400
612
  revisionMetadata: RevisionMetadataSource,
401
613
  ): void {
402
- const nodeChangeset1 =
403
- id1 !== undefined
404
- ? tryGetFromNestedMap(nodeChanges1, id1.revision, id1.localId) ??
405
- fail("Unknown node ID")
406
- : {};
407
-
408
- const nodeChangeset2 =
409
- id2 !== undefined
410
- ? tryGetFromNestedMap(nodeChanges2, id2.revision, id2.localId) ??
411
- fail("Unknown node ID")
412
- : {};
413
-
614
+ const nodeChangeset1 = nodeChangeFromId(nodeChanges1, id1);
615
+ const nodeChangeset2 = nodeChangeFromId(nodeChanges2, id2);
414
616
  const composedNodeChangeset = this.composeNodeChanges(
415
617
  nodeChangeset1,
416
618
  nodeChangeset2,
@@ -419,32 +621,37 @@ export class ModularChangeFamily
419
621
  revisionMetadata,
420
622
  );
421
623
 
422
- const nodeId = id1 ?? id2 ?? fail("Should not compose two undefined node IDs");
423
- setInNestedMap(
424
- composedNodeChanges,
425
- nodeId.revision,
426
- nodeId.localId,
427
- composedNodeChangeset,
428
- );
624
+ setInChangeAtomIdMap(composedNodes, id1, composedNodeChangeset);
625
+
626
+ if (!areEqualChangeAtomIds(id1, id2)) {
627
+ deleteFromNestedMap(composedNodes, id2.revision, id2.localId);
628
+ deleteFromNestedMap(composedNodeToParent, id2.revision, id2.localId);
629
+ setInChangeAtomIdMap(nodeAliases, id2, id1);
630
+
631
+ // We need to delete id1 to avoid forming a cycle in case id1 already had an alias.
632
+ deleteFromNestedMap(nodeAliases, id1.revision, id1.localId);
633
+ }
634
+
635
+ crossFieldTable.composedNodes.add(composedNodeChangeset);
429
636
  }
430
637
 
431
638
  private composeNodeChanges(
432
- change1: NodeChangeset | undefined,
433
- change2: NodeChangeset | undefined,
639
+ change1: NodeChangeset,
640
+ change2: NodeChangeset,
434
641
  genId: IdAllocator,
435
642
  crossFieldTable: ComposeTable,
436
643
  revisionMetadata: RevisionMetadataSource,
437
644
  ): NodeChangeset {
438
- const nodeExistsConstraint =
439
- change1?.nodeExistsConstraint ?? change2?.nodeExistsConstraint;
645
+ const nodeExistsConstraint = change1.nodeExistsConstraint ?? change2.nodeExistsConstraint;
440
646
 
441
647
  const composedFieldChanges = this.composeFieldMaps(
442
- change1?.fieldChanges,
443
- change2?.fieldChanges,
648
+ change1.fieldChanges,
649
+ change2.fieldChanges,
444
650
  genId,
445
651
  crossFieldTable,
446
652
  revisionMetadata,
447
653
  );
654
+
448
655
  const composedNodeChange: NodeChangeset = {};
449
656
 
450
657
  if (composedFieldChanges.size > 0) {
@@ -480,6 +687,9 @@ export class ModularChangeFamily
480
687
 
481
688
  if ((change.change.constraintViolationCount ?? 0) > 0) {
482
689
  return makeModularChangeset(
690
+ undefined,
691
+ undefined,
692
+ undefined,
483
693
  undefined,
484
694
  undefined,
485
695
  change.change.maxId,
@@ -490,14 +700,13 @@ export class ModularChangeFamily
490
700
  );
491
701
  }
492
702
 
493
- const idState: IdAllocationState = { maxId: change.change.maxId ?? -1 };
494
- // This idState is used for the whole of the IdAllocator's lifetime, which allows
495
- // this function to read the updated idState.maxId after more IDs are allocated.
496
- // TODO: add a getMax function to IdAllocator to make for a clearer contract.
497
- const genId: IdAllocator = idAllocatorFromState(idState);
703
+ const genId: IdAllocator = idAllocatorFromMaxId(change.change.maxId ?? -1);
704
+ const invertedNodeToParent = cloneNestedMap(change.change.nodeToParent);
705
+
498
706
  const crossFieldTable: InvertTable = {
499
707
  ...newCrossFieldTable<FieldChange>(),
500
708
  originalFieldToContext: new Map(),
709
+ invertedNodeToParent,
501
710
  };
502
711
 
503
712
  const { revInfos } = getRevInfoFromTaggedChanges([change]);
@@ -505,6 +714,7 @@ export class ModularChangeFamily
505
714
 
506
715
  const invertedFields = this.invertFieldMap(
507
716
  change.change.fieldChanges,
717
+ undefined,
508
718
  isRollback,
509
719
  genId,
510
720
  crossFieldTable,
@@ -519,6 +729,7 @@ export class ModularChangeFamily
519
729
  localId,
520
730
  this.invertNodeChange(
521
731
  nodeChangeset,
732
+ { revision, localId },
522
733
  isRollback,
523
734
  genId,
524
735
  crossFieldTable,
@@ -537,7 +748,7 @@ export class ModularChangeFamily
537
748
  context !== undefined,
538
749
  0x851 /* Should have context for every invalidated field */,
539
750
  );
540
- const { invertedField } = context;
751
+ const { invertedField, fieldId } = context;
541
752
 
542
753
  const amendedChange = getChangeHandler(
543
754
  this.fieldKinds,
@@ -546,17 +757,22 @@ export class ModularChangeFamily
546
757
  originalFieldChange,
547
758
  isRollback,
548
759
  genId,
549
- newCrossFieldManager(crossFieldTable, fieldChange),
760
+ new InvertManager(crossFieldTable, fieldChange, fieldId),
550
761
  revisionMetadata,
551
762
  );
552
763
  invertedField.change = brand(amendedChange);
553
764
  }
554
765
  }
555
766
 
767
+ const crossFieldKeys = this.makeCrossFieldKeyTable(invertedFields, invertedNodes);
768
+
556
769
  return makeModularChangeset(
557
770
  invertedFields,
558
771
  invertedNodes,
559
- idState.maxId,
772
+ invertedNodeToParent,
773
+ change.change.nodeAliases,
774
+ crossFieldKeys,
775
+ genId.getMaxId(),
560
776
  [],
561
777
  change.change.constraintViolationCount,
562
778
  undefined,
@@ -566,6 +782,7 @@ export class ModularChangeFamily
566
782
 
567
783
  private invertFieldMap(
568
784
  changes: FieldChangeMap,
785
+ parentId: NodeId | undefined,
569
786
  isRollback: boolean,
570
787
  genId: IdAllocator,
571
788
  crossFieldTable: InvertTable,
@@ -574,7 +791,8 @@ export class ModularChangeFamily
574
791
  const invertedFields: FieldChangeMap = new Map();
575
792
 
576
793
  for (const [field, fieldChange] of changes) {
577
- const manager = newCrossFieldManager(crossFieldTable, fieldChange);
794
+ const fieldId = { nodeId: parentId, field };
795
+ const manager = new InvertManager(crossFieldTable, fieldChange, fieldId);
578
796
  const invertedChange = getChangeHandler(
579
797
  this.fieldKinds,
580
798
  fieldChange.fieldKind,
@@ -587,6 +805,7 @@ export class ModularChangeFamily
587
805
  invertedFields.set(field, invertedFieldChange);
588
806
 
589
807
  crossFieldTable.originalFieldToContext.set(fieldChange, {
808
+ fieldId,
590
809
  invertedField: invertedFieldChange,
591
810
  });
592
811
  }
@@ -596,6 +815,7 @@ export class ModularChangeFamily
596
815
 
597
816
  private invertNodeChange(
598
817
  change: NodeChangeset,
818
+ id: NodeId,
599
819
  isRollback: boolean,
600
820
  genId: IdAllocator,
601
821
  crossFieldTable: InvertTable,
@@ -606,6 +826,7 @@ export class ModularChangeFamily
606
826
  if (change.fieldChanges !== undefined) {
607
827
  inverse.fieldChanges = this.invertFieldMap(
608
828
  change.fieldChanges,
829
+ id,
609
830
  isRollback,
610
831
  genId,
611
832
  crossFieldTable,
@@ -625,11 +846,18 @@ export class ModularChangeFamily
625
846
  const maxId = Math.max(change.maxId ?? -1, over.change.maxId ?? -1);
626
847
  const idState: IdAllocationState = { maxId };
627
848
  const genId: IdAllocator = idAllocatorFromState(idState);
849
+
628
850
  const crossFieldTable: RebaseTable = {
629
851
  ...newCrossFieldTable<FieldChange>(),
630
- fieldToContext: new Map(),
631
- rebasedNodeCache: new Map(),
852
+ newChange: change,
853
+ baseChange: over.change,
854
+ baseFieldToContext: new Map(),
855
+ baseToRebasedNodeId: new Map(),
856
+ rebasedFields: new Set(),
857
+ rebasedNodeToParent: cloneNestedMap(change.nodeToParent),
858
+ rebasedCrossFieldKeys: brand(change.crossFieldKeys.clone()),
632
859
  nodeIdPairs: [],
860
+ affectedBaseFields: newBTree(),
633
861
  };
634
862
 
635
863
  let constraintState = newConstraintState(change.constraintViolationCount ?? 0);
@@ -643,51 +871,33 @@ export class ModularChangeFamily
643
871
  getBaseRevisions,
644
872
  };
645
873
 
646
- const rebasedFields = this.rebaseFieldMap(
647
- change.fieldChanges,
648
- over.change.fieldChanges,
649
- genId,
874
+ const rebasedNodes: ChangeAtomIdMap<NodeChangeset> = cloneNestedMap(change.nodeChanges);
875
+
876
+ const rebasedFields = this.rebaseIntersectingFields(
650
877
  crossFieldTable,
878
+ rebasedNodes,
879
+ genId,
880
+ constraintState,
651
881
  rebaseMetadata,
652
882
  );
653
883
 
654
- const rebasedNodes: ChangeAtomIdMap<NodeChangeset> = new Map();
655
- for (const [newId, baseId] of crossFieldTable.nodeIdPairs) {
656
- const newNodeChange =
657
- newId !== undefined
658
- ? tryGetFromNestedMap(change.nodeChanges, newId.revision, newId.localId)
659
- : undefined;
660
-
661
- const baseNodeChange =
662
- baseId !== undefined
663
- ? tryGetFromNestedMap(over.change.nodeChanges, baseId.revision, baseId.localId) ??
664
- fail("Unknown node ID")
665
- : {};
666
-
667
- const rebasedNode = this.rebaseNodeChange(
668
- newNodeChange,
669
- baseNodeChange,
670
- genId,
671
- crossFieldTable,
672
- rebaseMetadata,
673
- );
674
-
675
- if (rebasedNode !== undefined) {
676
- const nodeId = newId ?? baseId ?? fail("Should not have two undefined IDs");
677
- setInNestedMap(rebasedNodes, nodeId.revision, nodeId.localId, rebasedNode);
678
- }
679
- }
884
+ this.rebaseFieldsWithoutNewChanges(
885
+ rebasedFields,
886
+ rebasedNodes,
887
+ crossFieldTable,
888
+ genId,
889
+ rebaseMetadata,
890
+ );
680
891
 
681
892
  if (crossFieldTable.invalidatedFields.size > 0) {
682
893
  const fieldsToUpdate = crossFieldTable.invalidatedFields;
683
894
  crossFieldTable.invalidatedFields = new Set();
684
895
  constraintState = newConstraintState(change.constraintViolationCount ?? 0);
685
896
  for (const field of fieldsToUpdate) {
686
- // TODO: Should we copy the context table out before this loop?
687
- const context = crossFieldTable.fieldToContext.get(field);
897
+ const context = crossFieldTable.baseFieldToContext.get(field);
688
898
  assert(context !== undefined, 0x852 /* Every field should have a context */);
689
899
  const {
690
- fieldKind,
900
+ changeHandler,
691
901
  change1: fieldChangeset,
692
902
  change2: baseChangeset,
693
903
  } = this.normalizeFieldChanges(
@@ -697,13 +907,34 @@ export class ModularChangeFamily
697
907
  revisionMetadata,
698
908
  );
699
909
 
700
- context.rebasedChange.change = fieldKind.changeHandler.rebaser.rebase(
701
- fieldChangeset,
702
- baseChangeset,
703
- (curr, base, existenceState) => curr ?? base,
704
- genId,
705
- newCrossFieldManager(crossFieldTable, field),
706
- rebaseMetadata,
910
+ const rebaseChild = (
911
+ curr: NodeId | undefined,
912
+ base: NodeId | undefined,
913
+ ): NodeId | undefined => {
914
+ if (curr !== undefined) {
915
+ return curr;
916
+ }
917
+
918
+ if (base !== undefined) {
919
+ for (const id of context.baseNodeIds) {
920
+ if (areEqualChangeAtomIds(base, id)) {
921
+ return base;
922
+ }
923
+ }
924
+ }
925
+
926
+ return undefined;
927
+ };
928
+
929
+ context.rebasedChange.change = brand(
930
+ changeHandler.rebaser.rebase(
931
+ fieldChangeset,
932
+ baseChangeset,
933
+ rebaseChild,
934
+ genId,
935
+ new RebaseManager(crossFieldTable, field, context.fieldId),
936
+ rebaseMetadata,
937
+ ),
707
938
  );
708
939
  }
709
940
  }
@@ -718,6 +949,9 @@ export class ModularChangeFamily
718
949
  return makeModularChangeset(
719
950
  this.pruneFieldMap(rebasedFields, rebasedNodes),
720
951
  rebasedNodes,
952
+ crossFieldTable.rebasedNodeToParent,
953
+ change.nodeAliases,
954
+ crossFieldTable.rebasedCrossFieldKeys,
721
955
  idState.maxId,
722
956
  change.revisions,
723
957
  constraintState.violationCount,
@@ -727,47 +961,283 @@ export class ModularChangeFamily
727
961
  );
728
962
  }
729
963
 
964
+ // This performs a first pass on all fields which have both new and base changes.
965
+ // TODO: Can we also handle additional passes in this method?
966
+ private rebaseIntersectingFields(
967
+ crossFieldTable: RebaseTable,
968
+ rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
969
+ genId: IdAllocator,
970
+ constraintState: ConstraintState,
971
+ metadata: RebaseRevisionMetadata,
972
+ ): FieldChangeMap {
973
+ const change = crossFieldTable.newChange;
974
+ const baseChange = crossFieldTable.baseChange;
975
+ const rebasedFields = this.rebaseFieldMap(
976
+ change.fieldChanges,
977
+ baseChange.fieldChanges,
978
+ undefined,
979
+ genId,
980
+ crossFieldTable,
981
+ metadata,
982
+ );
983
+
984
+ // This loop processes all fields which have both base and new changes.
985
+ // Note that the call to `rebaseNodeChange` can add entries to `crossFieldTable.nodeIdPairs`.
986
+ for (const [newId, baseId, _attachState] of crossFieldTable.nodeIdPairs) {
987
+ const rebasedNode = this.rebaseNodeChange(
988
+ newId,
989
+ baseId,
990
+ genId,
991
+ crossFieldTable,
992
+ metadata,
993
+ constraintState,
994
+ );
995
+
996
+ setInChangeAtomIdMap(rebasedNodes, newId, rebasedNode);
997
+ }
998
+
999
+ return rebasedFields;
1000
+ }
1001
+
1002
+ // This processes fields which have no new changes but have been invalidated by another field.
1003
+ private rebaseFieldsWithoutNewChanges(
1004
+ rebasedFields: FieldChangeMap,
1005
+ rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
1006
+ crossFieldTable: RebaseTable,
1007
+ genId: IdAllocator,
1008
+ metadata: RebaseRevisionMetadata,
1009
+ ): void {
1010
+ const baseChange = crossFieldTable.baseChange;
1011
+ for (const [revision, localId, fieldKey] of crossFieldTable.affectedBaseFields.keys()) {
1012
+ const baseNodeId =
1013
+ localId !== undefined
1014
+ ? normalizeNodeId({ revision, localId }, baseChange.nodeAliases)
1015
+ : undefined;
1016
+
1017
+ const baseFieldChange = fieldMapFromNodeId(
1018
+ baseChange.fieldChanges,
1019
+ baseChange.nodeChanges,
1020
+ baseNodeId,
1021
+ ).get(fieldKey);
1022
+
1023
+ assert(
1024
+ baseFieldChange !== undefined,
1025
+ 0x9c2 /* Cross field key registered for empty field */,
1026
+ );
1027
+ if (crossFieldTable.baseFieldToContext.has(baseFieldChange)) {
1028
+ // This field has already been processed because there were changes to rebase.
1029
+ continue;
1030
+ }
1031
+
1032
+ // This field has no changes in the new changeset, otherwise it would have been added to
1033
+ // `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
1034
+ const rebaseChild = (
1035
+ child: NodeId | undefined,
1036
+ baseChild: NodeId | undefined,
1037
+ stateChange: NodeAttachState | undefined,
1038
+ ): NodeId | undefined => {
1039
+ assert(child === undefined, 0x9c3 /* There should be no new changes in this field */);
1040
+ return undefined;
1041
+ };
1042
+
1043
+ const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
1044
+ const fieldChange: FieldChange = {
1045
+ ...baseFieldChange,
1046
+ change: brand(handler.createEmpty()),
1047
+ };
1048
+
1049
+ const rebasedNodeId =
1050
+ baseNodeId !== undefined
1051
+ ? rebasedNodeIdFromBaseNodeId(crossFieldTable, baseNodeId)
1052
+ : undefined;
1053
+
1054
+ const fieldId: FieldId = { nodeId: rebasedNodeId, field: fieldKey };
1055
+ const rebasedField: unknown = handler.rebaser.rebase(
1056
+ fieldChange.change,
1057
+ baseFieldChange.change,
1058
+ rebaseChild,
1059
+ genId,
1060
+ new RebaseManager(crossFieldTable, baseFieldChange, fieldId),
1061
+ metadata,
1062
+ );
1063
+
1064
+ const rebasedFieldChange: FieldChange = {
1065
+ ...baseFieldChange,
1066
+ change: brand(rebasedField),
1067
+ };
1068
+
1069
+ // TODO: Deduplicate
1070
+ crossFieldTable.baseFieldToContext.set(baseFieldChange, {
1071
+ newChange: fieldChange,
1072
+ baseChange: baseFieldChange,
1073
+ rebasedChange: rebasedFieldChange,
1074
+ fieldId,
1075
+ baseNodeIds: [],
1076
+ });
1077
+ crossFieldTable.rebasedFields.add(rebasedFieldChange);
1078
+
1079
+ this.attachRebasedField(
1080
+ rebasedFields,
1081
+ rebasedNodes,
1082
+ crossFieldTable,
1083
+ rebasedFieldChange,
1084
+ fieldId,
1085
+ genId,
1086
+ metadata,
1087
+ );
1088
+ }
1089
+ }
1090
+
1091
+ private attachRebasedField(
1092
+ rebasedFields: FieldChangeMap,
1093
+ rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
1094
+ table: RebaseTable,
1095
+ rebasedField: FieldChange,
1096
+ { nodeId, field: fieldKey }: FieldId,
1097
+ idAllocator: IdAllocator,
1098
+ metadata: RebaseRevisionMetadata,
1099
+ ): void {
1100
+ if (nodeId === undefined) {
1101
+ rebasedFields.set(fieldKey, rebasedField);
1102
+ return;
1103
+ }
1104
+ const rebasedNode = getFromChangeAtomIdMap(rebasedNodes, nodeId);
1105
+ if (rebasedNode !== undefined) {
1106
+ if (rebasedNode.fieldChanges === undefined) {
1107
+ rebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
1108
+ return;
1109
+ }
1110
+
1111
+ assert(!rebasedNode.fieldChanges.has(fieldKey), 0x9c4 /* Expected an empty field */);
1112
+ rebasedNode.fieldChanges.set(fieldKey, rebasedField);
1113
+ return;
1114
+ }
1115
+
1116
+ const newNode: NodeChangeset = {
1117
+ fieldChanges: new Map([[fieldKey, rebasedField]]),
1118
+ };
1119
+
1120
+ setInChangeAtomIdMap(rebasedNodes, nodeId, newNode);
1121
+ setInChangeAtomIdMap(table.baseToRebasedNodeId, nodeId, nodeId);
1122
+
1123
+ const parentFieldId = getParentFieldId(table.baseChange, nodeId);
1124
+
1125
+ this.attachRebasedNode(
1126
+ rebasedFields,
1127
+ rebasedNodes,
1128
+ table,
1129
+ nodeId,
1130
+ parentFieldId,
1131
+ idAllocator,
1132
+ metadata,
1133
+ );
1134
+ }
1135
+
1136
+ private attachRebasedNode(
1137
+ rebasedFields: FieldChangeMap,
1138
+ rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
1139
+ table: RebaseTable,
1140
+ baseNodeId: NodeId,
1141
+ parentFieldIdBase: FieldId,
1142
+ idAllocator: IdAllocator,
1143
+ metadata: RebaseRevisionMetadata,
1144
+ ): void {
1145
+ const baseFieldChange = fieldChangeFromId(
1146
+ table.baseChange.fieldChanges,
1147
+ table.baseChange.nodeChanges,
1148
+ parentFieldIdBase,
1149
+ );
1150
+
1151
+ const rebasedFieldId = rebasedFieldIdFromBaseId(table, parentFieldIdBase);
1152
+ setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, rebasedFieldId);
1153
+
1154
+ const context = table.baseFieldToContext.get(baseFieldChange);
1155
+ if (context !== undefined) {
1156
+ // We've already processed this field.
1157
+ // The new child node can be attached when processing invalidated fields.
1158
+ context.baseNodeIds.push(baseNodeId);
1159
+ table.invalidatedFields.add(baseFieldChange);
1160
+ return;
1161
+ }
1162
+
1163
+ const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
1164
+
1165
+ const fieldChange: FieldChange = {
1166
+ ...baseFieldChange,
1167
+ change: brand(handler.createEmpty()),
1168
+ };
1169
+
1170
+ const rebasedChangeset = handler.rebaser.rebase(
1171
+ handler.createEmpty(),
1172
+ baseFieldChange.change,
1173
+ (_idNew, idBase) =>
1174
+ idBase !== undefined && areEqualChangeAtomIds(idBase, baseNodeId)
1175
+ ? baseNodeId
1176
+ : undefined,
1177
+ idAllocator,
1178
+ new RebaseManager(table, baseFieldChange, rebasedFieldId),
1179
+ metadata,
1180
+ );
1181
+
1182
+ const rebasedField: FieldChange = { ...baseFieldChange, change: brand(rebasedChangeset) };
1183
+ table.rebasedFields.add(rebasedField);
1184
+ table.baseFieldToContext.set(baseFieldChange, {
1185
+ newChange: fieldChange,
1186
+ baseChange: baseFieldChange,
1187
+ rebasedChange: rebasedField,
1188
+ fieldId: rebasedFieldId,
1189
+ baseNodeIds: [],
1190
+ });
1191
+
1192
+ this.attachRebasedField(
1193
+ rebasedFields,
1194
+ rebasedNodes,
1195
+ table,
1196
+ rebasedField,
1197
+ rebasedFieldId,
1198
+ idAllocator,
1199
+ metadata,
1200
+ );
1201
+ }
1202
+
730
1203
  private rebaseFieldMap(
731
1204
  change: FieldChangeMap,
732
1205
  over: FieldChangeMap,
1206
+ parentId: NodeId | undefined,
733
1207
  genId: IdAllocator,
734
1208
  crossFieldTable: RebaseTable,
735
1209
  revisionMetadata: RebaseRevisionMetadata,
736
1210
  ): FieldChangeMap {
737
1211
  const rebasedFields: FieldChangeMap = new Map();
1212
+ const rebaseChild = (
1213
+ child: NodeId | undefined,
1214
+ baseChild: NodeId | undefined,
1215
+ stateChange: NodeAttachState | undefined,
1216
+ ): NodeId | undefined => {
1217
+ if (child !== undefined && baseChild !== undefined) {
1218
+ crossFieldTable.nodeIdPairs.push([child, baseChild, stateChange]);
1219
+ }
1220
+ return child;
1221
+ };
1222
+
1223
+ for (const [field, fieldChange] of change) {
1224
+ const fieldId: FieldId = { nodeId: parentId, field };
1225
+ const baseChange = over.get(field);
1226
+ if (baseChange === undefined) {
1227
+ rebasedFields.set(field, fieldChange);
1228
+ continue;
1229
+ }
738
1230
 
739
- // Rebase fields contained in the base changeset
740
- for (const [field, baseChanges] of over) {
741
- const fieldChange: FieldChange = change.get(field) ?? {
742
- fieldKind: genericFieldKind.identifier,
743
- change: brand(newGenericChangeset()),
744
- };
745
1231
  const {
746
1232
  fieldKind,
1233
+ changeHandler,
747
1234
  change1: fieldChangeset,
748
1235
  change2: baseChangeset,
749
- } = this.normalizeFieldChanges(fieldChange, baseChanges, genId, revisionMetadata);
1236
+ } = this.normalizeFieldChanges(fieldChange, baseChange, genId, revisionMetadata);
750
1237
 
751
- const manager = newCrossFieldManager(crossFieldTable, fieldChange);
1238
+ const manager = new RebaseManager(crossFieldTable, baseChange, fieldId);
752
1239
 
753
- const rebaseChild = (
754
- child: NodeId | undefined,
755
- baseChild: NodeId | undefined,
756
- _attachState: NodeAttachState | undefined,
757
- ): ChangeAtomId => {
758
- crossFieldTable.nodeIdPairs.push([child, baseChild]);
759
- return (
760
- child ??
761
- // The fact `child` is undefined means that the changeset to rebase does not include changes for
762
- // this node or its descendants. However, it's possible that it will after rebasing.
763
- // In that case, we will need a NodeId to represent these changes under in the rebased changeset.
764
- // We adopt `baseChild` for this purpose.
765
- baseChild ??
766
- fail("Should not have two undefined node IDs")
767
- );
768
- };
769
-
770
- const rebasedField = fieldKind.changeHandler.rebaser.rebase(
1240
+ const rebasedField = changeHandler.rebaser.rebase(
771
1241
  fieldChangeset,
772
1242
  baseChangeset,
773
1243
  rebaseChild,
@@ -777,88 +1247,54 @@ export class ModularChangeFamily
777
1247
  );
778
1248
 
779
1249
  const rebasedFieldChange: FieldChange = {
780
- fieldKind: fieldKind.identifier,
1250
+ fieldKind,
781
1251
  change: brand(rebasedField),
782
1252
  };
783
1253
 
784
1254
  rebasedFields.set(field, rebasedFieldChange);
785
1255
 
786
- crossFieldTable.fieldToContext.set(fieldChange, {
787
- baseChange: baseChanges,
1256
+ crossFieldTable.baseFieldToContext.set(baseChange, {
1257
+ baseChange,
788
1258
  newChange: fieldChange,
789
1259
  rebasedChange: rebasedFieldChange,
1260
+ fieldId,
1261
+ baseNodeIds: [],
790
1262
  });
791
- }
792
1263
 
793
- // Rebase the fields of the new changeset which don't have a corresponding base field.
794
- for (const [field, fieldChange] of change) {
795
- if (!over?.has(field)) {
796
- const baseChanges: FieldChange = {
797
- fieldKind: genericFieldKind.identifier,
798
- change: brand(newGenericChangeset()),
799
- };
800
-
801
- const {
802
- fieldKind,
803
- change1: fieldChangeset,
804
- change2: baseChangeset,
805
- } = this.normalizeFieldChanges(fieldChange, baseChanges, genId, revisionMetadata);
806
-
807
- // TODO: Don't we need to add an entry in the context table?
808
- const manager = newCrossFieldManager(crossFieldTable, fieldChange);
809
- const rebasedChangeset = fieldKind.changeHandler.rebaser.rebase(
810
- fieldChangeset,
811
- baseChangeset,
812
- (child, baseChild) => {
813
- assert(
814
- baseChild === undefined,
815
- 0x5b6 /* This field should not have any base changes */,
816
- );
817
-
818
- crossFieldTable.nodeIdPairs.push([child, undefined]);
819
-
820
- return child;
821
- },
822
- genId,
823
- manager,
824
- revisionMetadata,
825
- );
826
- const rebasedFieldChange: FieldChange = {
827
- fieldKind: fieldKind.identifier,
828
- change: brand(rebasedChangeset),
829
- };
830
- rebasedFields.set(field, rebasedFieldChange);
831
- }
1264
+ crossFieldTable.rebasedFields.add(rebasedFieldChange);
832
1265
  }
833
1266
 
834
1267
  return rebasedFields;
835
1268
  }
836
1269
 
837
1270
  private rebaseNodeChange(
838
- change: NodeChangeset | undefined,
839
- over: NodeChangeset | undefined,
1271
+ newId: NodeId,
1272
+ baseId: NodeId,
840
1273
  genId: IdAllocator,
841
1274
  crossFieldTable: RebaseTable,
842
1275
  revisionMetadata: RebaseRevisionMetadata,
843
- ): NodeChangeset | undefined {
844
- const key = change ?? over;
845
- if (key === undefined) {
846
- return undefined;
847
- }
1276
+ constraintState: ConstraintState,
1277
+ ): NodeChangeset {
1278
+ const change = nodeChangeFromId(crossFieldTable.newChange.nodeChanges, newId);
1279
+ const over = nodeChangeFromId(crossFieldTable.baseChange.nodeChanges, baseId);
848
1280
 
849
1281
  const baseMap: FieldChangeMap = over?.fieldChanges ?? new Map();
850
1282
 
851
- const fieldChanges = this.rebaseFieldMap(
852
- change?.fieldChanges ?? new Map(),
853
- baseMap,
854
- genId,
855
- crossFieldTable,
856
- revisionMetadata,
857
- );
1283
+ const fieldChanges =
1284
+ change.fieldChanges !== undefined && over.fieldChanges !== undefined
1285
+ ? this.rebaseFieldMap(
1286
+ change?.fieldChanges ?? new Map(),
1287
+ baseMap,
1288
+ newId,
1289
+ genId,
1290
+ crossFieldTable,
1291
+ revisionMetadata,
1292
+ )
1293
+ : change.fieldChanges;
858
1294
 
859
1295
  const rebasedChange: NodeChangeset = {};
860
1296
 
861
- if (fieldChanges.size > 0) {
1297
+ if (fieldChanges !== undefined && fieldChanges.size > 0) {
862
1298
  rebasedChange.fieldChanges = fieldChanges;
863
1299
  }
864
1300
 
@@ -866,7 +1302,7 @@ export class ModularChangeFamily
866
1302
  rebasedChange.nodeExistsConstraint = change.nodeExistsConstraint;
867
1303
  }
868
1304
 
869
- crossFieldTable.rebasedNodeCache.set(key, rebasedChange);
1305
+ setInChangeAtomIdMap(crossFieldTable.baseToRebasedNodeId, baseId, newId);
870
1306
  return rebasedChange;
871
1307
  }
872
1308
 
@@ -915,9 +1351,13 @@ export class ModularChangeFamily
915
1351
  }
916
1352
 
917
1353
  private pruneFieldMap(
918
- changeset: FieldChangeMap,
1354
+ changeset: FieldChangeMap | undefined,
919
1355
  nodeMap: ChangeAtomIdMap<NodeChangeset>,
920
1356
  ): FieldChangeMap | undefined {
1357
+ if (changeset === undefined) {
1358
+ return undefined;
1359
+ }
1360
+
921
1361
  const prunedChangeset: FieldChangeMap = new Map();
922
1362
  for (const [field, fieldChange] of changeset) {
923
1363
  const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
@@ -938,9 +1378,7 @@ export class ModularChangeFamily
938
1378
  nodeId: NodeId,
939
1379
  nodeMap: ChangeAtomIdMap<NodeChangeset>,
940
1380
  ): NodeId | undefined {
941
- const changeset = tryGetFromNestedMap(nodeMap, nodeId.revision, nodeId.localId);
942
- assert(changeset !== undefined, 0x930 /* Unknown node ID */);
943
-
1381
+ const changeset = nodeChangeFromId(nodeMap, nodeId);
944
1382
  const prunedFields =
945
1383
  changeset.fieldChanges !== undefined
946
1384
  ? this.pruneFieldMap(changeset.fieldChanges, nodeMap)
@@ -955,7 +1393,7 @@ export class ModularChangeFamily
955
1393
  deleteFromNestedMap(nodeMap, nodeId.revision, nodeId.localId);
956
1394
  return undefined;
957
1395
  } else {
958
- setInNestedMap(nodeMap, nodeId.revision, nodeId.localId, prunedChange);
1396
+ setInChangeAtomIdMap(nodeMap, nodeId, prunedChange);
959
1397
  return nodeId;
960
1398
  }
961
1399
  }
@@ -984,10 +1422,32 @@ export class ModularChangeFamily
984
1422
  ]),
985
1423
  );
986
1424
 
1425
+ const updatedNodeToParent: ChangeAtomIdMap<FieldId> = nestedMapFromFlatList(
1426
+ nestedMapToFlatList(change.nodeToParent).map(([revision, id, fieldId]) => [
1427
+ replaceRevision(revision, oldRevisions, newRevision),
1428
+ id,
1429
+ replaceFieldIdRevision(
1430
+ normalizeFieldId(fieldId, change.nodeAliases),
1431
+ oldRevisions,
1432
+ newRevision,
1433
+ ),
1434
+ ]),
1435
+ );
1436
+
987
1437
  const updated: Mutable<ModularChangeset> = {
988
1438
  ...change,
989
1439
  fieldChanges: updatedFields,
990
1440
  nodeChanges: updatedNodes,
1441
+ nodeToParent: updatedNodeToParent,
1442
+
1443
+ // We've updated all references to old node IDs, so we no longer need an alias table.
1444
+ nodeAliases: new Map(),
1445
+ crossFieldKeys: replaceCrossFieldKeyTableRevisions(
1446
+ change.crossFieldKeys,
1447
+ oldRevisions,
1448
+ newRevision,
1449
+ change.nodeAliases,
1450
+ ),
991
1451
  };
992
1452
 
993
1453
  if (change.builds !== undefined) {
@@ -1040,20 +1500,90 @@ export class ModularChangeFamily
1040
1500
  ): FieldChangeMap {
1041
1501
  const updatedFields: FieldChangeMap = new Map();
1042
1502
  for (const [field, fieldChange] of fields) {
1043
- const updatedFieldChange = getFieldKind(
1503
+ const updatedFieldChange = getChangeHandler(
1044
1504
  this.fieldKinds,
1045
1505
  fieldChange.fieldKind,
1046
- ).changeHandler.rebaser.replaceRevisions(fieldChange.change, oldRevisions, newRevision);
1506
+ ).rebaser.replaceRevisions(fieldChange.change, oldRevisions, newRevision);
1507
+
1508
+ updatedFields.set(field, { ...fieldChange, change: brand(updatedFieldChange) });
1509
+ }
1510
+
1511
+ return updatedFields;
1512
+ }
1513
+
1514
+ private makeCrossFieldKeyTable(
1515
+ fields: FieldChangeMap,
1516
+ nodes: ChangeAtomIdMap<NodeChangeset>,
1517
+ ): CrossFieldKeyTable {
1518
+ const keys: CrossFieldKeyTable = newCrossFieldKeyTable();
1519
+ this.populateCrossFieldKeyTableForFieldMap(keys, fields, undefined);
1520
+ forEachInNestedMap(nodes, (node, revision, localId) => {
1521
+ if (node.fieldChanges !== undefined) {
1522
+ this.populateCrossFieldKeyTableForFieldMap(keys, node.fieldChanges, {
1523
+ revision,
1524
+ localId,
1525
+ });
1526
+ }
1527
+ });
1047
1528
 
1048
- updatedFields.set(field, { ...fieldChange, change: updatedFieldChange });
1049
- }
1529
+ return keys;
1530
+ }
1050
1531
 
1051
- return updatedFields;
1532
+ private populateCrossFieldKeyTableForFieldMap(
1533
+ table: CrossFieldKeyTable,
1534
+ fields: FieldChangeMap,
1535
+ parent: NodeId | undefined,
1536
+ ): void {
1537
+ for (const [fieldKey, fieldChange] of fields) {
1538
+ const keys = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).getCrossFieldKeys(
1539
+ fieldChange.change,
1540
+ );
1541
+ for (const key of keys) {
1542
+ table.set(key, { nodeId: parent, field: fieldKey });
1543
+ }
1544
+ }
1052
1545
  }
1053
1546
 
1054
1547
  public buildEditor(changeReceiver: (change: ModularChangeset) => void): ModularEditBuilder {
1055
- return new ModularEditBuilder(this, changeReceiver);
1548
+ return new ModularEditBuilder(this, this.fieldKinds, changeReceiver);
1056
1549
  }
1550
+
1551
+ private createEmptyFieldChange(fieldKind: FieldKindIdentifier): FieldChange {
1552
+ const emptyChange = getChangeHandler(this.fieldKinds, fieldKind).createEmpty();
1553
+ return { fieldKind, change: brand(emptyChange) };
1554
+ }
1555
+ }
1556
+
1557
+ function replaceCrossFieldKeyTableRevisions(
1558
+ table: CrossFieldKeyTable,
1559
+ oldRevisions: Set<RevisionTag | undefined>,
1560
+ newRevision: RevisionTag | undefined,
1561
+ nodeAliases: ChangeAtomIdMap<NodeId>,
1562
+ ): CrossFieldKeyTable {
1563
+ const updated: CrossFieldKeyTable = newBTree();
1564
+ table.forEachPair(([target, revision, id, count], field) => {
1565
+ const updatedKey: CrossFieldKeyRange = [
1566
+ target,
1567
+ replaceRevision(revision, oldRevisions, newRevision),
1568
+ id,
1569
+ count,
1570
+ ];
1571
+
1572
+ const normalizedFieldId = normalizeFieldId(field, nodeAliases);
1573
+ const updatedNodeId =
1574
+ normalizedFieldId.nodeId !== undefined
1575
+ ? replaceAtomRevisions(normalizedFieldId.nodeId, oldRevisions, newRevision)
1576
+ : undefined;
1577
+
1578
+ const updatedValue: FieldId = {
1579
+ ...normalizedFieldId,
1580
+ nodeId: updatedNodeId,
1581
+ };
1582
+
1583
+ updated.set(updatedKey, updatedValue);
1584
+ });
1585
+
1586
+ return updated;
1057
1587
  }
1058
1588
 
1059
1589
  function replaceRevision(
@@ -1215,8 +1745,7 @@ function* relevantRemovedRootsFromFields(
1215
1745
  for (const [_, fieldChange] of change) {
1216
1746
  const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
1217
1747
  const delegate = function* (node: NodeId): Iterable<DeltaDetachedNodeId> {
1218
- const nodeChangeset = tryGetFromNestedMap(nodeChanges, node.revision, node.localId);
1219
- assert(nodeChangeset !== undefined, 0x931 /* Unknown node ID */);
1748
+ const nodeChangeset = nodeChangeFromId(nodeChanges, node);
1220
1749
  if (nodeChangeset.fieldChanges !== undefined) {
1221
1750
  yield* relevantRemovedRootsFromFields(
1222
1751
  nodeChangeset.fieldChanges,
@@ -1297,6 +1826,9 @@ export function updateRefreshers(
1297
1826
  return makeModularChangeset(
1298
1827
  fieldChanges,
1299
1828
  nodeChanges,
1829
+ change.nodeToParent,
1830
+ change.nodeAliases,
1831
+ change.crossFieldKeys,
1300
1832
  maxId,
1301
1833
  revisions,
1302
1834
  constraintViolationCount,
@@ -1383,13 +1915,7 @@ function intoDeltaImpl(
1383
1915
  const deltaField = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(
1384
1916
  fieldChange.change,
1385
1917
  (childChange): DeltaFieldMap => {
1386
- const nodeChange = tryGetFromNestedMap(
1387
- nodeChanges,
1388
- childChange.revision,
1389
- childChange.localId,
1390
- );
1391
-
1392
- assert(nodeChange !== undefined, 0x932 /* Unknown node ID */);
1918
+ const nodeChange = nodeChangeFromId(nodeChanges, childChange);
1393
1919
  return deltaFromNodeChange(nodeChange, nodeChanges, idAllocator, fieldKinds);
1394
1920
  },
1395
1921
  idAllocator,
@@ -1482,68 +2008,100 @@ interface CrossFieldTable<TFieldData> {
1482
2008
 
1483
2009
  interface InvertTable extends CrossFieldTable<FieldChange> {
1484
2010
  originalFieldToContext: Map<FieldChange, InvertContext>;
2011
+ invertedNodeToParent: ChangeAtomIdMap<FieldId>;
1485
2012
  }
1486
2013
 
1487
2014
  interface InvertContext {
2015
+ fieldId: FieldId;
1488
2016
  invertedField: FieldChange;
1489
2017
  }
1490
2018
 
1491
2019
  interface RebaseTable extends CrossFieldTable<FieldChange> {
2020
+ readonly baseChange: ModularChangeset;
2021
+ readonly newChange: ModularChangeset;
2022
+
1492
2023
  /**
1493
- * Maps from the FieldChange key used for the CrossFieldTable (which is the FieldChange being rebased)
1494
- * to context for the field.
1495
- */
1496
- fieldToContext: Map<FieldChange, RebaseFieldContext>;
1497
- /**
1498
- * This map caches the output of a prior rebasing computation for a node, keyed on that computation's input.
1499
- * The input for such a computation is characterized by a pair of node changesets:
1500
- * - The node changeset from the input changeset being rebased
1501
- * - The corresponding node changeset from the changeset being rebased over.
1502
- *
1503
- * Either of these may be undefined so we adopt the following convention:
1504
- * - If the node changeset from the changeset being rebased is defined, then we use that as the key
1505
- * - Otherwise, if the node changeset from the changeset being rebased over is defined, then we use that as the key
1506
- * - Otherwise, we don't cache the output (which will be undefined anyway).
1507
- *
1508
- * This map is needed once we switch from the initial pass (which generates a new changeset) to the second pass which
1509
- * performs surgery on the changeset generated in the first pass: we don't want to re-run the rebasing of nested
1510
- * changes. Instead we want to keep using the objects generated in the first pass and mutate them where needed.
2024
+ * Maps from the FieldChange key used for the CrossFieldTable (which is the base FieldChange)
2025
+ * to the context for the field.
1511
2026
  */
1512
- rebasedNodeCache: Map<NodeChangeset, NodeChangeset>;
2027
+ readonly baseFieldToContext: Map<FieldChange, RebaseFieldContext>;
2028
+ readonly baseToRebasedNodeId: ChangeAtomIdMap<NodeId>;
2029
+ readonly rebasedFields: Set<FieldChange>;
2030
+ readonly rebasedNodeToParent: ChangeAtomIdMap<FieldId>;
2031
+ readonly rebasedCrossFieldKeys: CrossFieldKeyTable;
1513
2032
 
1514
2033
  /**
1515
- * List of (newId, baseId) pairs encountered so far.
2034
+ * List of unprocessed (newId, baseId) pairs encountered so far.
1516
2035
  */
1517
- nodeIdPairs: [NodeId | undefined, NodeId | undefined][];
2036
+ readonly nodeIdPairs: [NodeId, NodeId, NodeAttachState | undefined][];
2037
+ readonly affectedBaseFields: TupleBTree<FieldIdKey, boolean>;
1518
2038
  }
1519
2039
 
2040
+ type FieldIdKey = [RevisionTag | undefined, ChangesetLocalId | undefined, FieldKey];
2041
+
1520
2042
  interface RebaseFieldContext {
1521
2043
  baseChange: FieldChange;
1522
2044
  newChange: FieldChange;
1523
2045
  rebasedChange: FieldChange;
2046
+ fieldId: FieldId;
2047
+
2048
+ /**
2049
+ * The set of node IDs in the base changeset which should be included in the rebased field,
2050
+ * even if there is no corresponding node changeset in the new change.
2051
+ */
2052
+ baseNodeIds: NodeId[];
1524
2053
  }
1525
2054
 
1526
- function newComposeTable(): ComposeTable {
2055
+ function newComposeTable(
2056
+ baseChange: ModularChangeset,
2057
+ newChange: ModularChangeset,
2058
+ ): ComposeTable {
1527
2059
  return {
1528
2060
  ...newCrossFieldTable<FieldChange>(),
2061
+ baseChange,
2062
+ newChange,
1529
2063
  fieldToContext: new Map(),
1530
- nodeIds: new Map(),
1531
- nodeIdPairs: [],
2064
+ newFieldToBaseField: new Map(),
2065
+ newToBaseNodeId: new Map(),
2066
+ composedNodes: new Set(),
2067
+ pendingCompositions: {
2068
+ nodeIdsToCompose: [],
2069
+ affectedBaseFields: newBTree(),
2070
+ affectedNewFields: newBTree(),
2071
+ },
1532
2072
  };
1533
2073
  }
1534
2074
 
1535
2075
  interface ComposeTable extends CrossFieldTable<FieldChange> {
2076
+ readonly baseChange: ModularChangeset;
2077
+ readonly newChange: ModularChangeset;
2078
+
1536
2079
  /**
1537
2080
  * Maps from an input changeset for a field (from change1 if it has one, from change2 otherwise) to the context for that field.
1538
2081
  */
1539
- fieldToContext: Map<FieldChange, ComposeFieldContext>;
2082
+ readonly fieldToContext: Map<FieldChange, ComposeFieldContext>;
2083
+ readonly newFieldToBaseField: Map<FieldChange, FieldChange>;
2084
+ readonly newToBaseNodeId: ChangeAtomIdMap<NodeId>;
2085
+ readonly composedNodes: Set<NodeChangeset>;
2086
+ readonly pendingCompositions: PendingCompositions;
2087
+ }
2088
+
2089
+ interface PendingCompositions {
2090
+ /**
2091
+ * Each entry in this list represents a node with both base and new changes which have not yet been composed.
2092
+ * Entries are of the form [baseId, newId].
2093
+ */
2094
+ readonly nodeIdsToCompose: [NodeId, NodeId][];
1540
2095
 
1541
2096
  /**
1542
- * The set of node IDs from the second changeset which have been encountered.
2097
+ * The set of fields in the base changeset which have been affected by a cross field effect.
1543
2098
  */
1544
- nodeIds: NestedSet<RevisionTag, ChangesetLocalId>;
2099
+ readonly affectedBaseFields: BTree<FieldIdKey, true>;
1545
2100
 
1546
- nodeIdPairs: [NodeId | undefined, NodeId | undefined][];
2101
+ /**
2102
+ * The set of fields in the new changeset which have been affected by a cross field effect.
2103
+ */
2104
+ readonly affectedNewFields: BTree<FieldIdKey, true>;
1547
2105
  }
1548
2106
 
1549
2107
  interface ComposeFieldContext {
@@ -1575,77 +2133,269 @@ function newConstraintState(violationCount: number): ConstraintState {
1575
2133
  };
1576
2134
  }
1577
2135
 
1578
- interface CrossFieldManagerI<T> extends CrossFieldManager {
1579
- table: CrossFieldTable<T>;
1580
- }
1581
-
1582
- function newCrossFieldManager<T>(
1583
- crossFieldTable: CrossFieldTable<T>,
1584
- currentFieldKey: T,
1585
- allowInval = true,
1586
- ): CrossFieldManagerI<T> {
1587
- const getMap = (target: CrossFieldTarget): CrossFieldMap<unknown> =>
1588
- target === CrossFieldTarget.Source ? crossFieldTable.srcTable : crossFieldTable.dstTable;
1589
-
1590
- const getDependents = (target: CrossFieldTarget): CrossFieldMap<T> =>
1591
- target === CrossFieldTarget.Source
1592
- ? crossFieldTable.srcDependents
1593
- : crossFieldTable.dstDependents;
1594
-
1595
- const manager = {
1596
- table: crossFieldTable,
1597
-
1598
- set: (
1599
- target: CrossFieldTarget,
1600
- revision: RevisionTag | undefined,
1601
- id: ChangesetLocalId,
1602
- count: number,
1603
- newValue: unknown,
1604
- invalidateDependents: boolean,
1605
- ) => {
1606
- if (invalidateDependents && allowInval) {
1607
- const lastChangedId = (id as number) + count - 1;
1608
- let firstId = id;
1609
- while (firstId <= lastChangedId) {
1610
- const dependentEntry = getFirstFromCrossFieldMap(
1611
- getDependents(target),
1612
- revision,
1613
- firstId,
1614
- lastChangedId - firstId + 1,
2136
+ abstract class CrossFieldManagerI<T> implements CrossFieldManager {
2137
+ public constructor(
2138
+ protected readonly crossFieldTable: CrossFieldTable<T>,
2139
+ private readonly currentFieldKey: T,
2140
+ protected readonly allowInval = true,
2141
+ ) {}
2142
+
2143
+ public set(
2144
+ target: CrossFieldTarget,
2145
+ revision: RevisionTag | undefined,
2146
+ id: ChangesetLocalId,
2147
+ count: number,
2148
+ newValue: unknown,
2149
+ invalidateDependents: boolean,
2150
+ ): void {
2151
+ if (invalidateDependents && this.allowInval) {
2152
+ const lastChangedId = (id as number) + count - 1;
2153
+ let firstId = id;
2154
+ while (firstId <= lastChangedId) {
2155
+ const dependentEntry = getFirstFromCrossFieldMap(
2156
+ this.getDependents(target),
2157
+ revision,
2158
+ firstId,
2159
+ lastChangedId - firstId + 1,
2160
+ );
2161
+ if (dependentEntry.value !== undefined) {
2162
+ this.crossFieldTable.invalidatedFields.add(dependentEntry.value);
2163
+ }
2164
+
2165
+ firstId = brand(firstId + dependentEntry.length);
2166
+ }
2167
+ }
2168
+ setInCrossFieldMap(this.getMap(target), revision, id, count, newValue);
2169
+ }
2170
+
2171
+ public get(
2172
+ target: CrossFieldTarget,
2173
+ revision: RevisionTag | undefined,
2174
+ id: ChangesetLocalId,
2175
+ count: number,
2176
+ addDependency: boolean,
2177
+ ): RangeQueryResult<unknown> {
2178
+ if (addDependency) {
2179
+ // We assume that if there is already an entry for this ID it is because
2180
+ // a field handler has called compose on the same node multiple times.
2181
+ // In this case we only want to update the latest version, so we overwrite the dependency.
2182
+ setInCrossFieldMap(
2183
+ this.getDependents(target),
2184
+ revision,
2185
+ id,
2186
+ count,
2187
+ this.currentFieldKey,
2188
+ );
2189
+ }
2190
+ return getFirstFromCrossFieldMap(this.getMap(target), revision, id, count);
2191
+ }
2192
+
2193
+ public abstract onMoveIn(id: NodeId): void;
2194
+
2195
+ public abstract moveKey(
2196
+ target: CrossFieldTarget,
2197
+ revision: RevisionTag | undefined,
2198
+ id: ChangesetLocalId,
2199
+ count: number,
2200
+ ): void;
2201
+
2202
+ private getMap(target: CrossFieldTarget): CrossFieldMap<unknown> {
2203
+ return target === CrossFieldTarget.Source
2204
+ ? this.crossFieldTable.srcTable
2205
+ : this.crossFieldTable.dstTable;
2206
+ }
2207
+
2208
+ private getDependents(target: CrossFieldTarget): CrossFieldMap<T> {
2209
+ return target === CrossFieldTarget.Source
2210
+ ? this.crossFieldTable.srcDependents
2211
+ : this.crossFieldTable.dstDependents;
2212
+ }
2213
+ }
2214
+
2215
+ class InvertManager extends CrossFieldManagerI<FieldChange> {
2216
+ public constructor(
2217
+ table: InvertTable,
2218
+ field: FieldChange,
2219
+ private readonly fieldId: FieldId,
2220
+ allowInval = true,
2221
+ ) {
2222
+ super(table, field, allowInval);
2223
+ }
2224
+
2225
+ public override onMoveIn(id: ChangeAtomId): void {
2226
+ setInChangeAtomIdMap(this.table.invertedNodeToParent, id, this.fieldId);
2227
+ }
2228
+
2229
+ public override moveKey(
2230
+ target: CrossFieldTarget,
2231
+ revision: RevisionTag | undefined,
2232
+ id: ChangesetLocalId,
2233
+ count: number,
2234
+ ): void {
2235
+ assert(false, 0x9c5 /* Keys should not be moved manually during invert */);
2236
+ }
2237
+
2238
+ private get table(): InvertTable {
2239
+ return this.crossFieldTable as InvertTable;
2240
+ }
2241
+ }
2242
+
2243
+ class RebaseManager extends CrossFieldManagerI<FieldChange> {
2244
+ public constructor(
2245
+ table: RebaseTable,
2246
+ currentField: FieldChange,
2247
+ private readonly fieldId: FieldId,
2248
+ allowInval = true,
2249
+ ) {
2250
+ super(table, currentField, allowInval);
2251
+ }
2252
+
2253
+ public override set(
2254
+ target: CrossFieldTarget,
2255
+ revision: RevisionTag | undefined,
2256
+ id: ChangesetLocalId,
2257
+ count: number,
2258
+ newValue: unknown,
2259
+ invalidateDependents: boolean,
2260
+ ): void {
2261
+ if (invalidateDependents && this.allowInval) {
2262
+ const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, [
2263
+ target,
2264
+ revision,
2265
+ id,
2266
+ count,
2267
+ ]);
2268
+
2269
+ assert(
2270
+ newFieldIds.length === 0,
2271
+ 0x9c6 /* TODO: Modifying a cross-field key from the new changeset is currently unsupported */,
2272
+ );
2273
+
2274
+ const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, [
2275
+ target,
2276
+ revision,
2277
+ id,
2278
+ count,
2279
+ ]);
2280
+
2281
+ assert(
2282
+ baseFieldIds.length > 0,
2283
+ 0x9c7 /* Cross field key not registered in base or new change */,
2284
+ );
2285
+
2286
+ for (const baseFieldId of baseFieldIds) {
2287
+ this.table.affectedBaseFields.set(
2288
+ [baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field],
2289
+ true,
2290
+ );
2291
+ }
2292
+ }
2293
+
2294
+ super.set(target, revision, id, count, newValue, invalidateDependents);
2295
+ }
2296
+
2297
+ public override onMoveIn(id: ChangeAtomId): void {
2298
+ setInChangeAtomIdMap(this.table.rebasedNodeToParent, id, this.fieldId);
2299
+ }
2300
+
2301
+ public override moveKey(
2302
+ target: CrossFieldTarget,
2303
+ revision: RevisionTag | undefined,
2304
+ id: ChangesetLocalId,
2305
+ count: number,
2306
+ ): void {
2307
+ setInCrossFieldKeyTable(
2308
+ this.table.rebasedCrossFieldKeys,
2309
+ target,
2310
+ revision,
2311
+ id,
2312
+ count,
2313
+ this.fieldId,
2314
+ );
2315
+ }
2316
+
2317
+ private get table(): RebaseTable {
2318
+ return this.crossFieldTable as RebaseTable;
2319
+ }
2320
+ }
2321
+
2322
+ // TODO: Deduplicate this with RebaseTable
2323
+ class ComposeManager extends CrossFieldManagerI<FieldChange> {
2324
+ public constructor(table: ComposeTable, currentField: FieldChange, allowInval = true) {
2325
+ super(table, currentField, allowInval);
2326
+ }
2327
+
2328
+ public override set(
2329
+ target: CrossFieldTarget,
2330
+ revision: RevisionTag | undefined,
2331
+ id: ChangesetLocalId,
2332
+ count: number,
2333
+ newValue: unknown,
2334
+ invalidateDependents: boolean,
2335
+ ): void {
2336
+ if (invalidateDependents && this.allowInval) {
2337
+ const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, [
2338
+ target,
2339
+ revision,
2340
+ id,
2341
+ count,
2342
+ ]);
2343
+
2344
+ if (newFieldIds.length > 0) {
2345
+ for (const newFieldId of newFieldIds) {
2346
+ this.table.pendingCompositions.affectedNewFields.set(
2347
+ [newFieldId.nodeId?.revision, newFieldId.nodeId?.localId, newFieldId.field],
2348
+ true,
1615
2349
  );
1616
- if (dependentEntry.value !== undefined) {
1617
- crossFieldTable.invalidatedFields.add(dependentEntry.value);
1618
- }
2350
+ }
2351
+ } else {
2352
+ const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, [
2353
+ target,
2354
+ revision,
2355
+ id,
2356
+ count,
2357
+ ]);
1619
2358
 
1620
- firstId = brand(firstId + dependentEntry.length);
2359
+ assert(
2360
+ baseFieldIds.length > 0,
2361
+ 0x9c8 /* Cross field key not registered in base or new change */,
2362
+ );
2363
+
2364
+ for (const baseFieldId of baseFieldIds) {
2365
+ this.table.pendingCompositions.affectedBaseFields.set(
2366
+ [baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field],
2367
+ true,
2368
+ );
1621
2369
  }
1622
2370
  }
1623
- setInCrossFieldMap(getMap(target), revision, id, count, newValue);
1624
- },
2371
+ }
1625
2372
 
1626
- get: (
1627
- target: CrossFieldTarget,
1628
- revision: RevisionTag | undefined,
1629
- id: ChangesetLocalId,
1630
- count: number,
1631
- addDependency: boolean,
1632
- ) => {
1633
- if (addDependency) {
1634
- // We assume that if there is already an entry for this ID it is because
1635
- // a field handler has called compose on the same node multiple times.
1636
- // In this case we only want to update the latest version, so we overwrite the dependency.
1637
- setInCrossFieldMap(getDependents(target), revision, id, count, currentFieldKey);
1638
- }
1639
- return getFirstFromCrossFieldMap(getMap(target), revision, id, count);
1640
- },
1641
- };
2373
+ super.set(target, revision, id, count, newValue, invalidateDependents);
2374
+ }
2375
+
2376
+ public override onMoveIn(id: ChangeAtomId): void {
2377
+ throw new Error("Method not implemented.");
2378
+ }
2379
+ public override moveKey(
2380
+ target: CrossFieldTarget,
2381
+ revision: RevisionTag | undefined,
2382
+ id: ChangesetLocalId,
2383
+ count: number,
2384
+ ): void {
2385
+ throw new Error("Method not implemented.");
2386
+ }
1642
2387
 
1643
- return manager;
2388
+ private get table(): ComposeTable {
2389
+ return this.crossFieldTable as ComposeTable;
2390
+ }
1644
2391
  }
1645
2392
 
1646
2393
  function makeModularChangeset(
1647
2394
  fieldChanges: FieldChangeMap | undefined = undefined,
1648
2395
  nodeChanges: ChangeAtomIdMap<NodeChangeset> | undefined = undefined,
2396
+ nodeToParent: ChangeAtomIdMap<FieldId> | undefined = undefined,
2397
+ nodeAliases: ChangeAtomIdMap<NodeId> | undefined = undefined,
2398
+ crossFieldKeys: CrossFieldKeyTable | undefined = undefined,
1649
2399
  maxId: number = -1,
1650
2400
  revisions: readonly RevisionInfo[] | undefined = undefined,
1651
2401
  constraintViolationCount: number | undefined = undefined,
@@ -1656,7 +2406,11 @@ function makeModularChangeset(
1656
2406
  const changeset: Mutable<ModularChangeset> = {
1657
2407
  fieldChanges: fieldChanges ?? new Map(),
1658
2408
  nodeChanges: nodeChanges ?? new Map(),
2409
+ nodeToParent: nodeToParent ?? new Map(),
2410
+ nodeAliases: nodeAliases ?? new Map(),
2411
+ crossFieldKeys: crossFieldKeys ?? newCrossFieldKeyTable(),
1659
2412
  };
2413
+
1660
2414
  if (revisions !== undefined && revisions.length > 0) {
1661
2415
  changeset.revisions = revisions;
1662
2416
  }
@@ -1684,6 +2438,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
1684
2438
 
1685
2439
  public constructor(
1686
2440
  family: ChangeFamily<ChangeFamilyEditor, ModularChangeset>,
2441
+ private readonly fieldKinds: ReadonlyMap<FieldKindIdentifier, FieldKindWithEditor>,
1687
2442
  changeReceiver: (change: ModularChangeset) => void,
1688
2443
  ) {
1689
2444
  super(family, changeReceiver);
@@ -1744,11 +2499,18 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
1744
2499
  fieldKind: FieldKindIdentifier,
1745
2500
  change: FieldChangeset,
1746
2501
  ): void {
2502
+ const crossFieldKeys = getChangeHandler(this.fieldKinds, fieldKind).getCrossFieldKeys(
2503
+ change,
2504
+ );
2505
+
1747
2506
  const modularChange = buildModularChangesetFromField(
1748
2507
  field,
1749
2508
  { fieldKind, change },
1750
2509
  new Map(),
2510
+ new Map(),
2511
+ newCrossFieldKeyTable(),
1751
2512
  this.idAllocator,
2513
+ crossFieldKeys,
1752
2514
  );
1753
2515
  this.applyChange(modularChange);
1754
2516
  }
@@ -1763,6 +2525,9 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
1763
2525
  makeAnonChange(
1764
2526
  change.type === "global"
1765
2527
  ? makeModularChangeset(
2528
+ undefined,
2529
+ undefined,
2530
+ undefined,
1766
2531
  undefined,
1767
2532
  undefined,
1768
2533
  this.idAllocator.getMaxId(),
@@ -1777,7 +2542,12 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
1777
2542
  change: change.change,
1778
2543
  },
1779
2544
  new Map(),
2545
+ new Map(),
2546
+ newCrossFieldKeyTable(),
1780
2547
  this.idAllocator,
2548
+ getChangeHandler(this.fieldKinds, change.fieldKind).getCrossFieldKeys(
2549
+ change.change,
2550
+ ),
1781
2551
  ),
1782
2552
  ),
1783
2553
  );
@@ -1801,7 +2571,14 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
1801
2571
  };
1802
2572
 
1803
2573
  this.applyChange(
1804
- buildModularChangesetFromNode(path, nodeChange, new Map(), this.idAllocator),
2574
+ buildModularChangesetFromNode(
2575
+ path,
2576
+ nodeChange,
2577
+ new Map(),
2578
+ new Map(),
2579
+ newCrossFieldKeyTable(),
2580
+ this.idAllocator,
2581
+ ),
1805
2582
  );
1806
2583
  }
1807
2584
  }
@@ -1810,29 +2587,74 @@ function buildModularChangesetFromField(
1810
2587
  path: FieldUpPath,
1811
2588
  fieldChange: FieldChange,
1812
2589
  nodeChanges: ChangeAtomIdMap<NodeChangeset>,
2590
+ nodeToParent: ChangeAtomIdMap<FieldId>,
2591
+ crossFieldKeys: CrossFieldKeyTable,
1813
2592
  idAllocator: IdAllocator = idAllocatorFromMaxId(),
2593
+ localCrossFieldKeys: CrossFieldKeyRange[] = [],
2594
+ childId: NodeId | undefined = undefined,
1814
2595
  ): ModularChangeset {
1815
2596
  const fieldChanges: FieldChangeMap = new Map([[path.field, fieldChange]]);
1816
2597
 
1817
2598
  if (path.parent === undefined) {
1818
- return makeModularChangeset(fieldChanges, nodeChanges, idAllocator.getMaxId());
2599
+ for (const key of localCrossFieldKeys) {
2600
+ crossFieldKeys.set(key, { nodeId: undefined, field: path.field });
2601
+ }
2602
+
2603
+ if (childId !== undefined) {
2604
+ setInChangeAtomIdMap(nodeToParent, childId, {
2605
+ nodeId: undefined,
2606
+ field: path.field,
2607
+ });
2608
+ }
2609
+
2610
+ return makeModularChangeset(
2611
+ fieldChanges,
2612
+ nodeChanges,
2613
+ nodeToParent,
2614
+ undefined,
2615
+ crossFieldKeys,
2616
+ idAllocator.getMaxId(),
2617
+ );
1819
2618
  }
1820
2619
 
1821
2620
  const nodeChangeset: NodeChangeset = {
1822
2621
  fieldChanges,
1823
2622
  };
1824
2623
 
1825
- return buildModularChangesetFromNode(path.parent, nodeChangeset, nodeChanges, idAllocator);
2624
+ const parentId: NodeId = { localId: brand(idAllocator.allocate()) };
2625
+
2626
+ for (const key of localCrossFieldKeys) {
2627
+ crossFieldKeys.set(key, { nodeId: parentId, field: path.field });
2628
+ }
2629
+
2630
+ if (childId !== undefined) {
2631
+ setInChangeAtomIdMap(nodeToParent, childId, {
2632
+ nodeId: parentId,
2633
+ field: path.field,
2634
+ });
2635
+ }
2636
+
2637
+ return buildModularChangesetFromNode(
2638
+ path.parent,
2639
+ nodeChangeset,
2640
+ nodeChanges,
2641
+ nodeToParent,
2642
+ crossFieldKeys,
2643
+ idAllocator,
2644
+ parentId,
2645
+ );
1826
2646
  }
1827
2647
 
1828
2648
  function buildModularChangesetFromNode(
1829
2649
  path: UpPath,
1830
2650
  nodeChange: NodeChangeset,
1831
2651
  nodeChanges: ChangeAtomIdMap<NodeChangeset>,
2652
+ nodeToParent: ChangeAtomIdMap<FieldId>,
2653
+ crossFieldKeys: CrossFieldKeyTable,
1832
2654
  idAllocator: IdAllocator,
2655
+ nodeId: NodeId = { localId: brand(idAllocator.allocate()) },
1833
2656
  ): ModularChangeset {
1834
- const nodeId: NodeId = { localId: brand(idAllocator.allocate()) };
1835
- setInNestedMap(nodeChanges, nodeId.revision, nodeId.localId, nodeChange);
2657
+ setInChangeAtomIdMap(nodeChanges, nodeId, nodeChange);
1836
2658
  const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChange(
1837
2659
  path.parentIndex,
1838
2660
  nodeId,
@@ -1847,7 +2669,11 @@ function buildModularChangesetFromNode(
1847
2669
  { parent: path.parent, field: path.parentField },
1848
2670
  fieldChange,
1849
2671
  nodeChanges,
2672
+ nodeToParent,
2673
+ crossFieldKeys,
1850
2674
  idAllocator,
2675
+ [],
2676
+ nodeId,
1851
2677
  );
1852
2678
  }
1853
2679
 
@@ -1936,8 +2762,259 @@ function revisionFromRevInfos(
1936
2762
  return revInfos[0].revision;
1937
2763
  }
1938
2764
 
1939
- function getActiveFieldChanges(changes: ModularChangeset): FieldChangeMap {
1940
- return (changes.constraintViolationCount ?? 0) === 0
1941
- ? changes.fieldChanges
1942
- : new Map<FieldKey, FieldChange>();
2765
+ function mergeBTrees<K, V>(tree1: BTree<K, V>, tree2: BTree<K, V>): BTree<K, V> {
2766
+ const result = tree1.clone();
2767
+ tree2.forEachPair((k, v) => {
2768
+ result.set(k, v);
2769
+ });
2770
+
2771
+ return result;
2772
+ }
2773
+
2774
+ function mergeNestedMaps<K1, K2, V>(
2775
+ map1: NestedMap<K1, K2, V>,
2776
+ map2: NestedMap<K1, K2, V>,
2777
+ ): NestedMap<K1, K2, V> {
2778
+ const merged: NestedMap<K1, K2, V> = new Map();
2779
+ populateNestedMap(map1, merged, true);
2780
+ populateNestedMap(map2, merged, true);
2781
+ return merged;
2782
+ }
2783
+
2784
+ function fieldChangeFromId(
2785
+ fields: FieldChangeMap,
2786
+ nodes: ChangeAtomIdMap<NodeChangeset>,
2787
+ id: FieldId,
2788
+ ): FieldChange {
2789
+ const fieldMap = fieldMapFromNodeId(fields, nodes, id.nodeId);
2790
+ return fieldMap.get(id.field) ?? fail("No field exists for the given ID");
2791
+ }
2792
+
2793
+ function fieldMapFromNodeId(
2794
+ rootFieldMap: FieldChangeMap,
2795
+ nodes: ChangeAtomIdMap<NodeChangeset>,
2796
+ nodeId: NodeId | undefined,
2797
+ ): FieldChangeMap {
2798
+ if (nodeId === undefined) {
2799
+ return rootFieldMap;
2800
+ }
2801
+
2802
+ const node = nodeChangeFromId(nodes, nodeId);
2803
+ assert(node.fieldChanges !== undefined, 0x9c9 /* Expected node to have field changes */);
2804
+ return node.fieldChanges;
2805
+ }
2806
+
2807
+ function rebasedFieldIdFromBaseId(table: RebaseTable, baseId: FieldId): FieldId {
2808
+ if (baseId.nodeId === undefined) {
2809
+ return baseId;
2810
+ }
2811
+
2812
+ return { ...baseId, nodeId: rebasedNodeIdFromBaseNodeId(table, baseId.nodeId) };
2813
+ }
2814
+
2815
+ function rebasedNodeIdFromBaseNodeId(table: RebaseTable, baseId: NodeId): NodeId {
2816
+ return getFromChangeAtomIdMap(table.baseToRebasedNodeId, baseId) ?? baseId;
2817
+ }
2818
+
2819
+ function nodeChangeFromId(nodes: ChangeAtomIdMap<NodeChangeset>, id: NodeId): NodeChangeset {
2820
+ const node = getFromChangeAtomIdMap(nodes, id);
2821
+ assert(node !== undefined, 0x9ca /* Unknown node ID */);
2822
+ return node;
2823
+ }
2824
+
2825
+ function fieldIdFromFieldIdKey([revision, localId, field]: FieldIdKey): FieldId {
2826
+ const nodeId = localId !== undefined ? { revision, localId } : undefined;
2827
+ return { nodeId, field };
2828
+ }
2829
+
2830
+ function cloneNodeChangeset(nodeChangeset: NodeChangeset): NodeChangeset {
2831
+ if (nodeChangeset.fieldChanges !== undefined) {
2832
+ return { ...nodeChangeset, fieldChanges: new Map(nodeChangeset.fieldChanges) };
2833
+ }
2834
+
2835
+ return { ...nodeChangeset };
2836
+ }
2837
+
2838
+ function replaceFieldIdRevision(
2839
+ fieldId: FieldId,
2840
+ oldRevisions: Set<RevisionTag | undefined>,
2841
+ newRevision: RevisionTag | undefined,
2842
+ ): FieldId {
2843
+ if (fieldId.nodeId === undefined) {
2844
+ return fieldId;
2845
+ }
2846
+
2847
+ return {
2848
+ ...fieldId,
2849
+ nodeId: replaceAtomRevisions(fieldId.nodeId, oldRevisions, newRevision),
2850
+ };
2851
+ }
2852
+
2853
+ export function getParentFieldId(changeset: ModularChangeset, nodeId: NodeId): FieldId {
2854
+ const parentId = getFromChangeAtomIdMap(changeset.nodeToParent, nodeId);
2855
+ assert(parentId !== undefined, 0x9cb /* Parent field should be defined */);
2856
+ return normalizeFieldId(parentId, changeset.nodeAliases);
2857
+ }
2858
+
2859
+ export function getFieldsForCrossFieldKey(
2860
+ changeset: ModularChangeset,
2861
+ [target, revision, id, count]: CrossFieldKeyRange,
2862
+ ): FieldId[] {
2863
+ let firstLocalId: number = id;
2864
+ const lastLocalId = id + count - 1;
2865
+
2866
+ const fields: FieldId[] = [];
2867
+
2868
+ // eslint-disable-next-line no-constant-condition
2869
+ while (true) {
2870
+ const entry = getFirstIntersectingCrossFieldEntry(changeset.crossFieldKeys, [
2871
+ target,
2872
+ revision,
2873
+ brand(firstLocalId),
2874
+ count,
2875
+ ]);
2876
+
2877
+ if (entry === undefined) {
2878
+ return fields;
2879
+ }
2880
+
2881
+ const [[_target, _revision, entryId, entryCount], fieldId] = entry;
2882
+ fields.push(normalizeFieldId(fieldId, changeset.nodeAliases));
2883
+
2884
+ const entryLastId = entryId + entryCount - 1;
2885
+ if (entryLastId >= lastLocalId) {
2886
+ return fields;
2887
+ }
2888
+
2889
+ firstLocalId = entryLastId + 1;
2890
+ }
2891
+ }
2892
+
2893
+ function getFirstIntersectingCrossFieldEntry(
2894
+ table: CrossFieldKeyTable,
2895
+ [target, revision, id, count]: CrossFieldKeyRange,
2896
+ ): [CrossFieldKeyRange, FieldId] | undefined {
2897
+ const entry = table.nextLowerPair([target, revision, id, Infinity]);
2898
+ if (entry === undefined) {
2899
+ return undefined;
2900
+ }
2901
+
2902
+ const [entryTarget, entryRevision, entryId, entryCount] = entry[0];
2903
+ if (entryTarget !== target || entryRevision !== revision) {
2904
+ return undefined;
2905
+ }
2906
+
2907
+ const lastQueryId = id + count - 1;
2908
+ const entryLastId = entryId + entryCount - 1;
2909
+ if (entryId > lastQueryId || entryLastId < id) {
2910
+ return undefined;
2911
+ }
2912
+
2913
+ return entry;
2914
+ }
2915
+
2916
+ function setInCrossFieldKeyTable(
2917
+ table: CrossFieldKeyTable,
2918
+ target: CrossFieldTarget,
2919
+ revision: RevisionTag | undefined,
2920
+ id: ChangesetLocalId,
2921
+ count: number,
2922
+ value: FieldId,
2923
+ ): void {
2924
+ let entry = getFirstIntersectingCrossFieldEntry(table, [target, revision, id, count]);
2925
+ const lastQueryId = id + count - 1;
2926
+ while (entry !== undefined) {
2927
+ const [entryKey, entryValue] = entry;
2928
+ table.delete(entryKey);
2929
+
2930
+ const [_target, _revision, entryId, entryCount] = entryKey;
2931
+ if (entryId < id) {
2932
+ table.set([target, revision, entryId, id - entryId], entryValue);
2933
+ }
2934
+
2935
+ const lastEntryId = entryId + entryCount - 1;
2936
+ if (lastEntryId > lastQueryId) {
2937
+ table.set(
2938
+ [target, revision, brand(lastQueryId + 1), lastEntryId - lastQueryId],
2939
+ entryValue,
2940
+ );
2941
+ break;
2942
+ }
2943
+
2944
+ const nextId: ChangesetLocalId = brand(lastEntryId + 1);
2945
+ entry = getFirstIntersectingCrossFieldEntry(table, [
2946
+ target,
2947
+ revision,
2948
+ nextId,
2949
+ lastQueryId - nextId + 1,
2950
+ ]);
2951
+ }
2952
+
2953
+ table.set([target, revision, id, count], value);
2954
+ }
2955
+
2956
+ function normalizeFieldId(fieldId: FieldId, nodeAliases: ChangeAtomIdMap<NodeId>): FieldId {
2957
+ return fieldId.nodeId !== undefined
2958
+ ? { ...fieldId, nodeId: normalizeNodeId(fieldId.nodeId, nodeAliases) }
2959
+ : fieldId;
2960
+ }
2961
+
2962
+ /**
2963
+ * @returns The canonical form of nodeId, according to nodeAliases
2964
+ */
2965
+ function normalizeNodeId(nodeId: NodeId, nodeAliases: ChangeAtomIdMap<NodeId>): NodeId {
2966
+ let currentId = nodeId;
2967
+
2968
+ // eslint-disable-next-line no-constant-condition
2969
+ while (true) {
2970
+ const dealiased = getFromChangeAtomIdMap(nodeAliases, currentId);
2971
+ if (dealiased === undefined) {
2972
+ return currentId;
2973
+ }
2974
+
2975
+ currentId = dealiased;
2976
+ }
2977
+ }
2978
+
2979
+ function hasConflicts(change: ModularChangeset): boolean {
2980
+ return (change.constraintViolationCount ?? 0) > 0;
2981
+ }
2982
+
2983
+ export function newCrossFieldKeyTable(): CrossFieldKeyTable {
2984
+ return newBTree();
2985
+ }
2986
+
2987
+ function newBTree<K extends readonly unknown[], V>(): TupleBTree<K, V> {
2988
+ return brand(new BTree<K, V>(undefined, compareTuples));
2989
+ }
2990
+
2991
+ // This assumes that the arrays are the same length.
2992
+ function compareTuples(arrayA: readonly unknown[], arrayB: readonly unknown[]): number {
2993
+ for (let i = 0; i < arrayA.length; i++) {
2994
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2995
+ const a = arrayA[i] as any;
2996
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2997
+ const b = arrayB[i] as any;
2998
+ if (a < b) {
2999
+ return -1;
3000
+ } else if (a > b) {
3001
+ return 1;
3002
+ }
3003
+ }
3004
+
3005
+ return 0;
3006
+ }
3007
+
3008
+ interface ModularChangesetContent {
3009
+ fieldChanges: FieldChangeMap;
3010
+ nodeChanges: ChangeAtomIdMap<NodeChangeset>;
3011
+ nodeToParent: ChangeAtomIdMap<FieldId>;
3012
+ nodeAliases: ChangeAtomIdMap<NodeId>;
3013
+ crossFieldKeys: CrossFieldKeyTable;
3014
+ }
3015
+
3016
+ function cloneNestedMap<K1, K2, V>(map: NestedMap<K1, K2, V>): NestedMap<K1, K2, V> {
3017
+ const cloned: NestedMap<K1, K2, V> = new Map();
3018
+ populateNestedMap(map, cloned, true);
3019
+ return cloned;
1943
3020
  }