@fluidframework/tree 2.61.0 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/api-report/tree.alpha.api.md +26 -21
  3. package/api-report/tree.beta.api.md +15 -0
  4. package/api-report/tree.legacy.beta.api.md +18 -0
  5. package/dist/alpha.d.ts +8 -8
  6. package/dist/api.d.ts +17 -0
  7. package/dist/api.d.ts.map +1 -0
  8. package/dist/api.js +24 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/beta.d.ts +5 -0
  11. package/dist/codec/codec.d.ts +3 -5
  12. package/dist/codec/codec.d.ts.map +1 -1
  13. package/dist/codec/codec.js +9 -2
  14. package/dist/codec/codec.js.map +1 -1
  15. package/dist/codec/index.d.ts +0 -1
  16. package/dist/codec/index.d.ts.map +1 -1
  17. package/dist/codec/index.js +1 -3
  18. package/dist/codec/index.js.map +1 -1
  19. package/dist/core/index.d.ts +1 -1
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +2 -1
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/rebase/index.d.ts +1 -1
  24. package/dist/core/rebase/index.d.ts.map +1 -1
  25. package/dist/core/rebase/index.js +2 -1
  26. package/dist/core/rebase/index.js.map +1 -1
  27. package/dist/core/rebase/utils.d.ts +10 -0
  28. package/dist/core/rebase/utils.d.ts.map +1 -1
  29. package/dist/core/rebase/utils.js +20 -1
  30. package/dist/core/rebase/utils.js.map +1 -1
  31. package/dist/core/tree/detachedFieldIndex.js +1 -1
  32. package/dist/core/tree/detachedFieldIndex.js.map +1 -1
  33. package/dist/external-utilities/index.d.ts +1 -1
  34. package/dist/external-utilities/index.d.ts.map +1 -1
  35. package/dist/external-utilities/index.js +1 -2
  36. package/dist/external-utilities/index.js.map +1 -1
  37. package/dist/external-utilities/typeboxValidator.d.ts +0 -13
  38. package/dist/external-utilities/typeboxValidator.d.ts.map +1 -1
  39. package/dist/external-utilities/typeboxValidator.js +3 -5
  40. package/dist/external-utilities/typeboxValidator.js.map +1 -1
  41. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
  42. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  43. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  44. package/dist/feature-libraries/flex-tree/index.d.ts +1 -0
  45. package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
  46. package/dist/feature-libraries/flex-tree/index.js +4 -1
  47. package/dist/feature-libraries/flex-tree/index.js.map +1 -1
  48. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  49. package/dist/feature-libraries/flex-tree/lazyNode.js +15 -8
  50. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  51. package/dist/feature-libraries/flex-tree/observer.d.ts +32 -0
  52. package/dist/feature-libraries/flex-tree/observer.d.ts.map +1 -0
  53. package/dist/feature-libraries/flex-tree/observer.js +33 -0
  54. package/dist/feature-libraries/flex-tree/observer.js.map +1 -0
  55. package/dist/feature-libraries/index.d.ts +1 -1
  56. package/dist/feature-libraries/index.d.ts.map +1 -1
  57. package/dist/feature-libraries/index.js +3 -1
  58. package/dist/feature-libraries/index.js.map +1 -1
  59. package/dist/index.d.ts +5 -5
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +9 -8
  62. package/dist/index.js.map +1 -1
  63. package/dist/legacy.d.ts +7 -1
  64. package/dist/packageVersion.d.ts +1 -1
  65. package/dist/packageVersion.js +1 -1
  66. package/dist/packageVersion.js.map +1 -1
  67. package/dist/shared-tree/index.d.ts +2 -2
  68. package/dist/shared-tree/index.d.ts.map +1 -1
  69. package/dist/shared-tree/index.js.map +1 -1
  70. package/dist/shared-tree/schematizingTreeView.js +2 -2
  71. package/dist/shared-tree/schematizingTreeView.js.map +1 -1
  72. package/dist/shared-tree/sharedTree.d.ts +21 -6
  73. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  74. package/dist/shared-tree/sharedTree.js +76 -37
  75. package/dist/shared-tree/sharedTree.js.map +1 -1
  76. package/dist/shared-tree/treeAlpha.d.ts +114 -1
  77. package/dist/shared-tree/treeAlpha.d.ts.map +1 -1
  78. package/dist/shared-tree/treeAlpha.js +140 -1
  79. package/dist/shared-tree/treeAlpha.js.map +1 -1
  80. package/dist/shared-tree/treeCheckout.d.ts +13 -7
  81. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  82. package/dist/shared-tree/treeCheckout.js +115 -85
  83. package/dist/shared-tree/treeCheckout.js.map +1 -1
  84. package/dist/shared-tree-core/branch.d.ts +3 -0
  85. package/dist/shared-tree-core/branch.d.ts.map +1 -1
  86. package/dist/shared-tree-core/branch.js.map +1 -1
  87. package/dist/shared-tree-core/branchIdCodec.d.ts +11 -0
  88. package/dist/shared-tree-core/branchIdCodec.d.ts.map +1 -0
  89. package/dist/shared-tree-core/branchIdCodec.js +18 -0
  90. package/dist/shared-tree-core/branchIdCodec.js.map +1 -0
  91. package/dist/shared-tree-core/editManager.d.ts +39 -64
  92. package/dist/shared-tree-core/editManager.d.ts.map +1 -1
  93. package/dist/shared-tree-core/editManager.js +455 -295
  94. package/dist/shared-tree-core/editManager.js.map +1 -1
  95. package/dist/shared-tree-core/editManagerCodecs.d.ts +1 -1
  96. package/dist/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
  97. package/dist/shared-tree-core/editManagerCodecs.js +7 -96
  98. package/dist/shared-tree-core/editManagerCodecs.js.map +1 -1
  99. package/dist/shared-tree-core/editManagerCodecsCommons.d.ts +17 -0
  100. package/dist/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -0
  101. package/dist/shared-tree-core/editManagerCodecsCommons.js +139 -0
  102. package/dist/shared-tree-core/editManagerCodecsCommons.js.map +1 -0
  103. package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts +16 -0
  104. package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -0
  105. package/dist/shared-tree-core/editManagerCodecsV1toV4.js +39 -0
  106. package/dist/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -0
  107. package/dist/shared-tree-core/editManagerCodecsV5.d.ts +16 -0
  108. package/dist/shared-tree-core/editManagerCodecsV5.d.ts.map +1 -0
  109. package/dist/shared-tree-core/editManagerCodecsV5.js +58 -0
  110. package/dist/shared-tree-core/editManagerCodecsV5.js.map +1 -0
  111. package/dist/shared-tree-core/{editManagerFormat.d.ts → editManagerFormatCommons.d.ts} +31 -7
  112. package/dist/shared-tree-core/editManagerFormatCommons.d.ts.map +1 -0
  113. package/dist/shared-tree-core/{editManagerFormat.js → editManagerFormatCommons.js} +13 -12
  114. package/dist/shared-tree-core/editManagerFormatCommons.js.map +1 -0
  115. package/dist/shared-tree-core/editManagerFormatV1toV4.d.ts +31 -0
  116. package/dist/shared-tree-core/editManagerFormatV1toV4.d.ts.map +1 -0
  117. package/dist/shared-tree-core/editManagerFormatV1toV4.js +24 -0
  118. package/dist/shared-tree-core/editManagerFormatV1toV4.js.map +1 -0
  119. package/dist/shared-tree-core/editManagerFormatV5.d.ts +62 -0
  120. package/dist/shared-tree-core/editManagerFormatV5.d.ts.map +1 -0
  121. package/dist/shared-tree-core/editManagerFormatV5.js +20 -0
  122. package/dist/shared-tree-core/editManagerFormatV5.js.map +1 -0
  123. package/dist/shared-tree-core/index.d.ts +3 -3
  124. package/dist/shared-tree-core/index.d.ts.map +1 -1
  125. package/dist/shared-tree-core/index.js.map +1 -1
  126. package/dist/shared-tree-core/messageCodecV1ToV4.d.ts +11 -0
  127. package/dist/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -0
  128. package/dist/shared-tree-core/messageCodecV1ToV4.js +59 -0
  129. package/dist/shared-tree-core/messageCodecV1ToV4.js.map +1 -0
  130. package/dist/shared-tree-core/messageCodecV5.d.ts +11 -0
  131. package/dist/shared-tree-core/messageCodecV5.d.ts.map +1 -0
  132. package/dist/shared-tree-core/messageCodecV5.js +78 -0
  133. package/dist/shared-tree-core/messageCodecV5.js.map +1 -0
  134. package/dist/shared-tree-core/messageCodecs.d.ts.map +1 -1
  135. package/dist/shared-tree-core/messageCodecs.js +16 -47
  136. package/dist/shared-tree-core/messageCodecs.js.map +1 -1
  137. package/dist/shared-tree-core/{messageFormat.d.ts → messageFormatV1ToV4.d.ts} +1 -1
  138. package/dist/shared-tree-core/messageFormatV1ToV4.d.ts.map +1 -0
  139. package/dist/shared-tree-core/{messageFormat.js → messageFormatV1ToV4.js} +1 -1
  140. package/dist/shared-tree-core/messageFormatV1ToV4.js.map +1 -0
  141. package/dist/shared-tree-core/messageFormatV5.d.ts +42 -0
  142. package/dist/shared-tree-core/messageFormatV5.d.ts.map +1 -0
  143. package/dist/shared-tree-core/messageFormatV5.js +20 -0
  144. package/dist/shared-tree-core/messageFormatV5.js.map +1 -0
  145. package/dist/shared-tree-core/messageTypes.d.ts +12 -2
  146. package/dist/shared-tree-core/messageTypes.d.ts.map +1 -1
  147. package/dist/shared-tree-core/messageTypes.js.map +1 -1
  148. package/dist/shared-tree-core/sequenceIdUtils.d.ts +1 -1
  149. package/dist/shared-tree-core/sequenceIdUtils.d.ts.map +1 -1
  150. package/dist/shared-tree-core/sequenceIdUtils.js.map +1 -1
  151. package/dist/shared-tree-core/sharedTreeCore.d.ts +19 -5
  152. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  153. package/dist/shared-tree-core/sharedTreeCore.js +182 -58
  154. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  155. package/dist/simple-tree/api/tree.d.ts +17 -0
  156. package/dist/simple-tree/api/tree.d.ts.map +1 -1
  157. package/dist/simple-tree/api/tree.js +2 -0
  158. package/dist/simple-tree/api/tree.js.map +1 -1
  159. package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  160. package/dist/simple-tree/core/unhydratedFlexTree.js +7 -1
  161. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  162. package/dist/treeFactory.d.ts +38 -9
  163. package/dist/treeFactory.d.ts.map +1 -1
  164. package/dist/treeFactory.js +44 -9
  165. package/dist/treeFactory.js.map +1 -1
  166. package/lib/alpha.d.ts +8 -8
  167. package/lib/api.d.ts +17 -0
  168. package/lib/api.d.ts.map +1 -0
  169. package/lib/api.js +22 -0
  170. package/lib/api.js.map +1 -0
  171. package/lib/beta.d.ts +5 -0
  172. package/lib/codec/codec.d.ts +3 -5
  173. package/lib/codec/codec.d.ts.map +1 -1
  174. package/lib/codec/codec.js +8 -1
  175. package/lib/codec/codec.js.map +1 -1
  176. package/lib/codec/index.d.ts +0 -1
  177. package/lib/codec/index.d.ts.map +1 -1
  178. package/lib/codec/index.js +0 -1
  179. package/lib/codec/index.js.map +1 -1
  180. package/lib/core/index.d.ts +1 -1
  181. package/lib/core/index.d.ts.map +1 -1
  182. package/lib/core/index.js +1 -1
  183. package/lib/core/index.js.map +1 -1
  184. package/lib/core/rebase/index.d.ts +1 -1
  185. package/lib/core/rebase/index.d.ts.map +1 -1
  186. package/lib/core/rebase/index.js +1 -1
  187. package/lib/core/rebase/index.js.map +1 -1
  188. package/lib/core/rebase/utils.d.ts +10 -0
  189. package/lib/core/rebase/utils.d.ts.map +1 -1
  190. package/lib/core/rebase/utils.js +18 -0
  191. package/lib/core/rebase/utils.js.map +1 -1
  192. package/lib/core/tree/detachedFieldIndex.js +2 -2
  193. package/lib/core/tree/detachedFieldIndex.js.map +1 -1
  194. package/lib/external-utilities/index.d.ts +1 -1
  195. package/lib/external-utilities/index.d.ts.map +1 -1
  196. package/lib/external-utilities/index.js +1 -1
  197. package/lib/external-utilities/index.js.map +1 -1
  198. package/lib/external-utilities/typeboxValidator.d.ts +0 -13
  199. package/lib/external-utilities/typeboxValidator.d.ts.map +1 -1
  200. package/lib/external-utilities/typeboxValidator.js +1 -3
  201. package/lib/external-utilities/typeboxValidator.js.map +1 -1
  202. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +2 -0
  203. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  204. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  205. package/lib/feature-libraries/flex-tree/index.d.ts +1 -0
  206. package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
  207. package/lib/feature-libraries/flex-tree/index.js +1 -0
  208. package/lib/feature-libraries/flex-tree/index.js.map +1 -1
  209. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  210. package/lib/feature-libraries/flex-tree/lazyNode.js +15 -8
  211. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  212. package/lib/feature-libraries/flex-tree/observer.d.ts +32 -0
  213. package/lib/feature-libraries/flex-tree/observer.d.ts.map +1 -0
  214. package/lib/feature-libraries/flex-tree/observer.js +40 -0
  215. package/lib/feature-libraries/flex-tree/observer.js.map +1 -0
  216. package/lib/feature-libraries/index.d.ts +1 -1
  217. package/lib/feature-libraries/index.d.ts.map +1 -1
  218. package/lib/feature-libraries/index.js +1 -1
  219. package/lib/feature-libraries/index.js.map +1 -1
  220. package/lib/index.d.ts +5 -5
  221. package/lib/index.d.ts.map +1 -1
  222. package/lib/index.js +3 -3
  223. package/lib/index.js.map +1 -1
  224. package/lib/legacy.d.ts +7 -1
  225. package/lib/packageVersion.d.ts +1 -1
  226. package/lib/packageVersion.js +1 -1
  227. package/lib/packageVersion.js.map +1 -1
  228. package/lib/shared-tree/index.d.ts +2 -2
  229. package/lib/shared-tree/index.d.ts.map +1 -1
  230. package/lib/shared-tree/index.js.map +1 -1
  231. package/lib/shared-tree/schematizingTreeView.js +2 -2
  232. package/lib/shared-tree/schematizingTreeView.js.map +1 -1
  233. package/lib/shared-tree/sharedTree.d.ts +21 -6
  234. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  235. package/lib/shared-tree/sharedTree.js +78 -39
  236. package/lib/shared-tree/sharedTree.js.map +1 -1
  237. package/lib/shared-tree/treeAlpha.d.ts +114 -1
  238. package/lib/shared-tree/treeAlpha.d.ts.map +1 -1
  239. package/lib/shared-tree/treeAlpha.js +143 -4
  240. package/lib/shared-tree/treeAlpha.js.map +1 -1
  241. package/lib/shared-tree/treeCheckout.d.ts +13 -7
  242. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  243. package/lib/shared-tree/treeCheckout.js +117 -87
  244. package/lib/shared-tree/treeCheckout.js.map +1 -1
  245. package/lib/shared-tree-core/branch.d.ts +3 -0
  246. package/lib/shared-tree-core/branch.d.ts.map +1 -1
  247. package/lib/shared-tree-core/branch.js.map +1 -1
  248. package/lib/shared-tree-core/branchIdCodec.d.ts +11 -0
  249. package/lib/shared-tree-core/branchIdCodec.d.ts.map +1 -0
  250. package/lib/shared-tree-core/branchIdCodec.js +13 -0
  251. package/lib/shared-tree-core/branchIdCodec.js.map +1 -0
  252. package/lib/shared-tree-core/editManager.d.ts +39 -64
  253. package/lib/shared-tree-core/editManager.d.ts.map +1 -1
  254. package/lib/shared-tree-core/editManager.js +455 -295
  255. package/lib/shared-tree-core/editManager.js.map +1 -1
  256. package/lib/shared-tree-core/editManagerCodecs.d.ts +1 -1
  257. package/lib/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
  258. package/lib/shared-tree-core/editManagerCodecs.js +4 -93
  259. package/lib/shared-tree-core/editManagerCodecs.js.map +1 -1
  260. package/lib/shared-tree-core/editManagerCodecsCommons.d.ts +17 -0
  261. package/lib/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -0
  262. package/lib/shared-tree-core/editManagerCodecsCommons.js +134 -0
  263. package/lib/shared-tree-core/editManagerCodecsCommons.js.map +1 -0
  264. package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts +16 -0
  265. package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -0
  266. package/lib/shared-tree-core/editManagerCodecsV1toV4.js +35 -0
  267. package/lib/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -0
  268. package/lib/shared-tree-core/editManagerCodecsV5.d.ts +16 -0
  269. package/lib/shared-tree-core/editManagerCodecsV5.d.ts.map +1 -0
  270. package/lib/shared-tree-core/editManagerCodecsV5.js +54 -0
  271. package/lib/shared-tree-core/editManagerCodecsV5.js.map +1 -0
  272. package/lib/shared-tree-core/{editManagerFormat.d.ts → editManagerFormatCommons.d.ts} +31 -7
  273. package/lib/shared-tree-core/editManagerFormatCommons.d.ts.map +1 -0
  274. package/lib/shared-tree-core/{editManagerFormat.js → editManagerFormatCommons.js} +10 -11
  275. package/lib/shared-tree-core/editManagerFormatCommons.js.map +1 -0
  276. package/lib/shared-tree-core/editManagerFormatV1toV4.d.ts +31 -0
  277. package/lib/shared-tree-core/editManagerFormatV1toV4.d.ts.map +1 -0
  278. package/lib/shared-tree-core/editManagerFormatV1toV4.js +20 -0
  279. package/lib/shared-tree-core/editManagerFormatV1toV4.js.map +1 -0
  280. package/lib/shared-tree-core/editManagerFormatV5.d.ts +62 -0
  281. package/lib/shared-tree-core/editManagerFormatV5.d.ts.map +1 -0
  282. package/lib/shared-tree-core/editManagerFormatV5.js +16 -0
  283. package/lib/shared-tree-core/editManagerFormatV5.js.map +1 -0
  284. package/lib/shared-tree-core/index.d.ts +3 -3
  285. package/lib/shared-tree-core/index.d.ts.map +1 -1
  286. package/lib/shared-tree-core/index.js.map +1 -1
  287. package/lib/shared-tree-core/messageCodecV1ToV4.d.ts +11 -0
  288. package/lib/shared-tree-core/messageCodecV1ToV4.d.ts.map +1 -0
  289. package/lib/shared-tree-core/messageCodecV1ToV4.js +55 -0
  290. package/lib/shared-tree-core/messageCodecV1ToV4.js.map +1 -0
  291. package/lib/shared-tree-core/messageCodecV5.d.ts +11 -0
  292. package/lib/shared-tree-core/messageCodecV5.d.ts.map +1 -0
  293. package/lib/shared-tree-core/messageCodecV5.js +74 -0
  294. package/lib/shared-tree-core/messageCodecV5.js.map +1 -0
  295. package/lib/shared-tree-core/messageCodecs.d.ts.map +1 -1
  296. package/lib/shared-tree-core/messageCodecs.js +17 -48
  297. package/lib/shared-tree-core/messageCodecs.js.map +1 -1
  298. package/lib/shared-tree-core/{messageFormat.d.ts → messageFormatV1ToV4.d.ts} +1 -1
  299. package/lib/shared-tree-core/messageFormatV1ToV4.d.ts.map +1 -0
  300. package/lib/shared-tree-core/{messageFormat.js → messageFormatV1ToV4.js} +1 -1
  301. package/lib/shared-tree-core/messageFormatV1ToV4.js.map +1 -0
  302. package/lib/shared-tree-core/messageFormatV5.d.ts +42 -0
  303. package/lib/shared-tree-core/messageFormatV5.d.ts.map +1 -0
  304. package/lib/shared-tree-core/messageFormatV5.js +16 -0
  305. package/lib/shared-tree-core/messageFormatV5.js.map +1 -0
  306. package/lib/shared-tree-core/messageTypes.d.ts +12 -2
  307. package/lib/shared-tree-core/messageTypes.d.ts.map +1 -1
  308. package/lib/shared-tree-core/messageTypes.js.map +1 -1
  309. package/lib/shared-tree-core/sequenceIdUtils.d.ts +1 -1
  310. package/lib/shared-tree-core/sequenceIdUtils.d.ts.map +1 -1
  311. package/lib/shared-tree-core/sequenceIdUtils.js.map +1 -1
  312. package/lib/shared-tree-core/sharedTreeCore.d.ts +19 -5
  313. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  314. package/lib/shared-tree-core/sharedTreeCore.js +183 -59
  315. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  316. package/lib/simple-tree/api/tree.d.ts +17 -0
  317. package/lib/simple-tree/api/tree.d.ts.map +1 -1
  318. package/lib/simple-tree/api/tree.js +2 -0
  319. package/lib/simple-tree/api/tree.js.map +1 -1
  320. package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  321. package/lib/simple-tree/core/unhydratedFlexTree.js +8 -2
  322. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  323. package/lib/treeFactory.d.ts +38 -9
  324. package/lib/treeFactory.d.ts.map +1 -1
  325. package/lib/treeFactory.js +41 -8
  326. package/lib/treeFactory.js.map +1 -1
  327. package/package.json +25 -25
  328. package/src/api.ts +30 -0
  329. package/src/codec/codec.ts +12 -6
  330. package/src/codec/index.ts +0 -1
  331. package/src/core/index.ts +1 -0
  332. package/src/core/rebase/index.ts +1 -0
  333. package/src/core/rebase/utils.ts +27 -0
  334. package/src/core/tree/detachedFieldIndex.ts +2 -2
  335. package/src/external-utilities/index.ts +1 -1
  336. package/src/external-utilities/typeboxValidator.ts +1 -3
  337. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +2 -0
  338. package/src/feature-libraries/flex-tree/index.ts +2 -0
  339. package/src/feature-libraries/flex-tree/lazyNode.ts +13 -3
  340. package/src/feature-libraries/flex-tree/observer.ts +64 -0
  341. package/src/feature-libraries/index.ts +3 -0
  342. package/src/index.ts +6 -4
  343. package/src/packageVersion.ts +1 -1
  344. package/src/shared-tree/index.ts +2 -0
  345. package/src/shared-tree/schematizingTreeView.ts +2 -2
  346. package/src/shared-tree/sharedTree.ts +116 -52
  347. package/src/shared-tree/treeAlpha.ts +309 -4
  348. package/src/shared-tree/treeCheckout.ts +152 -100
  349. package/src/shared-tree-core/branch.ts +7 -0
  350. package/src/shared-tree-core/branchIdCodec.ts +28 -0
  351. package/src/shared-tree-core/editManager.ts +729 -430
  352. package/src/shared-tree-core/editManagerCodecs.ts +4 -164
  353. package/src/shared-tree-core/editManagerCodecsCommons.ts +245 -0
  354. package/src/shared-tree-core/editManagerCodecsV1toV4.ts +108 -0
  355. package/src/shared-tree-core/editManagerCodecsV5.ts +156 -0
  356. package/src/shared-tree-core/{editManagerFormat.ts → editManagerFormatCommons.ts} +17 -13
  357. package/src/shared-tree-core/editManagerFormatV1toV4.ts +42 -0
  358. package/src/shared-tree-core/editManagerFormatV5.ts +35 -0
  359. package/src/shared-tree-core/index.ts +3 -1
  360. package/src/shared-tree-core/messageCodecV1ToV4.ts +104 -0
  361. package/src/shared-tree-core/messageCodecV5.ts +131 -0
  362. package/src/shared-tree-core/messageCodecs.ts +16 -85
  363. package/src/shared-tree-core/messageFormatV5.ts +50 -0
  364. package/src/shared-tree-core/messageTypes.ts +15 -2
  365. package/src/shared-tree-core/sequenceIdUtils.ts +1 -1
  366. package/src/shared-tree-core/sharedTreeCore.ts +281 -85
  367. package/src/simple-tree/api/tree.ts +23 -0
  368. package/src/simple-tree/core/unhydratedFlexTree.ts +11 -2
  369. package/src/treeFactory.ts +48 -8
  370. package/dist/codec/noopValidator.d.ts +0 -13
  371. package/dist/codec/noopValidator.d.ts.map +0 -1
  372. package/dist/codec/noopValidator.js +0 -17
  373. package/dist/codec/noopValidator.js.map +0 -1
  374. package/dist/shared-tree-core/editManagerFormat.d.ts.map +0 -1
  375. package/dist/shared-tree-core/editManagerFormat.js.map +0 -1
  376. package/dist/shared-tree-core/messageFormat.d.ts.map +0 -1
  377. package/dist/shared-tree-core/messageFormat.js.map +0 -1
  378. package/docs/user-facing/schema-evolution.md +0 -309
  379. package/lib/codec/noopValidator.d.ts +0 -13
  380. package/lib/codec/noopValidator.d.ts.map +0 -1
  381. package/lib/codec/noopValidator.js +0 -14
  382. package/lib/codec/noopValidator.js.map +0 -1
  383. package/lib/shared-tree-core/editManagerFormat.d.ts.map +0 -1
  384. package/lib/shared-tree-core/editManagerFormat.js.map +0 -1
  385. package/lib/shared-tree-core/messageFormat.d.ts.map +0 -1
  386. package/lib/shared-tree-core/messageFormat.js.map +0 -1
  387. package/src/codec/noopValidator.ts +0 -18
  388. /package/src/shared-tree-core/{messageFormat.ts → messageFormatV1ToV4.ts} +0 -0
@@ -37,31 +37,20 @@ class EditManager {
37
37
  /**
38
38
  * @param changeFamily - the change family of changes on the trunk and local branch
39
39
  * @param localSessionId - the id of the local session that will be used for local commits
40
+ * @param mintRevisionTag - a function which generates globally unique revision tags
41
+ * @param onSharedBranchCreated - called when a new shared branch is created. This is not called for the main branch.
40
42
  */
41
- constructor(changeFamily, localSessionId, mintRevisionTag, logger) {
43
+ constructor(changeFamily, localSessionId, mintRevisionTag, onSharedBranchCreated, logger) {
42
44
  this.changeFamily = changeFamily;
43
45
  this.localSessionId = localSessionId;
44
46
  this.mintRevisionTag = mintRevisionTag;
47
+ this.onSharedBranchCreated = onSharedBranchCreated;
45
48
  this._events = (0, client_utils_1.createEmitter)();
49
+ this.sharedBranches = new Map();
46
50
  /**
47
- * Records extra data associated with commits in the {@link trunk}.
48
- * This does not include an entry for the {@link trunkBase}.
49
- */
50
- this.trunkMetadata = new Map();
51
- /**
52
- * A map from a sequence id to the commit in the {@link trunk} which has that sequence id.
53
- * This also includes an entry for the {@link trunkBase} which always has the lowest key in the map.
54
- */
55
- this.sequenceMap = new sorted_btree_es6_1.BTree(undefined, sequenceIdUtils_js_1.sequenceIdComparator);
56
- /**
57
- * Branches are maintained to represent the local change list that the issuing client had
58
- * at the time of submitting the latest known edit on the branch.
59
- * This means the head commit of each branch is always in its original (non-rebased) form.
60
- */
61
- this.peerLocalBranches = new Map();
62
- /**
63
- * Tracks where on the trunk all registered branches are based. Each key is the sequence id of a commit on
64
- * the trunk, and the value is the set of all branches who have that commit as their common ancestor with the trunk.
51
+ * Tracks where on the trunk of the main branch all registered branches are based.
52
+ * Each key is the sequence id of a commit on the trunk,
53
+ * and the value is the set of all branches who have that commit as their common ancestor with the trunk.
65
54
  *
66
55
  * @remarks
67
56
  * This does not include the local branch.
@@ -74,39 +63,24 @@ class EditManager {
74
63
  * @remarks If there are more than one commit with the same sequence number we assume this refers to the last commit in the batch.
75
64
  */
76
65
  this.minimumSequenceNumber = exports.minimumPossibleSequenceNumber;
77
- /**
78
- * The list of commits (from oldest to most recent) that are on the local branch but not on the trunk.
79
- * When a local commit is sequenced, the first commit in this list shifted onto the tip of the trunk.
80
- */
81
- this.localCommits = [];
82
66
  this.trunkBase = {
83
67
  revision: rootRevision,
84
68
  change: changeFamily.rebaser.compose([]),
85
69
  };
86
- this.sequenceMap.set(minimumPossibleSequenceId, this.trunkBase);
87
70
  if (logger !== undefined) {
88
71
  this.telemetryEventBatcher = new internal_2.TelemetryEventBatcher({
89
72
  eventName: "rebaseProcessing",
90
73
  category: "performance",
91
74
  }, logger, maxRebaseStatsAggregationCount);
92
75
  }
93
- this.trunk = new branch_js_1.SharedTreeBranch(this.trunkBase, changeFamily, mintRevisionTag, this._events, this.telemetryEventBatcher);
94
- this.localBranch = new branch_js_1.SharedTreeBranch(this.trunk.getHead(), changeFamily, mintRevisionTag, this._events, this.telemetryEventBatcher);
95
- this.localBranch.events.on("afterChange", (event) => {
96
- if (event.type === "append") {
97
- for (const commit of event.newCommits) {
98
- this.localCommits.push(commit);
99
- }
100
- }
101
- else {
102
- this.localCommits.length = 0;
103
- (0, index_js_1.findCommonAncestor)([this.localBranch.getHead(), this.localCommits], this.trunk.getHead());
104
- }
105
- });
106
- // Track all forks of the local branch for purposes of trunk eviction. Unlike the local branch, they have
107
- // an unknown lifetime and rebase frequency, so we can not make any assumptions about which trunk commits
108
- // they require and therefore we monitor them explicitly.
109
- (0, branch_js_1.onForkTransitive)(this.localBranch, (fork) => this.registerBranch(fork));
76
+ const mainTrunk = new branch_js_1.SharedTreeBranch(this.trunkBase, changeFamily, mintRevisionTag, this._events, this.telemetryEventBatcher);
77
+ this.createAndAddSharedBranch("main", undefined, undefined, mainTrunk);
78
+ }
79
+ getLocalBranch(branchId) {
80
+ return this.getSharedBranch(branchId).localBranch;
81
+ }
82
+ getSharedBranch(branchId) {
83
+ return this.sharedBranches.get(branchId) ?? (0, internal_1.fail)(0xc56 /* Branch does not exist */);
110
84
  }
111
85
  /**
112
86
  * Make the given branch known to the `EditManager`. The `EditManager` will ensure that all registered
@@ -128,30 +102,32 @@ class EditManager {
128
102
  const offAfterRebase = branch.events.on("afterChange", (args) => {
129
103
  if (args.type === "rebase") {
130
104
  this.trackBranch(branch);
131
- this.trimTrunk();
105
+ this.trimHistory();
132
106
  }
133
107
  });
134
108
  // When the branch is disposed, update our branch set and trim the trunk
135
109
  const offDispose = branch.events.on("dispose", () => {
136
110
  this.untrackBranch(branch);
137
- this.trimTrunk();
111
+ this.trimHistory();
138
112
  offBeforeRebase();
139
113
  offAfterRebase();
140
114
  offDispose();
141
115
  });
142
116
  }
143
117
  trackBranch(b) {
144
- const trunkCommit = (0, index_js_1.findCommonAncestor)(this.trunk.getHead(), b.getHead()) ??
145
- (0, internal_1.fail)(0xad2 /* Expected branch to be related to trunk */);
146
- const sequenceId = this.getCommitSequenceId(trunkCommit);
118
+ const main = this.getSharedBranch("main");
119
+ const trunkCommit = (0, index_js_1.findCommonAncestor)(main.trunk.getHead(), b.getHead()) ??
120
+ (0, internal_1.fail)(0xad2 /* Expected branch to be related to main */);
121
+ const sequenceId = main.getCommitSequenceId(trunkCommit.revision);
147
122
  const branches = (0, index_js_2.getOrCreate)(this.trunkBranches, sequenceId, () => new Set());
148
123
  (0, internal_1.assert)(!branches.has(b), 0x670 /* Branch was registered more than once */);
149
124
  branches.add(b);
150
125
  }
151
126
  untrackBranch(b) {
152
- const trunkCommit = (0, index_js_1.findCommonAncestor)(this.trunk.getHead(), b.getHead()) ??
153
- (0, internal_1.fail)(0xad3 /* Expected branch to be related to trunk */);
154
- const sequenceId = this.getCommitSequenceId(trunkCommit);
127
+ const main = this.getSharedBranch("main");
128
+ const trunkCommit = (0, index_js_1.findCommonAncestor)(main.trunk.getHead(), b.getHead()) ??
129
+ (0, internal_1.fail)(0xad3 /* Expected branch to be related to main */);
130
+ const sequenceId = main.getCommitSequenceId(trunkCommit.revision);
155
131
  const branches = this.trunkBranches.get(sequenceId) ?? (0, internal_1.fail)(0xad4 /* Expected branch to be tracked */);
156
132
  (0, internal_1.assert)(branches.delete(b), 0x671 /* Expected branch to be tracked */);
157
133
  if (branches.size === 0) {
@@ -159,54 +135,17 @@ class EditManager {
159
135
  }
160
136
  }
161
137
  /**
162
- * Promote the oldest un-sequenced commit on the local branch to the head of the trunk.
163
- * @param sequenceId - The sequence id of the new trunk commit
164
- * @remarks This method is a performance optimization for the scenario where this client receives its own change back after sequencing.
165
- * The normal (not optimized) process in this case would be to apply the new sequenced commit to the trunk and then rebase the local branch over the trunk.
166
- * The first commit will be "the same" (as in, it will have the same revision) as the commit that was just sequenced, so the rebase will be a no-op.
167
- * Because the rebase is a no-op, we can skip it entirely and simply remove the commit from the local branch and append it to the trunk.
168
- * Avoiding the overhead of the rebase process, even when it's a no-op, has real measured performance benefits and is worth the added complexity here.
138
+ * Return the sequenced number of the latest sequenced change.
169
139
  */
170
- fastForwardNextLocalCommit(sequenceId) {
171
- // First, push the local commit to the trunk.
172
- // We are mutating our `localCommits` cache here, but there is no need to actually change the `localBranch` itself because it will simply catch up later if/when it next rebases.
173
- const firstLocalCommit = this.localCommits.shift();
174
- (0, internal_1.assert)(firstLocalCommit !== undefined, 0x6b5 /* Received a sequenced change from the local session despite having no local changes */);
175
- const previousSequenceId = this.getCommitSequenceId(this.trunk.getHead());
176
- this.pushGraphCommitToTrunk(sequenceId, firstLocalCommit, this.localSessionId);
177
- // Next, we need to update the sequence IDs that our local branches (user's branches, not peer branches) are associated with.
178
- // In particular, if a local branch is based on the previous trunk head (the branch's first ancestor in the trunk is the commit that was the head before we pushed the new commit)
179
- // and also branches off of the local branch (it has an ancestor that is part of the local branch), it needs to have its sequence number advanced to be that of the new trunk head.
180
- // Intuitively, this makes sense because:
181
- // 1. The trunk's head just advanced forward by some (sequence) amount.
182
- // 2. The local branch is always rebased to be branching off of the head of the trunk (not literally in this case, because of the optimization, but in effect).
183
- // 3. Therefore, the entire local branch just advanced forward by some (sequence) amount, and any commits downstream of it which track the sequence numbers of their base commits on the trunk should also advance.
184
- // This update is not necessarily required for all local branches, since some may have fallen behind the local branch and are based on older trunk commits (such branches do not need updating).
185
- const currentBranches = this.trunkBranches.get(previousSequenceId);
186
- if (currentBranches !== undefined) {
187
- const newBranches = (0, index_js_2.getOrCreate)(this.trunkBranches, sequenceId, () => new Set());
188
- for (const branch of currentBranches) {
189
- // Check every branch associated with the old sequence ID and advance it if it is based on the local branch (specifically, on the local branch as it was before we pushed its first commit to the trunk).
190
- // We validate this by checking if the branch's head is a descendant of the local commit that we just pushed.
191
- if ((0, index_js_1.findAncestor)(branch.getHead(), (c) => c === firstLocalCommit) !== undefined) {
192
- newBranches.add(branch);
193
- currentBranches.delete(branch);
194
- }
195
- }
196
- // Clean up our trunk branches map by removing any empty sets.
197
- if (currentBranches.size === 0) {
198
- this.trunkBranches.delete(previousSequenceId);
199
- }
200
- if (newBranches.size === 0) {
201
- this.trunkBranches.delete(sequenceId);
140
+ getLatestSequenceNumber() {
141
+ let maxSequenceNumber;
142
+ for (const branch of this.sharedBranches.values()) {
143
+ const branchMax = branch.getCommitSequenceId(branch.trunk.getHead().revision).sequenceNumber;
144
+ if (maxSequenceNumber === undefined || maxSequenceNumber < branchMax) {
145
+ maxSequenceNumber = branchMax;
202
146
  }
203
147
  }
204
- }
205
- /**
206
- * Return the sequence number at which the given commit was sequenced on the trunk, or undefined if the commit is not part of the trunk.
207
- */
208
- getSequenceNumber(trunkCommit) {
209
- return this.trunkMetadata.get(trunkCommit.revision)?.sequenceId.sequenceNumber;
148
+ return maxSequenceNumber;
210
149
  }
211
150
  /**
212
151
  * Advances the minimum sequence number, and removes all commits from the trunk which lie outside the collaboration window,
@@ -215,21 +154,21 @@ class EditManager {
215
154
  *
216
155
  * @remarks If there are more than one commit with the same sequence number we assume this refers to the last commit in the batch.
217
156
  */
218
- advanceMinimumSequenceNumber(minimumSequenceNumber, trimTrunk = true) {
157
+ advanceMinimumSequenceNumber(minimumSequenceNumber, trimHistory = true) {
219
158
  if (minimumSequenceNumber === this.minimumSequenceNumber) {
220
159
  return;
221
160
  }
222
161
  (0, internal_1.assert)(minimumSequenceNumber > this.minimumSequenceNumber, 0x476 /* number must be larger or equal to current minimumSequenceNumber. */);
223
162
  this.minimumSequenceNumber = minimumSequenceNumber;
224
- if (trimTrunk) {
225
- this.trimTrunk();
163
+ if (trimHistory) {
164
+ this.trimHistory();
226
165
  }
227
166
  }
228
167
  /**
229
168
  * Examines the latest known minimum sequence number and the trunk bases of any registered branches to determine
230
169
  * if any commits on the trunk are unreferenced and unneeded for future computation; those found are evicted from the trunk.
231
170
  */
232
- trimTrunk() {
171
+ trimHistory() {
233
172
  /** The sequence id of the most recent commit on the trunk that will be trimmed */
234
173
  let trunkTailSequenceId = {
235
174
  sequenceNumber: this.minimumSequenceNumber,
@@ -243,118 +182,72 @@ class EditManager {
243
182
  const sequenceIdBeforeMinimumBranchBase = (0, sequenceIdUtils_js_1.getUpperBoundOfPreviousSequenceId)(minimumBranchBaseSequenceId);
244
183
  trunkTailSequenceId = (0, sequenceIdUtils_js_1.minSequenceId)(trunkTailSequenceId, sequenceIdBeforeMinimumBranchBase);
245
184
  }
246
- const [sequenceId, latestEvicted] = this.getClosestTrunkCommit((0, sequenceIdUtils_js_1.maxSequenceId)(trunkTailSequenceId, this.sequenceMap.minKey() ?? minimumPossibleSequenceId));
185
+ const mainBranch = this.getSharedBranch("main");
186
+ const [sequenceId, latestEvicted] = mainBranch.getClosestTrunkCommit((0, sequenceIdUtils_js_1.maxSequenceId)(trunkTailSequenceId, mainBranch.sequenceIdToCommit.minKey() ?? minimumPossibleSequenceId));
247
187
  // Don't do any work if the commit found by the search is already the tail of the trunk
248
- if (latestEvicted !== this.trunkBase) {
249
- // The minimum sequence number informs us that all peer branches are at least caught up to the tail commit,
250
- // so rebase them accordingly. This is necessary to prevent peer branches from referencing any evicted commits.
251
- for (const [, branch] of this.peerLocalBranches) {
252
- branch.rebaseOnto(this.trunk, latestEvicted);
253
- }
254
- // This mutation is a performance hack. If commits are truly immutable, then changing the trunk's tail requires
255
- // regenerating the entire commit graph. Instead, we can simply chop off the tail like this if we're certain
256
- // that there are no outstanding references to any of the commits being removed (other than the references via
257
- // the trunk). The peer branches have been rebased to the head of the trunk, the local branch is already rebased
258
- // to the head of the trunk, and all other branches are tracked by `trunkBranches` and known to be ahead of or at
259
- // `newTrunkBase`. Therefore, no branches should have unique references to any of the commits being evicted here.
260
- // We mutate the most recent of the evicted commits to become the new trunk base. That way, any other commits that
261
- // have parent pointers to the latest evicted commit will stay linked, even though that it is no longer part of the trunk.
262
- const newTrunkBase = latestEvicted;
263
- // The metadata for new trunk base revision needs to be deleted before modifying it.
264
- this.trunkMetadata.delete(newTrunkBase.revision);
265
- // collect the revisions that will be trimmed to send as part of the branch trimmed event
266
- const trimmedRevisions = getPathFromBase(newTrunkBase, this.trunkBase).map((c) => c.revision);
267
- // Dropping the parent field removes (transitively) all references to the evicted commits so they can be garbage collected.
268
- delete newTrunkBase.parent;
269
- this.trunkBase = newTrunkBase;
270
- // Update any state that is derived from trunk commits
271
- this.sequenceMap.editRange(minimumPossibleSequenceId, sequenceId, true, (s, commit) => {
272
- // Cleanup look-aside data for each evicted commit
273
- this.trunkMetadata.delete(commit.revision);
274
- // Delete all evicted commits from `sequenceMap` except for the latest one, which is the new `trunkBase`
275
- if ((0, sequenceIdUtils_js_1.equalSequenceIds)(s, sequenceId)) {
276
- (0, internal_1.assert)(commit === newTrunkBase, 0x729 /* Expected last evicted commit to be new trunk base */);
277
- }
278
- else {
279
- Reflect.defineProperty(commit, "change", {
280
- get: () => (0, internal_1.assert)(false, 0xa5e /* Should not access 'change' property of an evicted commit */),
281
- });
282
- Reflect.defineProperty(commit, "revision", {
283
- get: () => (0, internal_1.assert)(false, 0xa5f /* Should not access 'revision' property of an evicted commit */),
284
- });
285
- Reflect.defineProperty(commit, "parent", {
286
- get: () => (0, internal_1.assert)(false, 0xa60 /* Should not access 'parent' property of an evicted commit */),
287
- });
288
- return { delete: true };
289
- }
188
+ if (latestEvicted === this.trunkBase) {
189
+ return;
190
+ }
191
+ // This mutation is a performance hack. If commits are truly immutable, then changing the trunk's tail requires
192
+ // regenerating the entire commit graph. Instead, we can simply chop off the tail like this if we're certain
193
+ // that there are no outstanding references to any of the commits being removed (other than the references via
194
+ // the trunk). The peer branches have been rebased to the head of the trunk, the local branch is already rebased
195
+ // to the head of the trunk, and all other branches are tracked by `trunkBranches` and known to be ahead of or at
196
+ // `newTrunkBase`. Therefore, no branches should have unique references to any of the commits being evicted here.
197
+ // We mutate the most recent of the evicted commits to become the new trunk base. That way, any other commits that
198
+ // have parent pointers to the latest evicted commit will stay linked, even though that it is no longer part of the trunk.
199
+ const newTrunkBase = latestEvicted;
200
+ // collect the revisions that will be trimmed to send as part of the branch trimmed event
201
+ const trimmedCommits = getPathFromBase(newTrunkBase, this.trunkBase);
202
+ const trimmedRevisions = trimmedCommits.map((c) => c.revision);
203
+ // The minimum sequence number informs us that all peer branches are at least caught up to the tail commit,
204
+ // so rebase them accordingly. This is necessary to prevent peer branches from referencing any evicted commits.
205
+ mainBranch.trimHistory(latestEvicted, sequenceId);
206
+ // Only the last trimmed commit, which is the new trunk base, should remain accessible.
207
+ for (const commit of trimmedCommits.slice(0, -1)) {
208
+ Reflect.defineProperty(commit, "change", {
209
+ get: () => (0, internal_1.assert)(false, 0xa5e /* Should not access 'change' property of an evicted commit */),
210
+ });
211
+ Reflect.defineProperty(commit, "revision", {
212
+ get: () => (0, internal_1.assert)(false, 0xa5f /* Should not access 'revision' property of an evicted commit */),
213
+ });
214
+ Reflect.defineProperty(commit, "parent", {
215
+ get: () => (0, internal_1.assert)(false, 0xa60 /* Should not access 'parent' property of an evicted commit */),
290
216
  });
291
- const trunkSize = getPathFromBase(this.trunk.getHead(), this.trunkBase).length;
292
- (0, internal_1.assert)(this.sequenceMap.size === trunkSize + 1, 0x744 /* The size of the sequenceMap must have one element more than the trunk */);
293
- (0, internal_1.assert)(this.trunkMetadata.size === trunkSize, 0x745 /* The size of the trunkMetadata must be the same as the trunk */);
294
- this._events.emit("ancestryTrimmed", trimmedRevisions);
295
217
  }
218
+ // Dropping the parent field removes (transitively) all references to the evicted commits so they can be garbage collected.
219
+ delete newTrunkBase.parent;
220
+ this.trunkBase = newTrunkBase;
221
+ this._events.emit("ancestryTrimmed", trimmedRevisions);
296
222
  }
297
223
  isEmpty() {
298
- return (this.trunk.getHead() === this.trunkBase &&
299
- this.peerLocalBranches.size === 0 &&
300
- this.localBranch.getHead() === this.trunk.getHead() &&
301
- this.minimumSequenceNumber === exports.minimumPossibleSequenceNumber);
224
+ for (const branch of this.sharedBranches.values()) {
225
+ if (!branch.isEmpty(this.trunkBase)) {
226
+ return false;
227
+ }
228
+ }
229
+ return this.minimumSequenceNumber === exports.minimumPossibleSequenceNumber;
302
230
  }
303
231
  getSummaryData() {
304
- // The assert below is acceptable at present because summarization only ever occurs on a client with no
305
- // local/in-flight changes.
306
- // In the future we may wish to relax this constraint. For that to work, the current implementation of
307
- // `EditManager` would have to be amended in one of two ways:
308
- // A) Changes made by the local session should be represented by a branch in `EditManager.branches`.
309
- // B) The contents of such a branch should be computed on demand based on the trunk.
310
- // Note that option (A) would be a simple change to `addSequencedChanges` whereas (B) would likely require
311
- // rebasing trunk changes over the inverse of trunk changes.
312
- (0, internal_1.assert)(this.localBranch.getHead() === this.trunk.getHead(), 0x428 /* Clients with local changes cannot be used to generate summaries */);
313
232
  // Trimming the trunk before serializing ensures that the trunk data in the summary is as minimal as possible.
314
- this.trimTrunk();
315
- let oldestCommitInCollabWindow = this.getClosestTrunkCommit(this.minimumSequenceNumber)[1];
316
- (0, internal_1.assert)(oldestCommitInCollabWindow.parent !== undefined ||
317
- oldestCommitInCollabWindow === this.trunkBase, 0x8c7 /* Expected oldest commit in collab window to have a parent or be the trunk base */);
318
- // Path construction is exclusive, so we need to use the parent of the oldest commit in the window if it exists
319
- oldestCommitInCollabWindow =
320
- oldestCommitInCollabWindow.parent ?? oldestCommitInCollabWindow;
321
- const trunk = getPathFromBase(this.trunk.getHead(), oldestCommitInCollabWindow).map((c) => {
322
- (0, internal_1.assert)(c !== this.trunkBase, 0xa61 /* Serialized trunk should not include the trunk base */);
323
- const metadata = this.trunkMetadata.get(c.revision) ??
324
- (0, internal_1.fail)(0xad5 /* Expected metadata for trunk commit */);
325
- const commit = {
326
- change: c.change,
327
- revision: c.revision,
328
- sequenceNumber: metadata.sequenceId.sequenceNumber,
329
- sessionId: metadata.sessionId,
330
- };
331
- if (metadata.sequenceId.indexInBatch !== undefined) {
332
- commit.indexInBatch = metadata.sequenceId.indexInBatch;
233
+ this.trimHistory();
234
+ const minSeqNumberToSummarize = {
235
+ sequenceNumber: (0, index_js_2.brand)(this.minimumSequenceNumber + 1),
236
+ };
237
+ let minBaseSeqId = minSeqNumberToSummarize;
238
+ const mainBranch = this.getSharedBranch("main");
239
+ const branches = new Map();
240
+ for (const [branchId, branch] of this.sharedBranches) {
241
+ if (branchId !== "main") {
242
+ const branchSummary = branch.getSummaryData(minSeqNumberToSummarize, this.trunkBase.revision);
243
+ branches.set(branchId, branchSummary);
244
+ (0, internal_1.assert)(branchSummary.base !== undefined, 0xc57 /* Branch summary must have a base */);
245
+ const baseSequenceId = mainBranch.getCommitSequenceId(branchSummary.base);
246
+ minBaseSeqId = (0, sequenceIdUtils_js_1.minSequenceId)(minBaseSeqId, baseSequenceId);
333
247
  }
334
- return commit;
335
- });
336
- const peerLocalBranches = new Map((0, index_js_2.mapIterable)(this.peerLocalBranches.entries(), ([sessionId, branch]) => {
337
- const branchPath = [];
338
- const ancestor = (0, index_js_1.findCommonAncestor)([branch.getHead(), branchPath], this.trunk.getHead()) ??
339
- (0, internal_1.fail)(0xad6 /* Expected branch to be based on trunk */);
340
- const base = ancestor === this.trunkBase ? rootRevision : ancestor.revision;
341
- return [
342
- sessionId,
343
- {
344
- base,
345
- commits: branchPath.map((c) => {
346
- (0, internal_1.assert)(c !== this.trunkBase, 0xa62 /* Serialized branch should not include the trunk base */);
347
- const commit = {
348
- change: c.change,
349
- revision: c.revision,
350
- sessionId,
351
- };
352
- return commit;
353
- }),
354
- },
355
- ];
356
- }));
357
- return { trunk, peerLocalBranches };
248
+ }
249
+ const mainSummary = mainBranch.getSummaryData(minBaseSeqId, this.trunkBase.revision);
250
+ return { main: mainSummary, branches, originator: this.localSessionId };
358
251
  }
359
252
  loadSummaryData(data) {
360
253
  (0, internal_1.assert)(this.isEmpty(), 0x68a /* Attempted to load from summary after edit manager was already mutated */);
@@ -362,53 +255,28 @@ class EditManager {
362
255
  // when hydrating the peer branches below
363
256
  const trunkRevisionCache = new Map();
364
257
  trunkRevisionCache.set(this.trunkBase.revision, this.trunkBase);
365
- this.trunk.setHead(data.trunk.reduce((base, c) => {
366
- const sequenceId = c.indexInBatch === undefined
367
- ? {
368
- sequenceNumber: c.sequenceNumber,
369
- }
370
- : {
371
- sequenceNumber: c.sequenceNumber,
372
- indexInBatch: c.indexInBatch,
373
- };
374
- const commit = (0, index_js_1.mintCommit)(base, c);
375
- this.sequenceMap.set(sequenceId, commit);
376
- this.trunkMetadata.set(c.revision, {
377
- sequenceId,
378
- sessionId: c.sessionId,
379
- });
380
- trunkRevisionCache.set(c.revision, commit);
381
- return commit;
382
- }, this.trunkBase));
383
- this.localBranch.setHead(this.trunk.getHead());
384
- for (const [sessionId, branch] of data.peerLocalBranches) {
385
- const commit = trunkRevisionCache.get(branch.base) ??
386
- (0, internal_1.fail)(0xad7 /* Expected summary branch to be based off of a revision in the trunk */);
387
- this.peerLocalBranches.set(sessionId, new branch_js_1.SharedTreeBranch(branch.commits.reduce(index_js_1.mintCommit, commit), this.changeFamily, this.mintRevisionTag));
258
+ const mainBranch = this.sharedBranches.get("main") ?? (0, internal_1.fail)(0xc58 /* Main branch must exist */);
259
+ mainBranch.loadSummaryData(data.main, trunkRevisionCache);
260
+ if (data.branches !== undefined) {
261
+ for (const [branchId, branchData] of data.branches) {
262
+ const branch = this.createSharedBranch(branchId, branchData.session, mainBranch, mainBranch.trunk.fork());
263
+ branch.loadSummaryData(branchData, trunkRevisionCache);
264
+ this.addSharedBranch(branchId, branch);
265
+ }
388
266
  }
389
267
  }
390
- getCommitSequenceId(trunkCommitOrTrunkBase) {
391
- const id = this.trunkMetadata.get(trunkCommitOrTrunkBase.revision)?.sequenceId;
392
- if (id === undefined) {
393
- (0, internal_1.assert)(trunkCommitOrTrunkBase === this.trunkBase, 0xa63 /* Commit must be either be on the trunk or be the trunk base */);
394
- return minimumPossibleSequenceId;
395
- }
396
- return id;
268
+ getTrunkHead(branchId) {
269
+ return this.getSharedBranch(branchId).trunk.getHead();
397
270
  }
398
- getTrunkChanges() {
399
- return this.getTrunkCommits().map((c) => c.change);
271
+ getTrunkChanges(branchId) {
272
+ return this.getTrunkCommits(branchId).map((c) => c.change);
400
273
  }
401
- getTrunkCommits() {
402
- return getPathFromBase(this.trunk.getHead(), this.trunkBase);
274
+ getTrunkCommits(branchId) {
275
+ return getPathFromBase(this.getTrunkHead(branchId), this.trunkBase);
403
276
  }
404
- getTrunkHead() {
405
- return this.trunk.getHead();
406
- }
407
- getLocalChanges() {
408
- return this.getLocalCommits().map((c) => c.change);
409
- }
410
- getLocalCommits() {
411
- return this.localCommits;
277
+ getLocalCommits(branchId) {
278
+ const branch = this.getSharedBranch(branchId);
279
+ return branch.getLocalCommits();
412
280
  }
413
281
  /**
414
282
  * Gets the length of the longest branch maintained by this `EditManager`.
@@ -420,15 +288,52 @@ class EditManager {
420
288
  */
421
289
  getLongestBranchLength() {
422
290
  let max = 0;
423
- const trunkHead = this.trunk.getHead();
424
- for (const branch of this.peerLocalBranches.values()) {
425
- const branchPath = getPathFromBase(branch.getHead(), trunkHead);
426
- if (branchPath.length > max) {
427
- max = branchPath.length;
428
- }
291
+ for (const branch of this.sharedBranches.values()) {
292
+ max = Math.max(max, branch.getLongestBranchLength());
429
293
  }
430
- const localPath = getPathFromBase(this.localBranch.getHead(), trunkHead);
431
- return Math.max(max, localPath.length);
294
+ return max;
295
+ }
296
+ sequenceBranchCreation(sessionId, referenceSequenceNumber, branchId) {
297
+ if (sessionId === this.localSessionId) {
298
+ (0, internal_1.assert)(this.sharedBranches.has(branchId), 0xc59 /* Expected branch to already exist */);
299
+ return;
300
+ }
301
+ const mainBranch = this.getSharedBranch("main");
302
+ const branchTrunk = mainBranch.rebasePeer(sessionId, referenceSequenceNumber).fork();
303
+ this.createAndAddSharedBranch(branchId, sessionId, mainBranch, branchTrunk);
304
+ }
305
+ addNewBranch(branchId) {
306
+ const main = this.getSharedBranch("main") ?? (0, internal_1.fail)(0xc5a /* Main branch must exist */);
307
+ this.createAndAddSharedBranch(branchId, this.localSessionId, main, this.getLocalBranch("main").fork());
308
+ }
309
+ getSharedBranchIds() {
310
+ return Array.from(this.sharedBranches.keys());
311
+ }
312
+ removeBranch(branchId) {
313
+ (0, internal_1.assert)(branchId !== "main", 0xc5b /* Cannot remove main branch */);
314
+ const hadBranch = this.sharedBranches.delete(branchId);
315
+ (0, internal_1.assert)(hadBranch, 0xc5c /* Expected branch to exist */);
316
+ }
317
+ createAndAddSharedBranch(branchId, sessionId, parent, branch) {
318
+ const sharedBranch = this.createSharedBranch(branchId, sessionId, parent, branch);
319
+ this.addSharedBranch(branchId, sharedBranch);
320
+ return sharedBranch;
321
+ }
322
+ addSharedBranch(branchId, branch) {
323
+ (0, internal_1.assert)(!this.sharedBranches.has(branchId), 0xc5d /* A branch with this ID already exists */);
324
+ this.sharedBranches.set(branchId, branch);
325
+ // Track all forks of the local branch for purposes of trunk eviction. Unlike the local branch, they have
326
+ // an unknown lifetime and rebase frequency, so we can not make any assumptions about which trunk commits
327
+ // they require and therefore we monitor them explicitly.
328
+ (0, branch_js_1.onForkTransitive)(branch.localBranch, (fork) => this.registerBranch(fork));
329
+ if (branchId !== "main") {
330
+ this.registerBranch(branch.localBranch);
331
+ this.onSharedBranchCreated?.(branchId);
332
+ }
333
+ }
334
+ createSharedBranch(branchId, sessionId, parent, branch) {
335
+ const sharedBranch = new SharedBranch(parent, branch, branchId, sessionId, minimumPossibleSequenceId, this.changeFamily, this.mintRevisionTag, this._events, this.telemetryEventBatcher);
336
+ return sharedBranch;
432
337
  }
433
338
  /* eslint-disable jsdoc/check-indentation */
434
339
  /**
@@ -441,34 +346,119 @@ class EditManager {
441
346
  * - They are not interleaved with messages from other clients.
442
347
  */
443
348
  /* eslint-enable jsdoc/check-indentation */
444
- addSequencedChanges(newCommits, sessionId, sequenceNumber, referenceSequenceNumber) {
349
+ addSequencedChanges(newCommits, sessionId, sequenceNumber, referenceSequenceNumber, branchId) {
445
350
  (0, internal_1.assert)(newCommits.length > 0, 0xad8 /* Expected at least one sequenced change */);
446
351
  (0, internal_1.assert)(sequenceNumber > this.minimumSequenceNumber, 0x713 /* Expected change sequence number to exceed the last known minimum sequence number */);
447
- (0, internal_1.assert)(sequenceNumber >= // This is ">=", not ">" because changes in the same batch will have the same sequence number
448
- (this.sequenceMap.maxKey()?.sequenceNumber ?? exports.minimumPossibleSequenceNumber), 0xa64 /* Attempted to sequence change with an outdated sequence number */);
449
- // Returns the sequence id for the next commit to be processed in the bunch. Since all the commits have the
450
- // same sequence number, only the index in the batch needs to be incremented.
451
- const getNextSequenceId = (sequenceId) => {
452
- return {
453
- sequenceNumber: sequenceId.sequenceNumber,
454
- indexInBatch: (sequenceId.indexInBatch ?? 0) + 1,
455
- };
352
+ const branch = this.getSharedBranch(branchId);
353
+ const onSequenceLocalCommit = (commit, sequenceId, previousSequenceId) => {
354
+ // Next, we need to update the sequence IDs that our local branches (user's branches, not peer branches) are associated with.
355
+ // In particular, if a local branch is based on the previous trunk head (the branch's first ancestor in the trunk is the commit that was the head before we pushed the new commit)
356
+ // and also branches off of the local branch (it has an ancestor that is part of the local branch), it needs to have its sequence number advanced to be that of the new trunk head.
357
+ // Intuitively, this makes sense because:
358
+ // 1. The trunk's head just advanced forward by some (sequence) amount.
359
+ // 2. The local branch is always rebased to be branching off of the head of the trunk (not literally in this case, because of the optimization, but in effect).
360
+ // 3. Therefore, the entire local branch just advanced forward by some (sequence) amount, and any commits downstream of it which track the sequence numbers of their base commits on the trunk should also advance.
361
+ // This update is not necessarily required for all local branches, since some may have fallen behind the local branch and are based on older trunk commits (such branches do not need updating).
362
+ const currentBranches = this.trunkBranches.get(previousSequenceId);
363
+ if (currentBranches !== undefined) {
364
+ const newBranches = (0, index_js_2.getOrCreate)(this.trunkBranches, sequenceId, () => new Set());
365
+ for (const forkedBranch of currentBranches) {
366
+ // Check every branch associated with the old sequence ID and advance it if it is based on the local branch (specifically, on the local branch as it was before we pushed its first commit to the trunk).
367
+ // We validate this by checking if the branch's head is a descendant of the local commit that we just pushed.
368
+ if ((0, index_js_1.findAncestor)(forkedBranch.getHead(), (c) => c === commit) !== undefined) {
369
+ newBranches.add(forkedBranch);
370
+ currentBranches.delete(forkedBranch);
371
+ }
372
+ }
373
+ // Clean up our trunk branches map by removing any empty sets.
374
+ if (currentBranches.size === 0) {
375
+ this.trunkBranches.delete(previousSequenceId);
376
+ }
377
+ if (newBranches.size === 0) {
378
+ this.trunkBranches.delete(sequenceId);
379
+ }
380
+ }
456
381
  };
457
- const commitsSequenceNumber = this.getBatch(sequenceNumber);
382
+ const areLocalCommits = sessionId === this.localSessionId;
383
+ branch.addSequencedChanges(newCommits, sessionId, sequenceNumber, areLocalCommits, referenceSequenceNumber, onSequenceLocalCommit);
384
+ }
385
+ findLocalCommit(branchId, revision) {
386
+ const commits = [];
387
+ const commit = (0, index_js_1.findAncestor)([this.getSharedBranch(branchId).localBranch.getHead(), commits], (c) => c.revision === revision);
388
+ (0, internal_1.assert)(commit !== undefined, 0x599 /* Expected local branch to contain revision */);
389
+ return [commit, commits];
390
+ }
391
+ }
392
+ exports.EditManager = EditManager;
393
+ /**
394
+ * Gets the path from the base of a branch to its head.
395
+ */
396
+ function getPathFromBase(branchHead, baseBranchHead) {
397
+ const path = [];
398
+ (0, internal_1.assert)((0, index_js_1.findCommonAncestor)([branchHead, path], baseBranchHead) !== undefined, 0x573 /* Expected branches to be related */);
399
+ return path;
400
+ }
401
+ class SharedBranch {
402
+ constructor(parentBranch, trunk, id, sessionId, baseCommitSequenceId, changeFamily, mintRevisionTag, branchTrimmer, telemetryEventBatcher) {
403
+ this.parentBranch = parentBranch;
404
+ this.trunk = trunk;
405
+ this.id = id;
406
+ this.sessionId = sessionId;
407
+ this.changeFamily = changeFamily;
408
+ this.mintRevisionTag = mintRevisionTag;
409
+ /**
410
+ * Branches are maintained to represent the local change list that the issuing client had
411
+ * at the time of submitting the latest known edit on the branch.
412
+ * This means the head commit of each branch is always in its original (non-rebased) form.
413
+ */
414
+ this.peerLocalBranches = new Map();
415
+ /**
416
+ * A map from a sequence id to the commit which has that sequence id.
417
+ * This also includes an entry for the {@link trunkBase} which always has the lowest key in the map.
418
+ */
419
+ this.sequenceIdToCommit = new sorted_btree_es6_1.BTree(undefined, sequenceIdUtils_js_1.sequenceIdComparator);
420
+ /**
421
+ * The list of commits (from oldest to most recent) that are on the local branch but not on the trunk.
422
+ * When a local commit is sequenced, the first commit in this list shifted onto the tip of the trunk.
423
+ */
424
+ this.localCommits = [];
425
+ /**
426
+ * Records extra data associated with sequenced commits.
427
+ * This does not include an entry for the {@link trunkBase}.
428
+ */
429
+ this.commitMetadata = new Map();
430
+ this.localBranch = new branch_js_1.SharedTreeBranch(this.trunk.getHead(), changeFamily, mintRevisionTag, branchTrimmer, telemetryEventBatcher);
431
+ this.sequenceIdToCommit.set(baseCommitSequenceId, this.trunk.getHead());
432
+ this.localBranch.events.on("afterChange", (event) => {
433
+ if (event.type === "append") {
434
+ for (const commit of event.newCommits) {
435
+ this.localCommits.push(commit);
436
+ }
437
+ }
438
+ else {
439
+ this.localCommits.length = 0;
440
+ (0, index_js_1.findCommonAncestor)([this.localBranch.getHead(), this.localCommits], this.trunk.getHead());
441
+ }
442
+ });
443
+ }
444
+ addSequencedChanges(newCommits, sessionId, sequenceNumber, areLocalCommits, referenceSequenceNumber, onSequenceLocalCommit) {
445
+ (0, internal_1.assert)(sequenceNumber >= // This is ">=", not ">" because changes in the same batch will have the same sequence number
446
+ (this.sequenceIdToCommit.maxKey()?.sequenceNumber ?? exports.minimumPossibleSequenceNumber), 0xa64 /* Attempted to sequence change with an outdated sequence number */);
447
+ const batchSize = this.getBatchSize(sequenceNumber);
458
448
  // The sequence id for the next commit to be processed in the bunch.
459
- let nextSequenceId = commitsSequenceNumber.length === 0
449
+ let nextSequenceId = batchSize === 0
460
450
  ? {
461
451
  sequenceNumber,
462
452
  }
463
453
  : {
464
454
  sequenceNumber,
465
- indexInBatch: commitsSequenceNumber.length,
455
+ indexInBatch: batchSize,
466
456
  };
467
457
  // Local changes, i.e., changes from this client are applied by fast forwarding the local branch commit onto
468
458
  // the trunk.
469
- if (sessionId === this.localSessionId) {
459
+ if (areLocalCommits) {
470
460
  for (const _ of newCommits) {
471
- this.fastForwardNextLocalCommit(nextSequenceId);
461
+ this.sequenceLocalCommit(nextSequenceId, sessionId, onSequenceLocalCommit);
472
462
  nextSequenceId = getNextSequenceId(nextSequenceId);
473
463
  }
474
464
  return;
@@ -478,9 +468,7 @@ class EditManager {
478
468
  // Get the revision that the remote change is based on and rebase that peer local branch over the part of the
479
469
  // trunk up to the base revision. This will be a no-op if the sending client has not advanced since the last
480
470
  // time we received an edit from it
481
- const [, baseRevisionInTrunk] = this.getClosestTrunkCommit(referenceSequenceNumber);
482
- const peerLocalBranch = (0, index_js_2.getOrCreate)(this.peerLocalBranches, sessionId, () => new branch_js_1.SharedTreeBranch(baseRevisionInTrunk, this.changeFamily, this.mintRevisionTag));
483
- peerLocalBranch.rebaseOnto(this.trunk, baseRevisionInTrunk);
471
+ const peerLocalBranch = this.rebasePeer(sessionId, referenceSequenceNumber);
484
472
  // Step 2 - Append the changes to the peer branch and rebase the changes to the tip of the trunk.
485
473
  if (peerLocalBranch.getHead() === this.trunk.getHead()) {
486
474
  // If the peer local branch is fully caught up and empty (no changes relative to the trunk) after being
@@ -501,8 +489,8 @@ class EditManager {
501
489
  // If the merge resulted in any changes to the trunk, update the sequence map and trunk metadata
502
490
  // with the rebased commits.
503
491
  for (const sourceCommit of result.sourceCommits) {
504
- this.sequenceMap.set(nextSequenceId, sourceCommit);
505
- this.trunkMetadata.set(sourceCommit.revision, {
492
+ this.sequenceIdToCommit.set(nextSequenceId, sourceCommit);
493
+ this.commitMetadata.set(sourceCommit.revision, {
506
494
  sequenceId: nextSequenceId,
507
495
  sessionId,
508
496
  });
@@ -513,21 +501,85 @@ class EditManager {
513
501
  // Step 3 - Rebase the local branch over the updated trunk.
514
502
  this.localBranch.rebaseOnto(this.trunk);
515
503
  }
516
- findLocalCommit(revision) {
517
- const commits = [];
518
- const commit = (0, index_js_1.findAncestor)([this.localBranch.getHead(), commits], (c) => c.revision === revision);
519
- (0, internal_1.assert)(commit !== undefined, 0x599 /* Expected local branch to contain revision */);
520
- return [commit, commits];
504
+ isEmpty(baseCommit) {
505
+ return (this.trunk.getHead() === baseCommit &&
506
+ this.peerLocalBranches.size === 0 &&
507
+ this.localBranch.getHead() === this.trunk.getHead());
521
508
  }
522
- pushCommitToTrunk(sequenceId, commit) {
523
- const mintedCommit = (0, index_js_1.mintCommit)(this.trunk.getHead(), commit);
524
- this.pushGraphCommitToTrunk(sequenceId, mintedCommit, commit.sessionId);
509
+ rebasePeer(sessionId, referenceSequenceNumber) {
510
+ const [, baseRevisionInTrunk] = this.getClosestTrunkCommit(referenceSequenceNumber);
511
+ const peerLocalBranch = (0, index_js_2.getOrCreate)(this.peerLocalBranches, sessionId, () => new branch_js_1.SharedTreeBranch(baseRevisionInTrunk, this.changeFamily, this.mintRevisionTag));
512
+ peerLocalBranch.rebaseOnto(this.trunk, baseRevisionInTrunk);
513
+ return peerLocalBranch;
525
514
  }
526
- pushGraphCommitToTrunk(sequenceId, graphCommit, sessionId) {
527
- this.trunk.setHead(graphCommit);
515
+ getPeerBranchOrTrunk(sessionId) {
516
+ return this.peerLocalBranches.get(sessionId) ?? this.trunk;
517
+ }
518
+ /**
519
+ * Gets the length of the longest branch maintained by this `SharedBranch`.
520
+ * This may be the length of a peer branch or the local branch.
521
+ *
522
+ * @remarks
523
+ * The length is counted from the lowest common ancestor with the trunk such that a fully sequenced branch would
524
+ * have length zero.
525
+ */
526
+ getLongestBranchLength() {
527
+ let max = 0;
528
528
  const trunkHead = this.trunk.getHead();
529
- this.sequenceMap.set(sequenceId, trunkHead);
530
- this.trunkMetadata.set(trunkHead.revision, { sequenceId, sessionId });
529
+ for (const branch of this.peerLocalBranches.values()) {
530
+ const branchPath = getPathFromBase(branch.getHead(), trunkHead);
531
+ if (branchPath.length > max) {
532
+ max = branchPath.length;
533
+ }
534
+ }
535
+ const localPath = getPathFromBase(this.localBranch.getHead(), trunkHead);
536
+ return Math.max(max, localPath.length);
537
+ }
538
+ trimHistory(newBase, sequenceId) {
539
+ this.rebasePeers(newBase);
540
+ this.sequenceIdToCommit.editRange(minimumPossibleSequenceId, sequenceId, true, (s, commit) => {
541
+ // Cleanup look-aside data for each evicted commit
542
+ this.commitMetadata.delete(commit.revision);
543
+ // Delete all evicted commits from `sequenceMap` except for the latest one, which is the new `trunkBase`
544
+ if ((0, sequenceIdUtils_js_1.equalSequenceIds)(s, sequenceId)) {
545
+ (0, internal_1.assert)(commit === newBase, 0x729 /* Expected last evicted commit to be new trunk base */);
546
+ }
547
+ else {
548
+ return { delete: true };
549
+ }
550
+ });
551
+ const trunkSize = getPathFromBase(this.trunk.getHead(), newBase).length;
552
+ (0, internal_1.assert)(this.sequenceIdToCommit.size === trunkSize + 1, 0x744 /* The size of the sequenceMap must have one element more than the trunk */);
553
+ (0, internal_1.assert)(this.commitMetadata.size === trunkSize, 0x745 /* The size of the trunkMetadata must be the same as the trunk */);
554
+ }
555
+ rebasePeers(commit) {
556
+ for (const [, branch] of this.peerLocalBranches) {
557
+ branch.rebaseOnto(this.trunk, commit);
558
+ }
559
+ // The metadata for new trunk base revision needs to be deleted before modifying it.
560
+ this.commitMetadata.delete(commit.revision);
561
+ }
562
+ getLocalCommits() {
563
+ return this.localCommits;
564
+ }
565
+ /**
566
+ * Promote the oldest un-sequenced commit on the local branch to the head of the trunk.
567
+ * @param sequenceId - The sequence id of the new trunk commit
568
+ * @remarks This method is a performance optimization for the scenario where this client receives its own change back after sequencing.
569
+ * The normal (not optimized) process in this case would be to apply the new sequenced commit to the trunk and then rebase the local branch over the trunk.
570
+ * The first commit will be "the same" (as in, it will have the same revision) as the commit that was just sequenced, so the rebase will be a no-op.
571
+ * Because the rebase is a no-op, we can skip it entirely and simply remove the commit from the local branch and append it to the trunk.
572
+ * Avoiding the overhead of the rebase process, even when it's a no-op, has real measured performance benefits and is worth the added complexity here.
573
+ */
574
+ sequenceLocalCommit(sequenceId, sessionId, onSequenceLocalCommit) {
575
+ // First, push the local commit to the trunk.
576
+ // We are mutating our `localCommits` cache here,but there is no need to actually change the `localBranch` itself because it will simply catch up later if/when it next rebases.
577
+ const firstLocalCommit = this.localCommits.shift();
578
+ (0, internal_1.assert)(firstLocalCommit !== undefined, 0x6b5 /* Received a sequenced change from the local session despite having no local changes */);
579
+ const prevSequenceId = this.getCommitSequenceId(this.trunk.getHead().revision);
580
+ this.pushGraphCommitToTrunk(sequenceId, firstLocalCommit, sessionId);
581
+ onSequenceLocalCommit(firstLocalCommit, sequenceId, prevSequenceId);
582
+ return firstLocalCommit;
531
583
  }
532
584
  getClosestTrunkCommit(searchBy) {
533
585
  const sequenceId = typeof searchBy === "number"
@@ -539,27 +591,135 @@ class EditManager {
539
591
  indexInBatch: Number.POSITIVE_INFINITY,
540
592
  }
541
593
  : searchBy;
542
- const commit = this.sequenceMap.getPairOrNextLower(sequenceId);
594
+ const commit = this.sequenceIdToCommit.getPairOrNextLower(sequenceId);
543
595
  (0, internal_1.assert)(commit !== undefined, 0x746 /* sequence id has been evicted */);
544
596
  return commit;
545
597
  }
546
- getBatch(sequenceNumber) {
598
+ pushGraphCommitToTrunk(sequenceId, graphCommit, sessionId) {
599
+ this.trunk.setHead(graphCommit);
600
+ this.registerSequencedCommit(sequenceId, sessionId, graphCommit);
601
+ }
602
+ pushCommitToTrunk(sequenceId, commit) {
603
+ const mintedCommit = (0, index_js_1.mintCommit)(this.trunk.getHead(), commit);
604
+ this.pushGraphCommitToTrunk(sequenceId, mintedCommit, commit.sessionId);
605
+ }
606
+ registerSequencedCommit(sequenceId, sessionId, commit) {
607
+ this.sequenceIdToCommit.set(sequenceId, commit);
608
+ this.commitMetadata.set(commit.revision, { sequenceId, sessionId });
609
+ }
610
+ getCommitSequenceId(commitRevision) {
611
+ const id = this.commitMetadata.get(commitRevision)?.sequenceId;
612
+ if (id === undefined) {
613
+ return minimumPossibleSequenceId;
614
+ }
615
+ return id;
616
+ }
617
+ // TODO: Document that this is to handle receiving separate commits with the same sequence ID,
618
+ // as a batch of changes are not guaranteed to be processed as one bunch.
619
+ getBatchSize(sequenceNumber) {
547
620
  const startSequenceId = {
548
621
  sequenceNumber,
549
622
  };
550
623
  const endSequenceId = {
551
624
  sequenceNumber: (0, index_js_2.brand)(sequenceNumber + 1),
552
625
  };
553
- return this.sequenceMap.getRange(startSequenceId, endSequenceId, false);
626
+ return this.sequenceIdToCommit.getRange(startSequenceId, endSequenceId, false).length;
627
+ }
628
+ getSummaryData(minSeqNumberToSummarize, trunkBaseRevision) {
629
+ // The assert below is acceptable at present because summarization only ever occurs on a client with no
630
+ // local/in-flight changes.
631
+ // In the future we may wish to relax this constraint. For that to work, the current implementation of
632
+ // `EditManager` would have to be amended in one of two ways:
633
+ // A) Changes made by the local session should be represented by a branch in `EditManager.branches`.
634
+ // B) The contents of such a branch should be computed on demand based on the trunk.
635
+ // Note that option (A) would be a simple change to `addSequencedChanges` whereas (B) would likely require
636
+ // rebasing trunk changes over the inverse of trunk changes.
637
+ (0, internal_1.assert)(this.localBranch.getHead() === this.trunk.getHead(), 0xc5e /* Clients with local changes cannot be used to generate summaries */);
638
+ let parentHead;
639
+ if (this.parentBranch === undefined) {
640
+ const oldestCommitInCollabWindow = this.getClosestTrunkCommit(minSeqNumberToSummarize)[1];
641
+ // Path construction is exclusive, so we need to use the parent of the oldest commit in the window if it exists
642
+ parentHead = oldestCommitInCollabWindow.parent ?? oldestCommitInCollabWindow;
643
+ }
644
+ else {
645
+ parentHead = this.parentBranch.trunk.getHead();
646
+ }
647
+ const childBranchTrunkCommits = [];
648
+ const forkPointFromMainTrunk = (0, index_js_1.findCommonAncestor)([this.trunk.getHead(), childBranchTrunkCommits], parentHead);
649
+ (0, internal_1.assert)(forkPointFromMainTrunk !== undefined, 0xc5f /* Expected child branch to be based on main branch */);
650
+ const trunk = childBranchTrunkCommits.map((c) => {
651
+ const metadata = this.commitMetadata.get(c.revision) ??
652
+ (0, internal_1.fail)(0xad5 /* Expected metadata for trunk commit */);
653
+ const commit = {
654
+ change: c.change,
655
+ revision: c.revision,
656
+ sequenceNumber: metadata.sequenceId.sequenceNumber,
657
+ sessionId: metadata.sessionId,
658
+ };
659
+ if (metadata.sequenceId.indexInBatch !== undefined) {
660
+ commit.indexInBatch = metadata.sequenceId.indexInBatch;
661
+ }
662
+ return commit;
663
+ });
664
+ const peerLocalBranches = new Map((0, index_js_2.mapIterable)(this.peerLocalBranches.entries(), ([sessionId, branch]) => {
665
+ const branchPath = [];
666
+ const ancestor = (0, index_js_1.findCommonAncestor)([branch.getHead(), branchPath], this.trunk.getHead()) ??
667
+ (0, internal_1.fail)(0xad6 /* Expected branch to be based on trunk */);
668
+ const base = ancestor.revision === trunkBaseRevision ? rootRevision : ancestor.revision;
669
+ return [
670
+ sessionId,
671
+ {
672
+ base,
673
+ commits: branchPath.map((c) => {
674
+ const commit = {
675
+ change: c.change,
676
+ revision: c.revision,
677
+ sessionId,
678
+ };
679
+ return commit;
680
+ }),
681
+ },
682
+ ];
683
+ }));
684
+ const trunkBase = this.parentBranch === undefined ? undefined : forkPointFromMainTrunk.revision;
685
+ return { trunk, peerLocalBranches, base: trunkBase, id: this.id, session: this.sessionId };
686
+ }
687
+ loadSummaryData(data, trunkRevisionCache) {
688
+ (0, internal_1.assert)((this.parentBranch === undefined) === (data.base === undefined), 0xc60 /* Expected branch base to match presence of parent branch */);
689
+ const parentTrunkBase = trunkRevisionCache.get(data.base ?? rootRevision) ??
690
+ (0, internal_1.fail)(0xc61 /* Expected base revision to be in trunk cache */);
691
+ this.trunk.setHead(data.trunk.reduce((base, c) => {
692
+ const sequenceId = c.indexInBatch === undefined
693
+ ? {
694
+ sequenceNumber: c.sequenceNumber,
695
+ }
696
+ : {
697
+ sequenceNumber: c.sequenceNumber,
698
+ indexInBatch: c.indexInBatch,
699
+ };
700
+ const commit = (0, index_js_1.mintCommit)(base, c);
701
+ this.sequenceIdToCommit.set(sequenceId, commit);
702
+ this.commitMetadata.set(c.revision, {
703
+ sequenceId,
704
+ sessionId: c.sessionId,
705
+ });
706
+ trunkRevisionCache.set(c.revision, commit);
707
+ return commit;
708
+ }, parentTrunkBase));
709
+ this.localBranch.setHead(this.trunk.getHead());
710
+ for (const [sessionId, branch] of data.peerLocalBranches) {
711
+ const commit = trunkRevisionCache.get(branch.base) ??
712
+ (0, internal_1.fail)(0xad7 /* Expected summary branch to be based off of a revision in the trunk */);
713
+ this.peerLocalBranches.set(sessionId, new branch_js_1.SharedTreeBranch(branch.commits.reduce(index_js_1.mintCommit, commit), this.changeFamily, this.mintRevisionTag));
714
+ }
554
715
  }
555
716
  }
556
- exports.EditManager = EditManager;
557
- /**
558
- * Gets the path from the base of a branch to its head.
559
- */
560
- function getPathFromBase(branchHead, baseBranchHead) {
561
- const path = [];
562
- (0, internal_1.assert)((0, index_js_1.findCommonAncestor)([branchHead, path], baseBranchHead) !== undefined, 0x573 /* Expected branches to be related */);
563
- return path;
717
+ // Returns the sequence id for the next commit to be processed in the bunch. Since all the commits have the
718
+ // same sequence number, only the index in the batch needs to be incremented.
719
+ function getNextSequenceId(sequenceId) {
720
+ return {
721
+ sequenceNumber: sequenceId.sequenceNumber,
722
+ indexInBatch: (sequenceId.indexInBatch ?? 0) + 1,
723
+ };
564
724
  }
565
725
  //# sourceMappingURL=editManager.js.map