@fluidframework/tree 2.1.0-276985 → 2.1.0-281041

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