@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
@@ -4,15 +4,15 @@
4
4
  */
5
5
  import { assert } from "@fluidframework/core-utils/internal";
6
6
  import { BTree } from "@tylerbu/sorted-btree-es6";
7
- import { EditBuilder, isEmptyFieldChanges, makeAnonChange, makeDetachedNodeId, mapCursorField, revisionMetadataSourceFromInfo, } from "../../core/index.js";
8
- import { addToNestedSet, brand, deleteFromNestedMap, fail, forEachInNestedMap, getOrAddInMap, idAllocatorFromMaxId, idAllocatorFromState, nestedMapFromFlatList, nestedMapToFlatList, nestedSetContains, populateNestedMap, setInNestedMap, tryGetFromNestedMap, } from "../../util/index.js";
7
+ import { EditBuilder, isEmptyFieldChanges, makeAnonChange, makeDetachedNodeId, mapCursorField, replaceAtomRevisions, revisionMetadataSourceFromInfo, setInChangeAtomIdMap, areEqualChangeAtomIds, getFromChangeAtomIdMap, } from "../../core/index.js";
8
+ import { brand, deleteFromNestedMap, fail, forEachInNestedMap, getOrAddInMap, idAllocatorFromMaxId, idAllocatorFromState, nestedMapFromFlatList, nestedMapToFlatList, populateNestedMap, setInNestedMap, tryGetFromNestedMap, } from "../../util/index.js";
9
9
  import { chunkFieldSingle, chunkTree, defaultChunkPolicy, } from "../chunked-forest/index.js";
10
10
  import { cursorForMapTreeNode, mapTreeFromCursor } from "../mapTreeCursor.js";
11
11
  import { MemoizedIdRangeAllocator } from "../memoizedIdRangeAllocator.js";
12
12
  import { CrossFieldTarget, getFirstFromCrossFieldMap, setInCrossFieldMap, } from "./crossFieldQueries.js";
13
13
  import { NodeAttachState, } from "./fieldChangeHandler.js";
14
14
  import { withEditor } from "./fieldKindWithEditor.js";
15
- import { convertGenericChange, genericFieldKind, newGenericChangeset, } from "./genericFieldKind.js";
15
+ import { convertGenericChange, genericFieldKind } from "./genericFieldKind.js";
16
16
  /**
17
17
  * Implementation of ChangeFamily which delegates work in a given field to the appropriate FieldKind
18
18
  * as determined by the schema.
@@ -34,27 +34,30 @@ export class ModularChangeFamily {
34
34
  */
35
35
  normalizeFieldChanges(change1, change2, genId, revisionMetadata) {
36
36
  // TODO: Handle the case where changes have conflicting field kinds
37
- const kind = change1 !== undefined && change1.fieldKind !== genericFieldKind.identifier
37
+ const kind = change1.fieldKind !== genericFieldKind.identifier
38
38
  ? change1.fieldKind
39
- : change2?.fieldKind ?? genericFieldKind.identifier;
39
+ : change2.fieldKind;
40
40
  if (kind === genericFieldKind.identifier) {
41
- // All the changes are generic
41
+ // Both changes are generic
42
42
  return {
43
- fieldKind: genericFieldKind,
44
- change1: change1?.change,
45
- change2: change2?.change,
43
+ fieldKind: genericFieldKind.identifier,
44
+ changeHandler: genericFieldKind.changeHandler,
45
+ change1: change1.change,
46
+ change2: change2.change,
46
47
  };
47
48
  }
48
49
  const fieldKind = getFieldKind(this.fieldKinds, kind);
49
- const handler = fieldKind.changeHandler;
50
- const normalizedChange1 = this.normalizeFieldChange(change1, handler, genId, revisionMetadata);
51
- const normalizedChange2 = this.normalizeFieldChange(change2, handler, genId, revisionMetadata);
52
- return { fieldKind, change1: normalizedChange1, change2: normalizedChange2 };
50
+ const changeHandler = fieldKind.changeHandler;
51
+ const normalizedChange1 = this.normalizeFieldChange(change1, changeHandler, genId, revisionMetadata);
52
+ const normalizedChange2 = this.normalizeFieldChange(change2, changeHandler, genId, revisionMetadata);
53
+ return {
54
+ fieldKind: kind,
55
+ changeHandler,
56
+ change1: normalizedChange1,
57
+ change2: normalizedChange2,
58
+ };
53
59
  }
54
60
  normalizeFieldChange(fieldChange, handler, genId, revisionMetadata) {
55
- if (fieldChange === undefined) {
56
- return undefined;
57
- }
58
61
  if (fieldChange.fieldKind !== genericFieldKind.identifier) {
59
62
  return fieldChange.change;
60
63
  }
@@ -69,105 +72,227 @@ export class ModularChangeFamily {
69
72
  compose(changes) {
70
73
  const { revInfos, maxId } = getRevInfoFromTaggedChanges(changes);
71
74
  const idState = { maxId };
72
- return changes.reduce((change1, change2) => makeAnonChange(this.composePair(change1, change2, revInfos, idState)), makeAnonChange({ fieldChanges: new Map(), nodeChanges: new Map() })).change;
75
+ return changes.reduce((change1, change2) => makeAnonChange(this.composePair(change1, change2, revInfos, idState)), makeAnonChange({
76
+ fieldChanges: new Map(),
77
+ nodeChanges: new Map(),
78
+ nodeToParent: new Map(),
79
+ nodeAliases: new Map(),
80
+ crossFieldKeys: newCrossFieldKeyTable(),
81
+ })).change;
73
82
  }
74
83
  composePair(change1, change2, revInfos, idState) {
75
- const genId = idAllocatorFromState(idState);
76
- const revisionMetadata = revisionMetadataSourceFromInfo(revInfos);
77
- const crossFieldTable = newComposeTable();
78
- const composedFields = this.composeFieldMaps(getActiveFieldChanges(change1.change), getActiveFieldChanges(change2.change), genId, crossFieldTable, revisionMetadata);
79
- const composedNodeChanges = new Map();
80
- for (const [id1, id2] of crossFieldTable.nodeIdPairs) {
81
- this.composeNodesById(change1.change.nodeChanges, change2.change.nodeChanges, composedNodeChanges, id1, id2, genId, crossFieldTable, revisionMetadata);
82
- }
83
- crossFieldTable.nodeIdPairs.length = 0;
84
- while (crossFieldTable.invalidatedFields.size > 0) {
85
- const fieldsToUpdate = crossFieldTable.invalidatedFields;
86
- crossFieldTable.invalidatedFields = new Set();
87
- for (const fieldChange of fieldsToUpdate) {
88
- const context = crossFieldTable.fieldToContext.get(fieldChange);
89
- assert(context !== undefined, 0x8cc /* Should have context for every invalidated field */);
90
- const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
91
- const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
92
- const composeNodes = (child1, child2) => {
93
- if (child2 !== undefined &&
94
- !nestedSetContains(crossFieldTable.nodeIds, child2.revision, child2.localId)) {
95
- crossFieldTable.nodeIdPairs.push([child1, child2]);
96
- if (child1 !== undefined && child2 !== undefined) {
97
- addToNestedSet(crossFieldTable.nodeIds, child2.revision, child2.localId);
98
- }
99
- }
100
- return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
101
- };
102
- const amendedChange = rebaser.compose(fieldChange1, fieldChange2, composeNodes, genId, newCrossFieldManager(crossFieldTable, fieldChange, false), revisionMetadata);
103
- composedChange.change = brand(amendedChange);
104
- // Process any newly discovered nodes.
105
- for (const [taggedId1, taggedId2] of crossFieldTable.nodeIdPairs) {
106
- this.composeNodesById(change1.change.nodeChanges, change2.change.nodeChanges, composedNodeChanges, taggedId1, taggedId2, genId, crossFieldTable, revisionMetadata);
107
- }
108
- crossFieldTable.nodeIdPairs.length = 0;
109
- }
110
- }
84
+ const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys } = this.composeAllFields(change1.change, change2.change, revInfos, idState);
111
85
  const { allBuilds, allDestroys, allRefreshers } = composeBuildsDestroysAndRefreshers([
112
86
  change1,
113
87
  change2,
114
88
  ]);
115
- return makeModularChangeset(this.pruneFieldMap(composedFields, composedNodeChanges), composedNodeChanges, idState.maxId, revInfos, undefined, allBuilds, allDestroys, allRefreshers);
89
+ return makeModularChangeset(this.pruneFieldMap(fieldChanges, nodeChanges), nodeChanges, nodeToParent, nodeAliases, crossFieldKeys, idState.maxId, revInfos, undefined, allBuilds, allDestroys, allRefreshers);
90
+ }
91
+ composeAllFields(change1, change2, revInfos, idState) {
92
+ if (hasConflicts(change1) && hasConflicts(change2)) {
93
+ return {
94
+ fieldChanges: new Map(),
95
+ nodeChanges: new Map(),
96
+ nodeToParent: new Map(),
97
+ nodeAliases: new Map(),
98
+ crossFieldKeys: newBTree(),
99
+ };
100
+ }
101
+ else if (hasConflicts(change1)) {
102
+ return change2;
103
+ }
104
+ else if (hasConflicts(change2)) {
105
+ return change1;
106
+ }
107
+ const genId = idAllocatorFromState(idState);
108
+ const revisionMetadata = revisionMetadataSourceFromInfo(revInfos);
109
+ const crossFieldTable = newComposeTable(change1, change2);
110
+ // We merge nodeChanges, nodeToParent, and nodeAliases from the two changesets.
111
+ // The merged tables will have correct entries for all nodes which are only referenced in one of the input changesets.
112
+ // During composeFieldMaps and processInvalidatedElements we will find all nodes referenced in both input changesets
113
+ // and adjust these tables as necessary.
114
+ // Note that when merging these tables we may encounter key collisions and will arbitrarily drop values in that case.
115
+ // A collision for a node ID means that that node is referenced in both changesets
116
+ // (since we assume that if two changesets use the same node ID they are referring to the same node),
117
+ // therefore all collisions will be addressed when processing the intersection of the changesets.
118
+ const composedNodeChanges = mergeNestedMaps(change1.nodeChanges, change2.nodeChanges);
119
+ const composedNodeToParent = mergeNestedMaps(change1.nodeToParent, change2.nodeToParent);
120
+ const composedNodeAliases = mergeNestedMaps(change1.nodeAliases, change2.nodeAliases);
121
+ const composedFields = this.composeFieldMaps(change1.fieldChanges, change2.fieldChanges, genId, crossFieldTable, revisionMetadata);
122
+ this.processInvalidatedElements(crossFieldTable, composedFields, composedNodeChanges, composedNodeToParent, composedNodeAliases, genId, revisionMetadata);
123
+ // Currently no field kinds require making changes to cross-field keys during composition, so we can just merge the two tables.
124
+ const composedCrossFieldKeys = mergeBTrees(change1.crossFieldKeys, change2.crossFieldKeys);
125
+ return {
126
+ fieldChanges: composedFields,
127
+ nodeChanges: composedNodeChanges,
128
+ nodeToParent: composedNodeToParent,
129
+ nodeAliases: composedNodeAliases,
130
+ crossFieldKeys: brand(composedCrossFieldKeys),
131
+ };
132
+ }
133
+ composeInvalidatedField(fieldChange, crossFieldTable, genId, revisionMetadata) {
134
+ const context = crossFieldTable.fieldToContext.get(fieldChange);
135
+ assert(context !== undefined, 0x8cc /* Should have context for every invalidated field */);
136
+ const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
137
+ const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
138
+ const composeNodes = (child1, child2) => {
139
+ if (child1 !== undefined &&
140
+ child2 !== undefined &&
141
+ getFromChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2) === undefined) {
142
+ setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
143
+ crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
144
+ }
145
+ return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
146
+ };
147
+ const amendedChange = rebaser.compose(fieldChange1, fieldChange2, composeNodes, genId, new ComposeManager(crossFieldTable, fieldChange, false), revisionMetadata);
148
+ composedChange.change = brand(amendedChange);
149
+ }
150
+ /**
151
+ * Updates everything in the composed output which may no longer be valid.
152
+ * This could be due to
153
+ * - discovering that two node changesets refer to the same node (`nodeIdsToCompose`)
154
+ * - a previously composed field being invalidated by a cross field effect (`invalidatedFields`)
155
+ * - a field which was copied directly from an input changeset being invalidated by a cross field effect
156
+ * (`affectedBaseFields` and `affectedNewFields`)
157
+ *
158
+ * Updating an element may invalidate further elements. This function runs until there is no more invalidation.
159
+ */
160
+ processInvalidatedElements(table, composedFields, composedNodes, composedNodeToParent, nodeAliases, genId, metadata) {
161
+ const pending = table.pendingCompositions;
162
+ while (table.invalidatedFields.size > 0 ||
163
+ pending.nodeIdsToCompose.length > 0 ||
164
+ pending.affectedBaseFields.length > 0 ||
165
+ pending.affectedNewFields.length > 0) {
166
+ // Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
167
+ for (const [id1, id2] of pending.nodeIdsToCompose) {
168
+ this.composeNodesById(table.baseChange.nodeChanges, table.newChange.nodeChanges, composedNodes, composedNodeToParent, nodeAliases, id1, id2, genId, table, metadata);
169
+ }
170
+ pending.nodeIdsToCompose.length = 0;
171
+ this.composeAffectedFields(table, table.baseChange, true, pending.affectedBaseFields, composedFields, composedNodes, genId, metadata);
172
+ this.composeAffectedFields(table, table.newChange, false, pending.affectedNewFields, composedFields, composedNodes, genId, metadata);
173
+ this.processInvalidatedCompositions(table, genId, metadata);
174
+ }
175
+ }
176
+ processInvalidatedCompositions(table, genId, metadata) {
177
+ const fieldsToUpdate = table.invalidatedFields;
178
+ table.invalidatedFields = new Set();
179
+ for (const fieldChange of fieldsToUpdate) {
180
+ this.composeInvalidatedField(fieldChange, table, genId, metadata);
181
+ }
182
+ }
183
+ /**
184
+ * Ensures that each field in `affectedFields` has been updated in the composition output.
185
+ * Any field which has already been composed is ignored.
186
+ * All other fields are optimistically assumed to not have any changes in the other input changeset.
187
+ *
188
+ * @param change - The changeset which contains the affected fields.
189
+ * This should be one of the two changesets being composed.
190
+ * @param areBaseFields - Whether the affected fields are part of the base changeset.
191
+ * If not, they are assumed to be part of the new changeset.
192
+ * @param affectedFields - The set of fields to process.
193
+ */
194
+ composeAffectedFields(table, change, areBaseFields, affectedFields, composedFields, composedNodes, genId, metadata) {
195
+ for (const fieldIdKey of affectedFields.keys()) {
196
+ const fieldId = normalizeFieldId(fieldIdFromFieldIdKey(fieldIdKey), change.nodeAliases);
197
+ const fieldChange = fieldChangeFromId(change.fieldChanges, change.nodeChanges, fieldId);
198
+ if (table.fieldToContext.has(fieldChange) ||
199
+ table.newFieldToBaseField.has(fieldChange)) {
200
+ // This function handles fields which were not part of the intersection of the two changesets but which need to be updated anyway.
201
+ // If we've already processed this field then either it is up to date
202
+ // or there is pending inval which will be handled in processInvalidatedCompositions.
203
+ continue;
204
+ }
205
+ const emptyChange = this.createEmptyFieldChange(fieldChange.fieldKind);
206
+ const [change1, change2] = areBaseFields
207
+ ? [fieldChange, emptyChange]
208
+ : [emptyChange, fieldChange];
209
+ const composedField = this.composeFieldChanges(change1, change2, genId, table, metadata);
210
+ if (fieldId.nodeId === undefined) {
211
+ composedFields.set(fieldId.field, composedField);
212
+ continue;
213
+ }
214
+ const nodeId = getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId;
215
+ let nodeChangeset = nodeChangeFromId(composedNodes, nodeId);
216
+ if (!table.composedNodes.has(nodeChangeset)) {
217
+ nodeChangeset = cloneNodeChangeset(nodeChangeset);
218
+ setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
219
+ }
220
+ if (nodeChangeset.fieldChanges === undefined) {
221
+ nodeChangeset.fieldChanges = new Map();
222
+ }
223
+ nodeChangeset.fieldChanges.set(fieldId.field, composedField);
224
+ }
225
+ affectedFields.clear();
116
226
  }
117
227
  composeFieldMaps(change1, change2, genId, crossFieldTable, revisionMetadata) {
118
228
  const composedFields = new Map();
119
- const fields = new Set();
120
- for (const field of change1?.keys() ?? []) {
121
- fields.add(field);
122
- }
123
- for (const field of change2?.keys() ?? []) {
124
- fields.add(field);
125
- }
126
- for (const field of fields) {
127
- const fieldChange1 = change1?.get(field);
128
- const fieldChange2 = change2?.get(field);
129
- const { fieldKind, change1: normalizedFieldChange1, change2: normalizedFieldChange2, } = this.normalizeFieldChanges(fieldChange1, fieldChange2, genId, revisionMetadata);
130
- const manager = newCrossFieldManager(crossFieldTable, fieldChange1 ?? fieldChange2);
131
- const change1Normalized = normalizedFieldChange1 ?? fieldKind.changeHandler.createEmpty();
132
- const change2Normalized = normalizedFieldChange2 ?? fieldKind.changeHandler.createEmpty();
133
- const composedChange = fieldKind.changeHandler.rebaser.compose(change1Normalized, change2Normalized, (child1, child2) => {
134
- crossFieldTable.nodeIdPairs.push([child1, child2]);
135
- if (child2 !== undefined) {
136
- addToNestedSet(crossFieldTable.nodeIds, child2.revision, child2.localId);
137
- }
138
- return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
139
- }, genId, manager, revisionMetadata);
140
- const composedField = {
141
- fieldKind: fieldKind.identifier,
142
- change: brand(composedChange),
143
- };
144
- const fieldKey = fieldChange1 ?? fieldChange2 ?? fail("At least one field should have changes");
145
- crossFieldTable.fieldToContext.set(fieldKey, {
146
- change1: change1Normalized,
147
- change2: change2Normalized,
148
- composedChange: composedField,
149
- });
150
- // TODO: Could optimize by checking that composedField is non-empty
229
+ if (change1 === undefined || change2 === undefined) {
230
+ return change1 ?? change2 ?? composedFields;
231
+ }
232
+ for (const [field, fieldChange1] of change1) {
233
+ const fieldChange2 = change2.get(field);
234
+ const composedField = fieldChange2 !== undefined
235
+ ? this.composeFieldChanges(fieldChange1, fieldChange2, genId, crossFieldTable, revisionMetadata)
236
+ : fieldChange1;
151
237
  composedFields.set(field, composedField);
152
238
  }
239
+ for (const [field, fieldChange2] of change2) {
240
+ if (change1 === undefined || !change1.has(field)) {
241
+ composedFields.set(field, fieldChange2);
242
+ }
243
+ }
153
244
  return composedFields;
154
245
  }
155
- composeNodesById(nodeChanges1, nodeChanges2, composedNodeChanges, id1, id2, idAllocator, crossFieldTable, revisionMetadata) {
156
- const nodeChangeset1 = id1 !== undefined
157
- ? tryGetFromNestedMap(nodeChanges1, id1.revision, id1.localId) ??
158
- fail("Unknown node ID")
159
- : {};
160
- const nodeChangeset2 = id2 !== undefined
161
- ? tryGetFromNestedMap(nodeChanges2, id2.revision, id2.localId) ??
162
- fail("Unknown node ID")
163
- : {};
246
+ /**
247
+ * Returns the composition of the two input fields.
248
+ *
249
+ * Any nodes in this field which were modified by both changesets
250
+ * will be added to `crossFieldTable.pendingCompositions.nodeIdsToCompose`.
251
+ *
252
+ * Any fields which had cross-field information sent to them as part of this field composition
253
+ * will be added to either `affectedBaseFields` or `affectedNewFields` in `crossFieldTable.pendingCompositions`.
254
+ *
255
+ * Any composed `FieldChange` which is invalidated by new cross-field information will be added to `crossFieldTable.invalidatedFields`.
256
+ */
257
+ composeFieldChanges(change1, change2, idAllocator, crossFieldTable, revisionMetadata) {
258
+ const { fieldKind, changeHandler, change1: change1Normalized, change2: change2Normalized, } = this.normalizeFieldChanges(change1, change2, idAllocator, revisionMetadata);
259
+ const manager = new ComposeManager(crossFieldTable, change1);
260
+ const composedChange = changeHandler.rebaser.compose(change1Normalized, change2Normalized, (child1, child2) => {
261
+ if (child1 !== undefined && child2 !== undefined) {
262
+ setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
263
+ crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
264
+ }
265
+ return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
266
+ }, idAllocator, manager, revisionMetadata);
267
+ const composedField = {
268
+ fieldKind,
269
+ change: brand(composedChange),
270
+ };
271
+ crossFieldTable.fieldToContext.set(change1, {
272
+ change1: change1Normalized,
273
+ change2: change2Normalized,
274
+ composedChange: composedField,
275
+ });
276
+ crossFieldTable.newFieldToBaseField.set(change2, change1);
277
+ return composedField;
278
+ }
279
+ composeNodesById(nodeChanges1, nodeChanges2, composedNodes, composedNodeToParent, nodeAliases, id1, id2, idAllocator, crossFieldTable, revisionMetadata) {
280
+ const nodeChangeset1 = nodeChangeFromId(nodeChanges1, id1);
281
+ const nodeChangeset2 = nodeChangeFromId(nodeChanges2, id2);
164
282
  const composedNodeChangeset = this.composeNodeChanges(nodeChangeset1, nodeChangeset2, idAllocator, crossFieldTable, revisionMetadata);
165
- const nodeId = id1 ?? id2 ?? fail("Should not compose two undefined node IDs");
166
- setInNestedMap(composedNodeChanges, nodeId.revision, nodeId.localId, composedNodeChangeset);
283
+ setInChangeAtomIdMap(composedNodes, id1, composedNodeChangeset);
284
+ if (!areEqualChangeAtomIds(id1, id2)) {
285
+ deleteFromNestedMap(composedNodes, id2.revision, id2.localId);
286
+ deleteFromNestedMap(composedNodeToParent, id2.revision, id2.localId);
287
+ setInChangeAtomIdMap(nodeAliases, id2, id1);
288
+ // We need to delete id1 to avoid forming a cycle in case id1 already had an alias.
289
+ deleteFromNestedMap(nodeAliases, id1.revision, id1.localId);
290
+ }
291
+ crossFieldTable.composedNodes.add(composedNodeChangeset);
167
292
  }
168
293
  composeNodeChanges(change1, change2, genId, crossFieldTable, revisionMetadata) {
169
- const nodeExistsConstraint = change1?.nodeExistsConstraint ?? change2?.nodeExistsConstraint;
170
- const composedFieldChanges = this.composeFieldMaps(change1?.fieldChanges, change2?.fieldChanges, genId, crossFieldTable, revisionMetadata);
294
+ const nodeExistsConstraint = change1.nodeExistsConstraint ?? change2.nodeExistsConstraint;
295
+ const composedFieldChanges = this.composeFieldMaps(change1.fieldChanges, change2.fieldChanges, genId, crossFieldTable, revisionMetadata);
171
296
  const composedNodeChange = {};
172
297
  if (composedFieldChanges.size > 0) {
173
298
  composedNodeChange.fieldChanges = composedFieldChanges;
@@ -190,23 +315,21 @@ export class ModularChangeFamily {
190
315
  // Destroys only occur in rollback changesets, which are never inverted.
191
316
  assert(change.change.destroys === undefined, 0x89a /* Unexpected destroys in change to invert */);
192
317
  if ((change.change.constraintViolationCount ?? 0) > 0) {
193
- return makeModularChangeset(undefined, undefined, change.change.maxId, [], undefined, undefined, destroys);
318
+ return makeModularChangeset(undefined, undefined, undefined, undefined, undefined, change.change.maxId, [], undefined, undefined, destroys);
194
319
  }
195
- const idState = { maxId: change.change.maxId ?? -1 };
196
- // This idState is used for the whole of the IdAllocator's lifetime, which allows
197
- // this function to read the updated idState.maxId after more IDs are allocated.
198
- // TODO: add a getMax function to IdAllocator to make for a clearer contract.
199
- const genId = idAllocatorFromState(idState);
320
+ const genId = idAllocatorFromMaxId(change.change.maxId ?? -1);
321
+ const invertedNodeToParent = cloneNestedMap(change.change.nodeToParent);
200
322
  const crossFieldTable = {
201
323
  ...newCrossFieldTable(),
202
324
  originalFieldToContext: new Map(),
325
+ invertedNodeToParent,
203
326
  };
204
327
  const { revInfos } = getRevInfoFromTaggedChanges([change]);
205
328
  const revisionMetadata = revisionMetadataSourceFromInfo(revInfos);
206
- const invertedFields = this.invertFieldMap(change.change.fieldChanges, isRollback, genId, crossFieldTable, revisionMetadata);
329
+ const invertedFields = this.invertFieldMap(change.change.fieldChanges, undefined, isRollback, genId, crossFieldTable, revisionMetadata);
207
330
  const invertedNodes = new Map();
208
331
  forEachInNestedMap(change.change.nodeChanges, (nodeChangeset, revision, localId) => {
209
- setInNestedMap(invertedNodes, revision, localId, this.invertNodeChange(nodeChangeset, isRollback, genId, crossFieldTable, revisionMetadata));
332
+ setInNestedMap(invertedNodes, revision, localId, this.invertNodeChange(nodeChangeset, { revision, localId }, isRollback, genId, crossFieldTable, revisionMetadata));
210
333
  });
211
334
  if (crossFieldTable.invalidatedFields.size > 0) {
212
335
  const fieldsToUpdate = crossFieldTable.invalidatedFields;
@@ -215,17 +338,19 @@ export class ModularChangeFamily {
215
338
  const originalFieldChange = fieldChange.change;
216
339
  const context = crossFieldTable.originalFieldToContext.get(fieldChange);
217
340
  assert(context !== undefined, 0x851 /* Should have context for every invalidated field */);
218
- const { invertedField } = context;
219
- const amendedChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.invert(originalFieldChange, isRollback, genId, newCrossFieldManager(crossFieldTable, fieldChange), revisionMetadata);
341
+ const { invertedField, fieldId } = context;
342
+ const amendedChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.invert(originalFieldChange, isRollback, genId, new InvertManager(crossFieldTable, fieldChange, fieldId), revisionMetadata);
220
343
  invertedField.change = brand(amendedChange);
221
344
  }
222
345
  }
223
- return makeModularChangeset(invertedFields, invertedNodes, idState.maxId, [], change.change.constraintViolationCount, undefined, destroys);
346
+ const crossFieldKeys = this.makeCrossFieldKeyTable(invertedFields, invertedNodes);
347
+ return makeModularChangeset(invertedFields, invertedNodes, invertedNodeToParent, change.change.nodeAliases, crossFieldKeys, genId.getMaxId(), [], change.change.constraintViolationCount, undefined, destroys);
224
348
  }
225
- invertFieldMap(changes, isRollback, genId, crossFieldTable, revisionMetadata) {
349
+ invertFieldMap(changes, parentId, isRollback, genId, crossFieldTable, revisionMetadata) {
226
350
  const invertedFields = new Map();
227
351
  for (const [field, fieldChange] of changes) {
228
- const manager = newCrossFieldManager(crossFieldTable, fieldChange);
352
+ const fieldId = { nodeId: parentId, field };
353
+ const manager = new InvertManager(crossFieldTable, fieldChange, fieldId);
229
354
  const invertedChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.invert(fieldChange.change, isRollback, genId, manager, revisionMetadata);
230
355
  const invertedFieldChange = {
231
356
  ...fieldChange,
@@ -233,15 +358,16 @@ export class ModularChangeFamily {
233
358
  };
234
359
  invertedFields.set(field, invertedFieldChange);
235
360
  crossFieldTable.originalFieldToContext.set(fieldChange, {
361
+ fieldId,
236
362
  invertedField: invertedFieldChange,
237
363
  });
238
364
  }
239
365
  return invertedFields;
240
366
  }
241
- invertNodeChange(change, isRollback, genId, crossFieldTable, revisionMetadata) {
367
+ invertNodeChange(change, id, isRollback, genId, crossFieldTable, revisionMetadata) {
242
368
  const inverse = {};
243
369
  if (change.fieldChanges !== undefined) {
244
- inverse.fieldChanges = this.invertFieldMap(change.fieldChanges, isRollback, genId, crossFieldTable, revisionMetadata);
370
+ inverse.fieldChanges = this.invertFieldMap(change.fieldChanges, id, isRollback, genId, crossFieldTable, revisionMetadata);
245
371
  }
246
372
  return inverse;
247
373
  }
@@ -252,9 +378,15 @@ export class ModularChangeFamily {
252
378
  const genId = idAllocatorFromState(idState);
253
379
  const crossFieldTable = {
254
380
  ...newCrossFieldTable(),
255
- fieldToContext: new Map(),
256
- rebasedNodeCache: new Map(),
381
+ newChange: change,
382
+ baseChange: over.change,
383
+ baseFieldToContext: new Map(),
384
+ baseToRebasedNodeId: new Map(),
385
+ rebasedFields: new Set(),
386
+ rebasedNodeToParent: cloneNestedMap(change.nodeToParent),
387
+ rebasedCrossFieldKeys: brand(change.crossFieldKeys.clone()),
257
388
  nodeIdPairs: [],
389
+ affectedBaseFields: newBTree(),
258
390
  };
259
391
  let constraintState = newConstraintState(change.constraintViolationCount ?? 0);
260
392
  const getBaseRevisions = () => revisionInfoFromTaggedChange(over).map((info) => info.revision);
@@ -263,108 +395,198 @@ export class ModularChangeFamily {
263
395
  getRevisionToRebase: () => taggedChange.revision,
264
396
  getBaseRevisions,
265
397
  };
266
- const rebasedFields = this.rebaseFieldMap(change.fieldChanges, over.change.fieldChanges, genId, crossFieldTable, rebaseMetadata);
267
- const rebasedNodes = new Map();
268
- for (const [newId, baseId] of crossFieldTable.nodeIdPairs) {
269
- const newNodeChange = newId !== undefined
270
- ? tryGetFromNestedMap(change.nodeChanges, newId.revision, newId.localId)
271
- : undefined;
272
- const baseNodeChange = baseId !== undefined
273
- ? tryGetFromNestedMap(over.change.nodeChanges, baseId.revision, baseId.localId) ??
274
- fail("Unknown node ID")
275
- : {};
276
- const rebasedNode = this.rebaseNodeChange(newNodeChange, baseNodeChange, genId, crossFieldTable, rebaseMetadata);
277
- if (rebasedNode !== undefined) {
278
- const nodeId = newId ?? baseId ?? fail("Should not have two undefined IDs");
279
- setInNestedMap(rebasedNodes, nodeId.revision, nodeId.localId, rebasedNode);
280
- }
281
- }
398
+ const rebasedNodes = cloneNestedMap(change.nodeChanges);
399
+ const rebasedFields = this.rebaseIntersectingFields(crossFieldTable, rebasedNodes, genId, constraintState, rebaseMetadata);
400
+ this.rebaseFieldsWithoutNewChanges(rebasedFields, rebasedNodes, crossFieldTable, genId, rebaseMetadata);
282
401
  if (crossFieldTable.invalidatedFields.size > 0) {
283
402
  const fieldsToUpdate = crossFieldTable.invalidatedFields;
284
403
  crossFieldTable.invalidatedFields = new Set();
285
404
  constraintState = newConstraintState(change.constraintViolationCount ?? 0);
286
405
  for (const field of fieldsToUpdate) {
287
- // TODO: Should we copy the context table out before this loop?
288
- const context = crossFieldTable.fieldToContext.get(field);
406
+ const context = crossFieldTable.baseFieldToContext.get(field);
289
407
  assert(context !== undefined, 0x852 /* Every field should have a context */);
290
- const { fieldKind, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(context.newChange, context.baseChange, genId, revisionMetadata);
291
- context.rebasedChange.change = fieldKind.changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, (curr, base, existenceState) => curr ?? base, genId, newCrossFieldManager(crossFieldTable, field), rebaseMetadata);
408
+ const { changeHandler, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(context.newChange, context.baseChange, genId, revisionMetadata);
409
+ const rebaseChild = (curr, base) => {
410
+ if (curr !== undefined) {
411
+ return curr;
412
+ }
413
+ if (base !== undefined) {
414
+ for (const id of context.baseNodeIds) {
415
+ if (areEqualChangeAtomIds(base, id)) {
416
+ return base;
417
+ }
418
+ }
419
+ }
420
+ return undefined;
421
+ };
422
+ context.rebasedChange.change = brand(changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, new RebaseManager(crossFieldTable, field, context.fieldId), rebaseMetadata));
292
423
  }
293
424
  }
294
425
  this.updateConstraintsForFields(rebasedFields, NodeAttachState.Attached, constraintState, rebasedNodes);
295
- return makeModularChangeset(this.pruneFieldMap(rebasedFields, rebasedNodes), rebasedNodes, idState.maxId, change.revisions, constraintState.violationCount, change.builds, change.destroys, change.refreshers);
426
+ return makeModularChangeset(this.pruneFieldMap(rebasedFields, rebasedNodes), rebasedNodes, crossFieldTable.rebasedNodeToParent, change.nodeAliases, crossFieldTable.rebasedCrossFieldKeys, idState.maxId, change.revisions, constraintState.violationCount, change.builds, change.destroys, change.refreshers);
296
427
  }
297
- rebaseFieldMap(change, over, genId, crossFieldTable, revisionMetadata) {
298
- const rebasedFields = new Map();
299
- // Rebase fields contained in the base changeset
300
- for (const [field, baseChanges] of over) {
301
- const fieldChange = change.get(field) ?? {
302
- fieldKind: genericFieldKind.identifier,
303
- change: brand(newGenericChangeset()),
428
+ // This performs a first pass on all fields which have both new and base changes.
429
+ // TODO: Can we also handle additional passes in this method?
430
+ rebaseIntersectingFields(crossFieldTable, rebasedNodes, genId, constraintState, metadata) {
431
+ const change = crossFieldTable.newChange;
432
+ const baseChange = crossFieldTable.baseChange;
433
+ const rebasedFields = this.rebaseFieldMap(change.fieldChanges, baseChange.fieldChanges, undefined, genId, crossFieldTable, metadata);
434
+ // This loop processes all fields which have both base and new changes.
435
+ // Note that the call to `rebaseNodeChange` can add entries to `crossFieldTable.nodeIdPairs`.
436
+ for (const [newId, baseId, _attachState] of crossFieldTable.nodeIdPairs) {
437
+ const rebasedNode = this.rebaseNodeChange(newId, baseId, genId, crossFieldTable, metadata, constraintState);
438
+ setInChangeAtomIdMap(rebasedNodes, newId, rebasedNode);
439
+ }
440
+ return rebasedFields;
441
+ }
442
+ // This processes fields which have no new changes but have been invalidated by another field.
443
+ rebaseFieldsWithoutNewChanges(rebasedFields, rebasedNodes, crossFieldTable, genId, metadata) {
444
+ const baseChange = crossFieldTable.baseChange;
445
+ for (const [revision, localId, fieldKey] of crossFieldTable.affectedBaseFields.keys()) {
446
+ const baseNodeId = localId !== undefined
447
+ ? normalizeNodeId({ revision, localId }, baseChange.nodeAliases)
448
+ : undefined;
449
+ const baseFieldChange = fieldMapFromNodeId(baseChange.fieldChanges, baseChange.nodeChanges, baseNodeId).get(fieldKey);
450
+ assert(baseFieldChange !== undefined, "Cross field key registered for empty field");
451
+ if (crossFieldTable.baseFieldToContext.has(baseFieldChange)) {
452
+ // This field has already been processed because there were changes to rebase.
453
+ continue;
454
+ }
455
+ // This field has no changes in the new changeset, otherwise it would have been added to
456
+ // `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
457
+ const rebaseChild = (child, baseChild, stateChange) => {
458
+ assert(child === undefined, "There should be no new changes in this field");
459
+ return undefined;
304
460
  };
305
- const { fieldKind, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(fieldChange, baseChanges, genId, revisionMetadata);
306
- const manager = newCrossFieldManager(crossFieldTable, fieldChange);
307
- const rebaseChild = (child, baseChild, _attachState) => {
308
- crossFieldTable.nodeIdPairs.push([child, baseChild]);
309
- return (child ??
310
- // The fact `child` is undefined means that the changeset to rebase does not include changes for
311
- // this node or its descendants. However, it's possible that it will after rebasing.
312
- // In that case, we will need a NodeId to represent these changes under in the rebased changeset.
313
- // We adopt `baseChild` for this purpose.
314
- baseChild ??
315
- fail("Should not have two undefined node IDs"));
461
+ const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
462
+ const fieldChange = {
463
+ ...baseFieldChange,
464
+ change: brand(handler.createEmpty()),
316
465
  };
317
- const rebasedField = fieldKind.changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, manager, revisionMetadata);
466
+ const rebasedNodeId = baseNodeId !== undefined
467
+ ? rebasedNodeIdFromBaseNodeId(crossFieldTable, baseNodeId)
468
+ : undefined;
469
+ const fieldId = { nodeId: rebasedNodeId, field: fieldKey };
470
+ const rebasedField = handler.rebaser.rebase(fieldChange.change, baseFieldChange.change, rebaseChild, genId, new RebaseManager(crossFieldTable, baseFieldChange, fieldId), metadata);
318
471
  const rebasedFieldChange = {
319
- fieldKind: fieldKind.identifier,
472
+ ...baseFieldChange,
320
473
  change: brand(rebasedField),
321
474
  };
322
- rebasedFields.set(field, rebasedFieldChange);
323
- crossFieldTable.fieldToContext.set(fieldChange, {
324
- baseChange: baseChanges,
475
+ // TODO: Deduplicate
476
+ crossFieldTable.baseFieldToContext.set(baseFieldChange, {
325
477
  newChange: fieldChange,
478
+ baseChange: baseFieldChange,
326
479
  rebasedChange: rebasedFieldChange,
480
+ fieldId,
481
+ baseNodeIds: [],
327
482
  });
483
+ crossFieldTable.rebasedFields.add(rebasedFieldChange);
484
+ this.attachRebasedField(rebasedFields, rebasedNodes, crossFieldTable, rebasedFieldChange, fieldId, genId, metadata);
485
+ }
486
+ }
487
+ attachRebasedField(rebasedFields, rebasedNodes, table, rebasedField, { nodeId, field: fieldKey }, idAllocator, metadata) {
488
+ if (nodeId === undefined) {
489
+ rebasedFields.set(fieldKey, rebasedField);
490
+ return;
491
+ }
492
+ const rebasedNode = getFromChangeAtomIdMap(rebasedNodes, nodeId);
493
+ if (rebasedNode !== undefined) {
494
+ if (rebasedNode.fieldChanges === undefined) {
495
+ rebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
496
+ return;
497
+ }
498
+ assert(!rebasedNode.fieldChanges.has(fieldKey), "Expected an empty field");
499
+ rebasedNode.fieldChanges.set(fieldKey, rebasedField);
500
+ return;
501
+ }
502
+ const newNode = {
503
+ fieldChanges: new Map([[fieldKey, rebasedField]]),
504
+ };
505
+ setInChangeAtomIdMap(rebasedNodes, nodeId, newNode);
506
+ setInChangeAtomIdMap(table.baseToRebasedNodeId, nodeId, nodeId);
507
+ const parentFieldId = getParentFieldId(table.baseChange, nodeId);
508
+ this.attachRebasedNode(rebasedFields, rebasedNodes, table, nodeId, parentFieldId, idAllocator, metadata);
509
+ }
510
+ attachRebasedNode(rebasedFields, rebasedNodes, table, baseNodeId, parentFieldIdBase, idAllocator, metadata) {
511
+ const baseFieldChange = fieldChangeFromId(table.baseChange.fieldChanges, table.baseChange.nodeChanges, parentFieldIdBase);
512
+ const rebasedFieldId = rebasedFieldIdFromBaseId(table, parentFieldIdBase);
513
+ setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, rebasedFieldId);
514
+ const context = table.baseFieldToContext.get(baseFieldChange);
515
+ if (context !== undefined) {
516
+ // We've already processed this field.
517
+ // The new child node can be attached when processing invalidated fields.
518
+ context.baseNodeIds.push(baseNodeId);
519
+ table.invalidatedFields.add(baseFieldChange);
520
+ return;
328
521
  }
329
- // Rebase the fields of the new changeset which don't have a corresponding base field.
522
+ const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
523
+ const fieldChange = {
524
+ ...baseFieldChange,
525
+ change: brand(handler.createEmpty()),
526
+ };
527
+ const rebasedChangeset = handler.rebaser.rebase(handler.createEmpty(), baseFieldChange.change, (_idNew, idBase) => idBase !== undefined && areEqualChangeAtomIds(idBase, baseNodeId)
528
+ ? baseNodeId
529
+ : undefined, idAllocator, new RebaseManager(table, baseFieldChange, rebasedFieldId), metadata);
530
+ const rebasedField = { ...baseFieldChange, change: brand(rebasedChangeset) };
531
+ table.rebasedFields.add(rebasedField);
532
+ table.baseFieldToContext.set(baseFieldChange, {
533
+ newChange: fieldChange,
534
+ baseChange: baseFieldChange,
535
+ rebasedChange: rebasedField,
536
+ fieldId: rebasedFieldId,
537
+ baseNodeIds: [],
538
+ });
539
+ this.attachRebasedField(rebasedFields, rebasedNodes, table, rebasedField, rebasedFieldId, idAllocator, metadata);
540
+ }
541
+ rebaseFieldMap(change, over, parentId, genId, crossFieldTable, revisionMetadata) {
542
+ const rebasedFields = new Map();
543
+ const rebaseChild = (child, baseChild, stateChange) => {
544
+ if (child !== undefined && baseChild !== undefined) {
545
+ crossFieldTable.nodeIdPairs.push([child, baseChild, stateChange]);
546
+ }
547
+ return child;
548
+ };
330
549
  for (const [field, fieldChange] of change) {
331
- if (!over?.has(field)) {
332
- const baseChanges = {
333
- fieldKind: genericFieldKind.identifier,
334
- change: brand(newGenericChangeset()),
335
- };
336
- const { fieldKind, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(fieldChange, baseChanges, genId, revisionMetadata);
337
- // TODO: Don't we need to add an entry in the context table?
338
- const manager = newCrossFieldManager(crossFieldTable, fieldChange);
339
- const rebasedChangeset = fieldKind.changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, (child, baseChild) => {
340
- assert(baseChild === undefined, 0x5b6 /* This field should not have any base changes */);
341
- crossFieldTable.nodeIdPairs.push([child, undefined]);
342
- return child;
343
- }, genId, manager, revisionMetadata);
344
- const rebasedFieldChange = {
345
- fieldKind: fieldKind.identifier,
346
- change: brand(rebasedChangeset),
347
- };
348
- rebasedFields.set(field, rebasedFieldChange);
550
+ const fieldId = { nodeId: parentId, field };
551
+ const baseChange = over.get(field);
552
+ if (baseChange === undefined) {
553
+ rebasedFields.set(field, fieldChange);
554
+ continue;
349
555
  }
556
+ const { fieldKind, changeHandler, change1: fieldChangeset, change2: baseChangeset, } = this.normalizeFieldChanges(fieldChange, baseChange, genId, revisionMetadata);
557
+ const manager = new RebaseManager(crossFieldTable, baseChange, fieldId);
558
+ const rebasedField = changeHandler.rebaser.rebase(fieldChangeset, baseChangeset, rebaseChild, genId, manager, revisionMetadata);
559
+ const rebasedFieldChange = {
560
+ fieldKind,
561
+ change: brand(rebasedField),
562
+ };
563
+ rebasedFields.set(field, rebasedFieldChange);
564
+ crossFieldTable.baseFieldToContext.set(baseChange, {
565
+ baseChange,
566
+ newChange: fieldChange,
567
+ rebasedChange: rebasedFieldChange,
568
+ fieldId,
569
+ baseNodeIds: [],
570
+ });
571
+ crossFieldTable.rebasedFields.add(rebasedFieldChange);
350
572
  }
351
573
  return rebasedFields;
352
574
  }
353
- rebaseNodeChange(change, over, genId, crossFieldTable, revisionMetadata) {
354
- const key = change ?? over;
355
- if (key === undefined) {
356
- return undefined;
357
- }
575
+ rebaseNodeChange(newId, baseId, genId, crossFieldTable, revisionMetadata, constraintState) {
576
+ const change = nodeChangeFromId(crossFieldTable.newChange.nodeChanges, newId);
577
+ const over = nodeChangeFromId(crossFieldTable.baseChange.nodeChanges, baseId);
358
578
  const baseMap = over?.fieldChanges ?? new Map();
359
- const fieldChanges = this.rebaseFieldMap(change?.fieldChanges ?? new Map(), baseMap, genId, crossFieldTable, revisionMetadata);
579
+ const fieldChanges = change.fieldChanges !== undefined && over.fieldChanges !== undefined
580
+ ? this.rebaseFieldMap(change?.fieldChanges ?? new Map(), baseMap, newId, genId, crossFieldTable, revisionMetadata)
581
+ : change.fieldChanges;
360
582
  const rebasedChange = {};
361
- if (fieldChanges.size > 0) {
583
+ if (fieldChanges !== undefined && fieldChanges.size > 0) {
362
584
  rebasedChange.fieldChanges = fieldChanges;
363
585
  }
364
586
  if (change?.nodeExistsConstraint !== undefined) {
365
587
  rebasedChange.nodeExistsConstraint = change.nodeExistsConstraint;
366
588
  }
367
- crossFieldTable.rebasedNodeCache.set(key, rebasedChange);
589
+ setInChangeAtomIdMap(crossFieldTable.baseToRebasedNodeId, baseId, newId);
368
590
  return rebasedChange;
369
591
  }
370
592
  updateConstraintsForFields(fields, parentAttachState, constraintState, nodes) {
@@ -396,6 +618,9 @@ export class ModularChangeFamily {
396
618
  }
397
619
  }
398
620
  pruneFieldMap(changeset, nodeMap) {
621
+ if (changeset === undefined) {
622
+ return undefined;
623
+ }
399
624
  const prunedChangeset = new Map();
400
625
  for (const [field, fieldChange] of changeset) {
401
626
  const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
@@ -407,8 +632,7 @@ export class ModularChangeFamily {
407
632
  return prunedChangeset.size > 0 ? prunedChangeset : undefined;
408
633
  }
409
634
  pruneNodeChange(nodeId, nodeMap) {
410
- const changeset = tryGetFromNestedMap(nodeMap, nodeId.revision, nodeId.localId);
411
- assert(changeset !== undefined, 0x930 /* Unknown node ID */);
635
+ const changeset = nodeChangeFromId(nodeMap, nodeId);
412
636
  const prunedFields = changeset.fieldChanges !== undefined
413
637
  ? this.pruneFieldMap(changeset.fieldChanges, nodeMap)
414
638
  : undefined;
@@ -421,7 +645,7 @@ export class ModularChangeFamily {
421
645
  return undefined;
422
646
  }
423
647
  else {
424
- setInNestedMap(nodeMap, nodeId.revision, nodeId.localId, prunedChange);
648
+ setInChangeAtomIdMap(nodeMap, nodeId, prunedChange);
425
649
  return nodeId;
426
650
  }
427
651
  }
@@ -435,10 +659,19 @@ export class ModularChangeFamily {
435
659
  id,
436
660
  this.replaceNodeChangesetRevisions(nodeChangeset, oldRevisions, newRevision),
437
661
  ]));
662
+ const updatedNodeToParent = nestedMapFromFlatList(nestedMapToFlatList(change.nodeToParent).map(([revision, id, fieldId]) => [
663
+ replaceRevision(revision, oldRevisions, newRevision),
664
+ id,
665
+ replaceFieldIdRevision(normalizeFieldId(fieldId, change.nodeAliases), oldRevisions, newRevision),
666
+ ]));
438
667
  const updated = {
439
668
  ...change,
440
669
  fieldChanges: updatedFields,
441
670
  nodeChanges: updatedNodes,
671
+ nodeToParent: updatedNodeToParent,
672
+ // We've updated all references to old node IDs, so we no longer need an alias table.
673
+ nodeAliases: new Map(),
674
+ crossFieldKeys: replaceCrossFieldKeyTableRevisions(change.crossFieldKeys, oldRevisions, newRevision, change.nodeAliases),
442
675
  };
443
676
  if (change.builds !== undefined) {
444
677
  updated.builds = replaceIdMapRevisions(change.builds, oldRevisions, newRevision);
@@ -471,16 +704,62 @@ export class ModularChangeFamily {
471
704
  replaceFieldMapRevisions(fields, oldRevisions, newRevision) {
472
705
  const updatedFields = new Map();
473
706
  for (const [field, fieldChange] of fields) {
474
- const updatedFieldChange = getFieldKind(this.fieldKinds, fieldChange.fieldKind).changeHandler.rebaser.replaceRevisions(fieldChange.change, oldRevisions, newRevision);
475
- updatedFields.set(field, { ...fieldChange, change: updatedFieldChange });
707
+ const updatedFieldChange = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).rebaser.replaceRevisions(fieldChange.change, oldRevisions, newRevision);
708
+ updatedFields.set(field, { ...fieldChange, change: brand(updatedFieldChange) });
476
709
  }
477
710
  return updatedFields;
478
711
  }
712
+ makeCrossFieldKeyTable(fields, nodes) {
713
+ const keys = newCrossFieldKeyTable();
714
+ this.populateCrossFieldKeyTableForFieldMap(keys, fields, undefined);
715
+ forEachInNestedMap(nodes, (node, revision, localId) => {
716
+ if (node.fieldChanges !== undefined) {
717
+ this.populateCrossFieldKeyTableForFieldMap(keys, node.fieldChanges, {
718
+ revision,
719
+ localId,
720
+ });
721
+ }
722
+ });
723
+ return keys;
724
+ }
725
+ populateCrossFieldKeyTableForFieldMap(table, fields, parent) {
726
+ for (const [fieldKey, fieldChange] of fields) {
727
+ const keys = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).getCrossFieldKeys(fieldChange.change);
728
+ for (const key of keys) {
729
+ table.set(key, { nodeId: parent, field: fieldKey });
730
+ }
731
+ }
732
+ }
479
733
  buildEditor(changeReceiver) {
480
- return new ModularEditBuilder(this, changeReceiver);
734
+ return new ModularEditBuilder(this, this.fieldKinds, changeReceiver);
735
+ }
736
+ createEmptyFieldChange(fieldKind) {
737
+ const emptyChange = getChangeHandler(this.fieldKinds, fieldKind).createEmpty();
738
+ return { fieldKind, change: brand(emptyChange) };
481
739
  }
482
740
  }
483
741
  ModularChangeFamily.emptyChange = makeModularChangeset();
742
+ function replaceCrossFieldKeyTableRevisions(table, oldRevisions, newRevision, nodeAliases) {
743
+ const updated = newBTree();
744
+ table.forEachPair(([target, revision, id, count], field) => {
745
+ const updatedKey = [
746
+ target,
747
+ replaceRevision(revision, oldRevisions, newRevision),
748
+ id,
749
+ count,
750
+ ];
751
+ const normalizedFieldId = normalizeFieldId(field, nodeAliases);
752
+ const updatedNodeId = normalizedFieldId.nodeId !== undefined
753
+ ? replaceAtomRevisions(normalizedFieldId.nodeId, oldRevisions, newRevision)
754
+ : undefined;
755
+ const updatedValue = {
756
+ ...normalizedFieldId,
757
+ nodeId: updatedNodeId,
758
+ };
759
+ updated.set(updatedKey, updatedValue);
760
+ });
761
+ return updated;
762
+ }
484
763
  function replaceRevision(revision, oldRevisions, newRevision) {
485
764
  return oldRevisions.has(revision) ? newRevision : revision;
486
765
  }
@@ -595,8 +874,7 @@ function* relevantRemovedRootsFromFields(change, nodeChanges, fieldKinds) {
595
874
  for (const [_, fieldChange] of change) {
596
875
  const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
597
876
  const delegate = function* (node) {
598
- const nodeChangeset = tryGetFromNestedMap(nodeChanges, node.revision, node.localId);
599
- assert(nodeChangeset !== undefined, 0x931 /* Unknown node ID */);
877
+ const nodeChangeset = nodeChangeFromId(nodeChanges, node);
600
878
  if (nodeChangeset.fieldChanges !== undefined) {
601
879
  yield* relevantRemovedRootsFromFields(nodeChangeset.fieldChanges, nodeChanges, fieldKinds);
602
880
  }
@@ -650,7 +928,7 @@ export function updateRefreshers(change, getDetachedNode, removedRoots, requireR
650
928
  }
651
929
  }
652
930
  const { fieldChanges, nodeChanges, maxId, revisions, constraintViolationCount, builds, destroys, } = change;
653
- return makeModularChangeset(fieldChanges, nodeChanges, maxId, revisions, constraintViolationCount, builds, destroys, refreshers);
931
+ return makeModularChangeset(fieldChanges, nodeChanges, change.nodeToParent, change.nodeAliases, change.crossFieldKeys, maxId, revisions, constraintViolationCount, builds, destroys, refreshers);
654
932
  }
655
933
  /**
656
934
  * @param change - The change to convert into a delta.
@@ -706,8 +984,7 @@ function intoDeltaImpl(change, nodeChanges, idAllocator, fieldKinds) {
706
984
  const delta = new Map();
707
985
  for (const [field, fieldChange] of change) {
708
986
  const deltaField = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(fieldChange.change, (childChange) => {
709
- const nodeChange = tryGetFromNestedMap(nodeChanges, childChange.revision, childChange.localId);
710
- assert(nodeChange !== undefined, 0x932 /* Unknown node ID */);
987
+ const nodeChange = nodeChangeFromId(nodeChanges, childChange);
711
988
  return deltaFromNodeChange(nodeChange, nodeChanges, idAllocator, fieldKinds);
712
989
  }, idAllocator);
713
990
  if (!isEmptyFieldChanges(deltaField)) {
@@ -764,12 +1041,20 @@ export function getFieldKind(fieldKinds, kind) {
764
1041
  export function getChangeHandler(fieldKinds, kind) {
765
1042
  return getFieldKind(fieldKinds, kind).changeHandler;
766
1043
  }
767
- function newComposeTable() {
1044
+ function newComposeTable(baseChange, newChange) {
768
1045
  return {
769
1046
  ...newCrossFieldTable(),
1047
+ baseChange,
1048
+ newChange,
770
1049
  fieldToContext: new Map(),
771
- nodeIds: new Map(),
772
- nodeIdPairs: [],
1050
+ newFieldToBaseField: new Map(),
1051
+ newToBaseNodeId: new Map(),
1052
+ composedNodes: new Set(),
1053
+ pendingCompositions: {
1054
+ nodeIdsToCompose: [],
1055
+ affectedBaseFields: newBTree(),
1056
+ affectedNewFields: newBTree(),
1057
+ },
773
1058
  };
774
1059
  }
775
1060
  function newCrossFieldTable() {
@@ -786,43 +1071,148 @@ function newConstraintState(violationCount) {
786
1071
  violationCount,
787
1072
  };
788
1073
  }
789
- function newCrossFieldManager(crossFieldTable, currentFieldKey, allowInval = true) {
790
- const getMap = (target) => target === CrossFieldTarget.Source ? crossFieldTable.srcTable : crossFieldTable.dstTable;
791
- const getDependents = (target) => target === CrossFieldTarget.Source
792
- ? crossFieldTable.srcDependents
793
- : crossFieldTable.dstDependents;
794
- const manager = {
795
- table: crossFieldTable,
796
- set: (target, revision, id, count, newValue, invalidateDependents) => {
797
- if (invalidateDependents && allowInval) {
798
- const lastChangedId = id + count - 1;
799
- let firstId = id;
800
- while (firstId <= lastChangedId) {
801
- const dependentEntry = getFirstFromCrossFieldMap(getDependents(target), revision, firstId, lastChangedId - firstId + 1);
802
- if (dependentEntry.value !== undefined) {
803
- crossFieldTable.invalidatedFields.add(dependentEntry.value);
804
- }
805
- firstId = brand(firstId + dependentEntry.length);
1074
+ class CrossFieldManagerI {
1075
+ constructor(crossFieldTable, currentFieldKey, allowInval = true) {
1076
+ this.crossFieldTable = crossFieldTable;
1077
+ this.currentFieldKey = currentFieldKey;
1078
+ this.allowInval = allowInval;
1079
+ }
1080
+ set(target, revision, id, count, newValue, invalidateDependents) {
1081
+ if (invalidateDependents && this.allowInval) {
1082
+ const lastChangedId = id + count - 1;
1083
+ let firstId = id;
1084
+ while (firstId <= lastChangedId) {
1085
+ const dependentEntry = getFirstFromCrossFieldMap(this.getDependents(target), revision, firstId, lastChangedId - firstId + 1);
1086
+ if (dependentEntry.value !== undefined) {
1087
+ this.crossFieldTable.invalidatedFields.add(dependentEntry.value);
806
1088
  }
1089
+ firstId = brand(firstId + dependentEntry.length);
807
1090
  }
808
- setInCrossFieldMap(getMap(target), revision, id, count, newValue);
809
- },
810
- get: (target, revision, id, count, addDependency) => {
811
- if (addDependency) {
812
- // We assume that if there is already an entry for this ID it is because
813
- // a field handler has called compose on the same node multiple times.
814
- // In this case we only want to update the latest version, so we overwrite the dependency.
815
- setInCrossFieldMap(getDependents(target), revision, id, count, currentFieldKey);
1091
+ }
1092
+ setInCrossFieldMap(this.getMap(target), revision, id, count, newValue);
1093
+ }
1094
+ get(target, revision, id, count, addDependency) {
1095
+ if (addDependency) {
1096
+ // We assume that if there is already an entry for this ID it is because
1097
+ // a field handler has called compose on the same node multiple times.
1098
+ // In this case we only want to update the latest version, so we overwrite the dependency.
1099
+ setInCrossFieldMap(this.getDependents(target), revision, id, count, this.currentFieldKey);
1100
+ }
1101
+ return getFirstFromCrossFieldMap(this.getMap(target), revision, id, count);
1102
+ }
1103
+ getMap(target) {
1104
+ return target === CrossFieldTarget.Source
1105
+ ? this.crossFieldTable.srcTable
1106
+ : this.crossFieldTable.dstTable;
1107
+ }
1108
+ getDependents(target) {
1109
+ return target === CrossFieldTarget.Source
1110
+ ? this.crossFieldTable.srcDependents
1111
+ : this.crossFieldTable.dstDependents;
1112
+ }
1113
+ }
1114
+ class InvertManager extends CrossFieldManagerI {
1115
+ constructor(table, field, fieldId, allowInval = true) {
1116
+ super(table, field, allowInval);
1117
+ this.fieldId = fieldId;
1118
+ }
1119
+ onMoveIn(id) {
1120
+ setInChangeAtomIdMap(this.table.invertedNodeToParent, id, this.fieldId);
1121
+ }
1122
+ moveKey(target, revision, id, count) {
1123
+ assert(false, "Keys should not be moved manually during invert");
1124
+ }
1125
+ get table() {
1126
+ return this.crossFieldTable;
1127
+ }
1128
+ }
1129
+ class RebaseManager extends CrossFieldManagerI {
1130
+ constructor(table, currentField, fieldId, allowInval = true) {
1131
+ super(table, currentField, allowInval);
1132
+ this.fieldId = fieldId;
1133
+ }
1134
+ set(target, revision, id, count, newValue, invalidateDependents) {
1135
+ if (invalidateDependents && this.allowInval) {
1136
+ const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, [
1137
+ target,
1138
+ revision,
1139
+ id,
1140
+ count,
1141
+ ]);
1142
+ assert(newFieldIds.length === 0, "TODO: Modifying a cross-field key from the new changeset is currently unsupported");
1143
+ const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, [
1144
+ target,
1145
+ revision,
1146
+ id,
1147
+ count,
1148
+ ]);
1149
+ assert(baseFieldIds.length > 0, "Cross field key not registered in base or new change");
1150
+ for (const baseFieldId of baseFieldIds) {
1151
+ this.table.affectedBaseFields.set([baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field], true);
816
1152
  }
817
- return getFirstFromCrossFieldMap(getMap(target), revision, id, count);
818
- },
819
- };
820
- return manager;
1153
+ }
1154
+ super.set(target, revision, id, count, newValue, invalidateDependents);
1155
+ }
1156
+ onMoveIn(id) {
1157
+ setInChangeAtomIdMap(this.table.rebasedNodeToParent, id, this.fieldId);
1158
+ }
1159
+ moveKey(target, revision, id, count) {
1160
+ setInCrossFieldKeyTable(this.table.rebasedCrossFieldKeys, target, revision, id, count, this.fieldId);
1161
+ }
1162
+ get table() {
1163
+ return this.crossFieldTable;
1164
+ }
1165
+ }
1166
+ // TODO: Deduplicate this with RebaseTable
1167
+ class ComposeManager extends CrossFieldManagerI {
1168
+ constructor(table, currentField, allowInval = true) {
1169
+ super(table, currentField, allowInval);
1170
+ }
1171
+ set(target, revision, id, count, newValue, invalidateDependents) {
1172
+ if (invalidateDependents && this.allowInval) {
1173
+ const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, [
1174
+ target,
1175
+ revision,
1176
+ id,
1177
+ count,
1178
+ ]);
1179
+ if (newFieldIds.length > 0) {
1180
+ for (const newFieldId of newFieldIds) {
1181
+ this.table.pendingCompositions.affectedNewFields.set([newFieldId.nodeId?.revision, newFieldId.nodeId?.localId, newFieldId.field], true);
1182
+ }
1183
+ }
1184
+ else {
1185
+ const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, [
1186
+ target,
1187
+ revision,
1188
+ id,
1189
+ count,
1190
+ ]);
1191
+ assert(baseFieldIds.length > 0, "Cross field key not registered in base or new change");
1192
+ for (const baseFieldId of baseFieldIds) {
1193
+ this.table.pendingCompositions.affectedBaseFields.set([baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field], true);
1194
+ }
1195
+ }
1196
+ }
1197
+ super.set(target, revision, id, count, newValue, invalidateDependents);
1198
+ }
1199
+ onMoveIn(id) {
1200
+ throw new Error("Method not implemented.");
1201
+ }
1202
+ moveKey(target, revision, id, count) {
1203
+ throw new Error("Method not implemented.");
1204
+ }
1205
+ get table() {
1206
+ return this.crossFieldTable;
1207
+ }
821
1208
  }
822
- function makeModularChangeset(fieldChanges = undefined, nodeChanges = undefined, maxId = -1, revisions = undefined, constraintViolationCount = undefined, builds, destroys, refreshers) {
1209
+ function makeModularChangeset(fieldChanges = undefined, nodeChanges = undefined, nodeToParent = undefined, nodeAliases = undefined, crossFieldKeys = undefined, maxId = -1, revisions = undefined, constraintViolationCount = undefined, builds, destroys, refreshers) {
823
1210
  const changeset = {
824
1211
  fieldChanges: fieldChanges ?? new Map(),
825
1212
  nodeChanges: nodeChanges ?? new Map(),
1213
+ nodeToParent: nodeToParent ?? new Map(),
1214
+ nodeAliases: nodeAliases ?? new Map(),
1215
+ crossFieldKeys: crossFieldKeys ?? newCrossFieldKeyTable(),
826
1216
  };
827
1217
  if (revisions !== undefined && revisions.length > 0) {
828
1218
  changeset.revisions = revisions;
@@ -845,8 +1235,9 @@ function makeModularChangeset(fieldChanges = undefined, nodeChanges = undefined,
845
1235
  return changeset;
846
1236
  }
847
1237
  export class ModularEditBuilder extends EditBuilder {
848
- constructor(family, changeReceiver) {
1238
+ constructor(family, fieldKinds, changeReceiver) {
849
1239
  super(family, changeReceiver);
1240
+ this.fieldKinds = fieldKinds;
850
1241
  this.transactionDepth = 0;
851
1242
  this.idAllocator = idAllocatorFromMaxId();
852
1243
  }
@@ -892,7 +1283,8 @@ export class ModularEditBuilder extends EditBuilder {
892
1283
  * @param maxId - the highest `ChangesetLocalId` used in this change
893
1284
  */
894
1285
  submitChange(field, fieldKind, change) {
895
- const modularChange = buildModularChangesetFromField(field, { fieldKind, change }, new Map(), this.idAllocator);
1286
+ const crossFieldKeys = getChangeHandler(this.fieldKinds, fieldKind).getCrossFieldKeys(change);
1287
+ const modularChange = buildModularChangesetFromField(field, { fieldKind, change }, new Map(), new Map(), newCrossFieldKeyTable(), this.idAllocator, crossFieldKeys);
896
1288
  this.applyChange(modularChange);
897
1289
  }
898
1290
  submitChanges(changes) {
@@ -901,11 +1293,11 @@ export class ModularEditBuilder extends EditBuilder {
901
1293
  }
902
1294
  buildChanges(changes) {
903
1295
  const changeMaps = changes.map((change) => makeAnonChange(change.type === "global"
904
- ? makeModularChangeset(undefined, undefined, this.idAllocator.getMaxId(), undefined, undefined, change.builds)
1296
+ ? makeModularChangeset(undefined, undefined, undefined, undefined, undefined, this.idAllocator.getMaxId(), undefined, undefined, change.builds)
905
1297
  : buildModularChangesetFromField(change.field, {
906
1298
  fieldKind: change.fieldKind,
907
1299
  change: change.change,
908
- }, new Map(), this.idAllocator)));
1300
+ }, new Map(), new Map(), newCrossFieldKeyTable(), this.idAllocator, getChangeHandler(this.fieldKinds, change.fieldKind).getCrossFieldKeys(change.change))));
909
1301
  const composedChange = this.changeFamily.rebaser.compose(changeMaps);
910
1302
  const maxId = brand(this.idAllocator.getMaxId());
911
1303
  if (maxId >= 0) {
@@ -920,28 +1312,46 @@ export class ModularEditBuilder extends EditBuilder {
920
1312
  const nodeChange = {
921
1313
  nodeExistsConstraint: { violated: false },
922
1314
  };
923
- this.applyChange(buildModularChangesetFromNode(path, nodeChange, new Map(), this.idAllocator));
1315
+ this.applyChange(buildModularChangesetFromNode(path, nodeChange, new Map(), new Map(), newCrossFieldKeyTable(), this.idAllocator));
924
1316
  }
925
1317
  }
926
- function buildModularChangesetFromField(path, fieldChange, nodeChanges, idAllocator = idAllocatorFromMaxId()) {
1318
+ function buildModularChangesetFromField(path, fieldChange, nodeChanges, nodeToParent, crossFieldKeys, idAllocator = idAllocatorFromMaxId(), localCrossFieldKeys = [], childId = undefined) {
927
1319
  const fieldChanges = new Map([[path.field, fieldChange]]);
928
1320
  if (path.parent === undefined) {
929
- return makeModularChangeset(fieldChanges, nodeChanges, idAllocator.getMaxId());
1321
+ for (const key of localCrossFieldKeys) {
1322
+ crossFieldKeys.set(key, { nodeId: undefined, field: path.field });
1323
+ }
1324
+ if (childId !== undefined) {
1325
+ setInChangeAtomIdMap(nodeToParent, childId, {
1326
+ nodeId: undefined,
1327
+ field: path.field,
1328
+ });
1329
+ }
1330
+ return makeModularChangeset(fieldChanges, nodeChanges, nodeToParent, undefined, crossFieldKeys, idAllocator.getMaxId());
930
1331
  }
931
1332
  const nodeChangeset = {
932
1333
  fieldChanges,
933
1334
  };
934
- return buildModularChangesetFromNode(path.parent, nodeChangeset, nodeChanges, idAllocator);
1335
+ const parentId = { localId: brand(idAllocator.allocate()) };
1336
+ for (const key of localCrossFieldKeys) {
1337
+ crossFieldKeys.set(key, { nodeId: parentId, field: path.field });
1338
+ }
1339
+ if (childId !== undefined) {
1340
+ setInChangeAtomIdMap(nodeToParent, childId, {
1341
+ nodeId: parentId,
1342
+ field: path.field,
1343
+ });
1344
+ }
1345
+ return buildModularChangesetFromNode(path.parent, nodeChangeset, nodeChanges, nodeToParent, crossFieldKeys, idAllocator, parentId);
935
1346
  }
936
- function buildModularChangesetFromNode(path, nodeChange, nodeChanges, idAllocator) {
937
- const nodeId = { localId: brand(idAllocator.allocate()) };
938
- setInNestedMap(nodeChanges, nodeId.revision, nodeId.localId, nodeChange);
1347
+ function buildModularChangesetFromNode(path, nodeChange, nodeChanges, nodeToParent, crossFieldKeys, idAllocator, nodeId = { localId: brand(idAllocator.allocate()) }) {
1348
+ setInChangeAtomIdMap(nodeChanges, nodeId, nodeChange);
939
1349
  const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChange(path.parentIndex, nodeId);
940
1350
  const fieldChange = {
941
1351
  fieldKind: genericFieldKind.identifier,
942
1352
  change: fieldChangeset,
943
1353
  };
944
- return buildModularChangesetFromField({ parent: path.parent, field: path.parentField }, fieldChange, nodeChanges, idAllocator);
1354
+ return buildModularChangesetFromField({ parent: path.parent, field: path.parentField }, fieldChange, nodeChanges, nodeToParent, crossFieldKeys, idAllocator, [], nodeId);
945
1355
  }
946
1356
  function getRevInfoFromTaggedChanges(changes) {
947
1357
  let maxId = -1;
@@ -990,9 +1400,181 @@ function revisionFromRevInfos(revInfos) {
990
1400
  }
991
1401
  return revInfos[0].revision;
992
1402
  }
993
- function getActiveFieldChanges(changes) {
994
- return (changes.constraintViolationCount ?? 0) === 0
995
- ? changes.fieldChanges
996
- : new Map();
1403
+ function mergeBTrees(tree1, tree2) {
1404
+ const result = tree1.clone();
1405
+ tree2.forEachPair((k, v) => {
1406
+ result.set(k, v);
1407
+ });
1408
+ return result;
1409
+ }
1410
+ function mergeNestedMaps(map1, map2) {
1411
+ const merged = new Map();
1412
+ populateNestedMap(map1, merged, true);
1413
+ populateNestedMap(map2, merged, true);
1414
+ return merged;
1415
+ }
1416
+ function fieldChangeFromId(fields, nodes, id) {
1417
+ const fieldMap = fieldMapFromNodeId(fields, nodes, id.nodeId);
1418
+ return fieldMap.get(id.field) ?? fail("No field exists for the given ID");
1419
+ }
1420
+ function fieldMapFromNodeId(rootFieldMap, nodes, nodeId) {
1421
+ if (nodeId === undefined) {
1422
+ return rootFieldMap;
1423
+ }
1424
+ const node = nodeChangeFromId(nodes, nodeId);
1425
+ assert(node.fieldChanges !== undefined, "Expected node to have field changes");
1426
+ return node.fieldChanges;
1427
+ }
1428
+ function rebasedFieldIdFromBaseId(table, baseId) {
1429
+ if (baseId.nodeId === undefined) {
1430
+ return baseId;
1431
+ }
1432
+ return { ...baseId, nodeId: rebasedNodeIdFromBaseNodeId(table, baseId.nodeId) };
1433
+ }
1434
+ function rebasedNodeIdFromBaseNodeId(table, baseId) {
1435
+ return getFromChangeAtomIdMap(table.baseToRebasedNodeId, baseId) ?? baseId;
1436
+ }
1437
+ function nodeChangeFromId(nodes, id) {
1438
+ const node = getFromChangeAtomIdMap(nodes, id);
1439
+ assert(node !== undefined, "Unknown node ID");
1440
+ return node;
1441
+ }
1442
+ function fieldIdFromFieldIdKey([revision, localId, field]) {
1443
+ const nodeId = localId !== undefined ? { revision, localId } : undefined;
1444
+ return { nodeId, field };
1445
+ }
1446
+ function cloneNodeChangeset(nodeChangeset) {
1447
+ if (nodeChangeset.fieldChanges !== undefined) {
1448
+ return { ...nodeChangeset, fieldChanges: new Map(nodeChangeset.fieldChanges) };
1449
+ }
1450
+ return { ...nodeChangeset };
1451
+ }
1452
+ function replaceFieldIdRevision(fieldId, oldRevisions, newRevision) {
1453
+ if (fieldId.nodeId === undefined) {
1454
+ return fieldId;
1455
+ }
1456
+ return {
1457
+ ...fieldId,
1458
+ nodeId: replaceAtomRevisions(fieldId.nodeId, oldRevisions, newRevision),
1459
+ };
1460
+ }
1461
+ export function getParentFieldId(changeset, nodeId) {
1462
+ const parentId = getFromChangeAtomIdMap(changeset.nodeToParent, nodeId);
1463
+ assert(parentId !== undefined, "Parent field should be defined");
1464
+ return normalizeFieldId(parentId, changeset.nodeAliases);
1465
+ }
1466
+ export function getFieldsForCrossFieldKey(changeset, [target, revision, id, count]) {
1467
+ let firstLocalId = id;
1468
+ const lastLocalId = id + count - 1;
1469
+ const fields = [];
1470
+ // eslint-disable-next-line no-constant-condition
1471
+ while (true) {
1472
+ const entry = getFirstIntersectingCrossFieldEntry(changeset.crossFieldKeys, [
1473
+ target,
1474
+ revision,
1475
+ brand(firstLocalId),
1476
+ count,
1477
+ ]);
1478
+ if (entry === undefined) {
1479
+ return fields;
1480
+ }
1481
+ const [[_target, _revision, entryId, entryCount], fieldId] = entry;
1482
+ fields.push(normalizeFieldId(fieldId, changeset.nodeAliases));
1483
+ const entryLastId = entryId + entryCount - 1;
1484
+ if (entryLastId >= lastLocalId) {
1485
+ return fields;
1486
+ }
1487
+ firstLocalId = entryLastId + 1;
1488
+ }
1489
+ }
1490
+ function getFirstIntersectingCrossFieldEntry(table, [target, revision, id, count]) {
1491
+ const entry = table.nextLowerPair([target, revision, id, Infinity]);
1492
+ if (entry === undefined) {
1493
+ return undefined;
1494
+ }
1495
+ const [entryTarget, entryRevision, entryId, entryCount] = entry[0];
1496
+ if (entryTarget !== target || entryRevision !== revision) {
1497
+ return undefined;
1498
+ }
1499
+ const lastQueryId = id + count - 1;
1500
+ const entryLastId = entryId + entryCount - 1;
1501
+ if (entryId > lastQueryId || entryLastId < id) {
1502
+ return undefined;
1503
+ }
1504
+ return entry;
1505
+ }
1506
+ function setInCrossFieldKeyTable(table, target, revision, id, count, value) {
1507
+ let entry = getFirstIntersectingCrossFieldEntry(table, [target, revision, id, count]);
1508
+ const lastQueryId = id + count - 1;
1509
+ while (entry !== undefined) {
1510
+ const [entryKey, entryValue] = entry;
1511
+ table.delete(entryKey);
1512
+ const [_target, _revision, entryId, entryCount] = entryKey;
1513
+ if (entryId < id) {
1514
+ table.set([target, revision, entryId, id - entryId], entryValue);
1515
+ }
1516
+ const lastEntryId = entryId + entryCount - 1;
1517
+ if (lastEntryId > lastQueryId) {
1518
+ table.set([target, revision, brand(lastQueryId + 1), lastEntryId - lastQueryId], entryValue);
1519
+ break;
1520
+ }
1521
+ const nextId = brand(lastEntryId + 1);
1522
+ entry = getFirstIntersectingCrossFieldEntry(table, [
1523
+ target,
1524
+ revision,
1525
+ nextId,
1526
+ lastQueryId - nextId + 1,
1527
+ ]);
1528
+ }
1529
+ table.set([target, revision, id, count], value);
1530
+ }
1531
+ function normalizeFieldId(fieldId, nodeAliases) {
1532
+ return fieldId.nodeId !== undefined
1533
+ ? { ...fieldId, nodeId: normalizeNodeId(fieldId.nodeId, nodeAliases) }
1534
+ : fieldId;
1535
+ }
1536
+ /**
1537
+ * @returns The canonical form of nodeId, according to nodeAliases
1538
+ */
1539
+ function normalizeNodeId(nodeId, nodeAliases) {
1540
+ let currentId = nodeId;
1541
+ // eslint-disable-next-line no-constant-condition
1542
+ while (true) {
1543
+ const dealiased = getFromChangeAtomIdMap(nodeAliases, currentId);
1544
+ if (dealiased === undefined) {
1545
+ return currentId;
1546
+ }
1547
+ currentId = dealiased;
1548
+ }
1549
+ }
1550
+ function hasConflicts(change) {
1551
+ return (change.constraintViolationCount ?? 0) > 0;
1552
+ }
1553
+ export function newCrossFieldKeyTable() {
1554
+ return newBTree();
1555
+ }
1556
+ function newBTree() {
1557
+ return brand(new BTree(undefined, compareTuples));
1558
+ }
1559
+ // This assumes that the arrays are the same length.
1560
+ function compareTuples(arrayA, arrayB) {
1561
+ for (let i = 0; i < arrayA.length; i++) {
1562
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1563
+ const a = arrayA[i];
1564
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1565
+ const b = arrayB[i];
1566
+ if (a < b) {
1567
+ return -1;
1568
+ }
1569
+ else if (a > b) {
1570
+ return 1;
1571
+ }
1572
+ }
1573
+ return 0;
1574
+ }
1575
+ function cloneNestedMap(map) {
1576
+ const cloned = new Map();
1577
+ populateNestedMap(map, cloned, true);
1578
+ return cloned;
997
1579
  }
998
1580
  //# sourceMappingURL=modularChangeFamily.js.map