@fluidframework/tree 2.1.0-281041 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/.vscode/Tree.code-workspace +2 -1
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +6 -6
  4. package/api-report/tree.alpha.api.md +1 -1
  5. package/api-report/tree.beta.api.md +1 -1
  6. package/api-report/tree.public.api.md +1 -1
  7. package/dist/core/forest/editableForest.d.ts.map +1 -1
  8. package/dist/core/forest/editableForest.js +4 -2
  9. package/dist/core/forest/editableForest.js.map +1 -1
  10. package/dist/core/tree/anchorSet.d.ts +1 -0
  11. package/dist/core/tree/anchorSet.d.ts.map +1 -1
  12. package/dist/core/tree/anchorSet.js +13 -0
  13. package/dist/core/tree/anchorSet.js.map +1 -1
  14. package/dist/core/tree/detachedFieldIndex.d.ts +48 -11
  15. package/dist/core/tree/detachedFieldIndex.d.ts.map +1 -1
  16. package/dist/core/tree/detachedFieldIndex.js +144 -20
  17. package/dist/core/tree/detachedFieldIndex.js.map +1 -1
  18. package/dist/core/tree/detachedFieldIndexCodec.d.ts.map +1 -1
  19. package/dist/core/tree/detachedFieldIndexCodec.js +13 -4
  20. package/dist/core/tree/detachedFieldIndexCodec.js.map +1 -1
  21. package/dist/core/tree/detachedFieldIndexFormat.d.ts +1 -1
  22. package/dist/core/tree/detachedFieldIndexFormat.d.ts.map +1 -1
  23. package/dist/core/tree/detachedFieldIndexFormat.js.map +1 -1
  24. package/dist/core/tree/detachedFieldIndexTypes.d.ts +39 -4
  25. package/dist/core/tree/detachedFieldIndexTypes.d.ts.map +1 -1
  26. package/dist/core/tree/detachedFieldIndexTypes.js.map +1 -1
  27. package/dist/core/tree/index.d.ts +2 -1
  28. package/dist/core/tree/index.d.ts.map +1 -1
  29. package/dist/core/tree/index.js.map +1 -1
  30. package/dist/core/tree/visitDelta.d.ts +3 -1
  31. package/dist/core/tree/visitDelta.d.ts.map +1 -1
  32. package/dist/core/tree/visitDelta.js +31 -15
  33. package/dist/core/tree/visitDelta.js.map +1 -1
  34. package/dist/core/tree/visitorUtils.d.ts +3 -3
  35. package/dist/core/tree/visitorUtils.d.ts.map +1 -1
  36. package/dist/core/tree/visitorUtils.js +4 -4
  37. package/dist/core/tree/visitorUtils.js.map +1 -1
  38. package/dist/feature-libraries/editableTreeBinder.js +1 -1
  39. package/dist/feature-libraries/editableTreeBinder.js.map +1 -1
  40. package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts +1 -8
  41. package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
  42. package/dist/feature-libraries/flex-map-tree/mapTreeNode.js +0 -52
  43. package/dist/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
  44. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +1 -13
  45. package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  46. package/dist/feature-libraries/flex-tree/flexTreeTypes.js +0 -2
  47. package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  48. package/dist/feature-libraries/flex-tree/index.d.ts +2 -1
  49. package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
  50. package/dist/feature-libraries/flex-tree/index.js +5 -1
  51. package/dist/feature-libraries/flex-tree/index.js.map +1 -1
  52. package/dist/feature-libraries/flex-tree/lazyEntity.d.ts +1 -2
  53. package/dist/feature-libraries/flex-tree/lazyEntity.d.ts.map +1 -1
  54. package/dist/feature-libraries/flex-tree/lazyEntity.js.map +1 -1
  55. package/dist/feature-libraries/flex-tree/lazyField.d.ts +1 -2
  56. package/dist/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  57. package/dist/feature-libraries/flex-tree/lazyField.js +10 -18
  58. package/dist/feature-libraries/flex-tree/lazyField.js.map +1 -1
  59. package/dist/feature-libraries/flex-tree/lazyNode.d.ts +1 -4
  60. package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  61. package/dist/feature-libraries/flex-tree/lazyNode.js +0 -27
  62. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  63. package/dist/feature-libraries/forest-summary/forestSummarizer.d.ts.map +1 -1
  64. package/dist/feature-libraries/forest-summary/forestSummarizer.js +1 -1
  65. package/dist/feature-libraries/forest-summary/forestSummarizer.js.map +1 -1
  66. package/dist/feature-libraries/index.d.ts +1 -1
  67. package/dist/feature-libraries/index.d.ts.map +1 -1
  68. package/dist/feature-libraries/index.js +4 -1
  69. package/dist/feature-libraries/index.js.map +1 -1
  70. package/dist/feature-libraries/modular-schema/discrepancies.js +3 -3
  71. package/dist/feature-libraries/modular-schema/discrepancies.js.map +1 -1
  72. package/dist/feature-libraries/modular-schema/modularChangeCodecs.js +1 -1
  73. package/dist/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
  74. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  75. package/dist/feature-libraries/modular-schema/modularChangeFamily.js +14 -17
  76. package/dist/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  77. package/dist/feature-libraries/object-forest/objectForest.d.ts +3 -2
  78. package/dist/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
  79. package/dist/feature-libraries/object-forest/objectForest.js +5 -4
  80. package/dist/feature-libraries/object-forest/objectForest.js.map +1 -1
  81. package/dist/packageVersion.d.ts +1 -1
  82. package/dist/packageVersion.d.ts.map +1 -1
  83. package/dist/packageVersion.js +1 -1
  84. package/dist/packageVersion.js.map +1 -1
  85. package/dist/shared-tree/sharedTree.d.ts +5 -1
  86. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  87. package/dist/shared-tree/sharedTree.js +8 -1
  88. package/dist/shared-tree/sharedTree.js.map +1 -1
  89. package/dist/shared-tree/sharedTreeChangeEnricher.js +1 -1
  90. package/dist/shared-tree/sharedTreeChangeEnricher.js.map +1 -1
  91. package/dist/shared-tree/treeApi.js +1 -1
  92. package/dist/shared-tree/treeApi.js.map +1 -1
  93. package/dist/shared-tree/treeCheckout.d.ts +10 -1
  94. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  95. package/dist/shared-tree/treeCheckout.js +47 -4
  96. package/dist/shared-tree/treeCheckout.js.map +1 -1
  97. package/dist/shared-tree/treeView.d.ts.map +1 -1
  98. package/dist/shared-tree/treeView.js +7 -3
  99. package/dist/shared-tree/treeView.js.map +1 -1
  100. package/dist/shared-tree-core/branch.d.ts +6 -0
  101. package/dist/shared-tree-core/branch.d.ts.map +1 -1
  102. package/dist/shared-tree-core/branch.js +2 -0
  103. package/dist/shared-tree-core/branch.js.map +1 -1
  104. package/dist/shared-tree-core/sharedTreeCore.d.ts +4 -0
  105. package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  106. package/dist/shared-tree-core/sharedTreeCore.js +6 -0
  107. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  108. package/dist/simple-tree/arrayNode.js +1 -1
  109. package/dist/simple-tree/arrayNode.js.map +1 -1
  110. package/dist/simple-tree/index.d.ts +3 -3
  111. package/dist/simple-tree/index.d.ts.map +1 -1
  112. package/dist/simple-tree/index.js +2 -1
  113. package/dist/simple-tree/index.js.map +1 -1
  114. package/dist/simple-tree/proxies.d.ts.map +1 -1
  115. package/dist/simple-tree/proxies.js +7 -21
  116. package/dist/simple-tree/proxies.js.map +1 -1
  117. package/dist/simple-tree/proxyBinding.d.ts +4 -0
  118. package/dist/simple-tree/proxyBinding.d.ts.map +1 -1
  119. package/dist/simple-tree/proxyBinding.js +23 -1
  120. package/dist/simple-tree/proxyBinding.js.map +1 -1
  121. package/dist/simple-tree/tree.d.ts.map +1 -1
  122. package/dist/simple-tree/tree.js +1 -1
  123. package/dist/simple-tree/tree.js.map +1 -1
  124. package/dist/simple-tree/treeNodeApi.d.ts +2 -75
  125. package/dist/simple-tree/treeNodeApi.d.ts.map +1 -1
  126. package/dist/simple-tree/treeNodeApi.js +7 -15
  127. package/dist/simple-tree/treeNodeApi.js.map +1 -1
  128. package/dist/simple-tree/treeNodeKernel.d.ts +26 -0
  129. package/dist/simple-tree/treeNodeKernel.d.ts.map +1 -0
  130. package/dist/simple-tree/treeNodeKernel.js +83 -0
  131. package/dist/simple-tree/treeNodeKernel.js.map +1 -0
  132. package/dist/simple-tree/types.d.ts +73 -0
  133. package/dist/simple-tree/types.d.ts.map +1 -1
  134. package/dist/simple-tree/types.js +89 -1
  135. package/dist/simple-tree/types.js.map +1 -1
  136. package/dist/util/breakable.js +1 -1
  137. package/dist/util/breakable.js.map +1 -1
  138. package/dist/util/index.d.ts +1 -1
  139. package/dist/util/index.d.ts.map +1 -1
  140. package/dist/util/index.js.map +1 -1
  141. package/lib/core/forest/editableForest.d.ts.map +1 -1
  142. package/lib/core/forest/editableForest.js +4 -2
  143. package/lib/core/forest/editableForest.js.map +1 -1
  144. package/lib/core/tree/anchorSet.d.ts +1 -0
  145. package/lib/core/tree/anchorSet.d.ts.map +1 -1
  146. package/lib/core/tree/anchorSet.js +13 -0
  147. package/lib/core/tree/anchorSet.js.map +1 -1
  148. package/lib/core/tree/detachedFieldIndex.d.ts +48 -11
  149. package/lib/core/tree/detachedFieldIndex.d.ts.map +1 -1
  150. package/lib/core/tree/detachedFieldIndex.js +145 -21
  151. package/lib/core/tree/detachedFieldIndex.js.map +1 -1
  152. package/lib/core/tree/detachedFieldIndexCodec.d.ts.map +1 -1
  153. package/lib/core/tree/detachedFieldIndexCodec.js +13 -4
  154. package/lib/core/tree/detachedFieldIndexCodec.js.map +1 -1
  155. package/lib/core/tree/detachedFieldIndexFormat.d.ts +1 -1
  156. package/lib/core/tree/detachedFieldIndexFormat.d.ts.map +1 -1
  157. package/lib/core/tree/detachedFieldIndexFormat.js.map +1 -1
  158. package/lib/core/tree/detachedFieldIndexTypes.d.ts +39 -4
  159. package/lib/core/tree/detachedFieldIndexTypes.d.ts.map +1 -1
  160. package/lib/core/tree/detachedFieldIndexTypes.js.map +1 -1
  161. package/lib/core/tree/index.d.ts +2 -1
  162. package/lib/core/tree/index.d.ts.map +1 -1
  163. package/lib/core/tree/index.js.map +1 -1
  164. package/lib/core/tree/visitDelta.d.ts +3 -1
  165. package/lib/core/tree/visitDelta.d.ts.map +1 -1
  166. package/lib/core/tree/visitDelta.js +31 -15
  167. package/lib/core/tree/visitDelta.js.map +1 -1
  168. package/lib/core/tree/visitorUtils.d.ts +3 -3
  169. package/lib/core/tree/visitorUtils.d.ts.map +1 -1
  170. package/lib/core/tree/visitorUtils.js +4 -4
  171. package/lib/core/tree/visitorUtils.js.map +1 -1
  172. package/lib/feature-libraries/editableTreeBinder.js +1 -1
  173. package/lib/feature-libraries/editableTreeBinder.js.map +1 -1
  174. package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts +1 -8
  175. package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
  176. package/lib/feature-libraries/flex-map-tree/mapTreeNode.js +2 -54
  177. package/lib/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
  178. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +1 -13
  179. package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
  180. package/lib/feature-libraries/flex-tree/flexTreeTypes.js +0 -2
  181. package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
  182. package/lib/feature-libraries/flex-tree/index.d.ts +2 -1
  183. package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
  184. package/lib/feature-libraries/flex-tree/index.js +2 -1
  185. package/lib/feature-libraries/flex-tree/index.js.map +1 -1
  186. package/lib/feature-libraries/flex-tree/lazyEntity.d.ts +1 -2
  187. package/lib/feature-libraries/flex-tree/lazyEntity.d.ts.map +1 -1
  188. package/lib/feature-libraries/flex-tree/lazyEntity.js.map +1 -1
  189. package/lib/feature-libraries/flex-tree/lazyField.d.ts +1 -2
  190. package/lib/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
  191. package/lib/feature-libraries/flex-tree/lazyField.js +12 -20
  192. package/lib/feature-libraries/flex-tree/lazyField.js.map +1 -1
  193. package/lib/feature-libraries/flex-tree/lazyNode.d.ts +1 -4
  194. package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
  195. package/lib/feature-libraries/flex-tree/lazyNode.js +3 -30
  196. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  197. package/lib/feature-libraries/forest-summary/forestSummarizer.d.ts.map +1 -1
  198. package/lib/feature-libraries/forest-summary/forestSummarizer.js +1 -1
  199. package/lib/feature-libraries/forest-summary/forestSummarizer.js.map +1 -1
  200. package/lib/feature-libraries/index.d.ts +1 -1
  201. package/lib/feature-libraries/index.d.ts.map +1 -1
  202. package/lib/feature-libraries/index.js +1 -1
  203. package/lib/feature-libraries/index.js.map +1 -1
  204. package/lib/feature-libraries/modular-schema/discrepancies.js +3 -3
  205. package/lib/feature-libraries/modular-schema/discrepancies.js.map +1 -1
  206. package/lib/feature-libraries/modular-schema/modularChangeCodecs.js +1 -1
  207. package/lib/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
  208. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  209. package/lib/feature-libraries/modular-schema/modularChangeFamily.js +14 -17
  210. package/lib/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  211. package/lib/feature-libraries/object-forest/objectForest.d.ts +3 -2
  212. package/lib/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
  213. package/lib/feature-libraries/object-forest/objectForest.js +5 -4
  214. package/lib/feature-libraries/object-forest/objectForest.js.map +1 -1
  215. package/lib/packageVersion.d.ts +1 -1
  216. package/lib/packageVersion.d.ts.map +1 -1
  217. package/lib/packageVersion.js +1 -1
  218. package/lib/packageVersion.js.map +1 -1
  219. package/lib/shared-tree/sharedTree.d.ts +5 -1
  220. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  221. package/lib/shared-tree/sharedTree.js +8 -1
  222. package/lib/shared-tree/sharedTree.js.map +1 -1
  223. package/lib/shared-tree/sharedTreeChangeEnricher.js +1 -1
  224. package/lib/shared-tree/sharedTreeChangeEnricher.js.map +1 -1
  225. package/lib/shared-tree/treeApi.js +1 -1
  226. package/lib/shared-tree/treeApi.js.map +1 -1
  227. package/lib/shared-tree/treeCheckout.d.ts +10 -1
  228. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  229. package/lib/shared-tree/treeCheckout.js +47 -4
  230. package/lib/shared-tree/treeCheckout.js.map +1 -1
  231. package/lib/shared-tree/treeView.d.ts.map +1 -1
  232. package/lib/shared-tree/treeView.js +4 -0
  233. package/lib/shared-tree/treeView.js.map +1 -1
  234. package/lib/shared-tree-core/branch.d.ts +6 -0
  235. package/lib/shared-tree-core/branch.d.ts.map +1 -1
  236. package/lib/shared-tree-core/branch.js +2 -0
  237. package/lib/shared-tree-core/branch.js.map +1 -1
  238. package/lib/shared-tree-core/sharedTreeCore.d.ts +4 -0
  239. package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
  240. package/lib/shared-tree-core/sharedTreeCore.js +6 -0
  241. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  242. package/lib/simple-tree/arrayNode.js +2 -2
  243. package/lib/simple-tree/arrayNode.js.map +1 -1
  244. package/lib/simple-tree/index.d.ts +3 -3
  245. package/lib/simple-tree/index.d.ts.map +1 -1
  246. package/lib/simple-tree/index.js +1 -1
  247. package/lib/simple-tree/index.js.map +1 -1
  248. package/lib/simple-tree/proxies.d.ts.map +1 -1
  249. package/lib/simple-tree/proxies.js +7 -21
  250. package/lib/simple-tree/proxies.js.map +1 -1
  251. package/lib/simple-tree/proxyBinding.d.ts +4 -0
  252. package/lib/simple-tree/proxyBinding.d.ts.map +1 -1
  253. package/lib/simple-tree/proxyBinding.js +19 -0
  254. package/lib/simple-tree/proxyBinding.js.map +1 -1
  255. package/lib/simple-tree/tree.d.ts.map +1 -1
  256. package/lib/simple-tree/tree.js +1 -1
  257. package/lib/simple-tree/tree.js.map +1 -1
  258. package/lib/simple-tree/treeNodeApi.d.ts +2 -75
  259. package/lib/simple-tree/treeNodeApi.d.ts.map +1 -1
  260. package/lib/simple-tree/treeNodeApi.js +9 -17
  261. package/lib/simple-tree/treeNodeApi.js.map +1 -1
  262. package/lib/simple-tree/treeNodeKernel.d.ts +26 -0
  263. package/lib/simple-tree/treeNodeKernel.d.ts.map +1 -0
  264. package/lib/simple-tree/treeNodeKernel.js +79 -0
  265. package/lib/simple-tree/treeNodeKernel.js.map +1 -0
  266. package/lib/simple-tree/types.d.ts +73 -0
  267. package/lib/simple-tree/types.d.ts.map +1 -1
  268. package/lib/simple-tree/types.js +90 -2
  269. package/lib/simple-tree/types.js.map +1 -1
  270. package/lib/util/breakable.js +1 -1
  271. package/lib/util/breakable.js.map +1 -1
  272. package/lib/util/index.d.ts +1 -1
  273. package/lib/util/index.d.ts.map +1 -1
  274. package/lib/util/index.js.map +1 -1
  275. package/package.json +22 -22
  276. package/src/core/forest/editableForest.ts +10 -2
  277. package/src/core/tree/anchorSet.ts +14 -0
  278. package/src/core/tree/detachedFieldIndex.ts +217 -35
  279. package/src/core/tree/detachedFieldIndexCodec.ts +17 -8
  280. package/src/core/tree/detachedFieldIndexFormat.ts +1 -1
  281. package/src/core/tree/detachedFieldIndexTypes.ts +41 -5
  282. package/src/core/tree/index.ts +2 -1
  283. package/src/core/tree/visitDelta.ts +57 -16
  284. package/src/core/tree/visitorUtils.ts +7 -4
  285. package/src/feature-libraries/editableTreeBinder.ts +1 -1
  286. package/src/feature-libraries/flex-map-tree/mapTreeNode.ts +1 -65
  287. package/src/feature-libraries/flex-tree/flexTreeTypes.ts +0 -19
  288. package/src/feature-libraries/flex-tree/index.ts +7 -1
  289. package/src/feature-libraries/flex-tree/lazyEntity.ts +0 -3
  290. package/src/feature-libraries/flex-tree/lazyField.ts +14 -26
  291. package/src/feature-libraries/flex-tree/lazyNode.ts +1 -42
  292. package/src/feature-libraries/forest-summary/forestSummarizer.ts +1 -0
  293. package/src/feature-libraries/index.ts +3 -0
  294. package/src/feature-libraries/modular-schema/discrepancies.ts +3 -3
  295. package/src/feature-libraries/modular-schema/modularChangeCodecs.ts +1 -1
  296. package/src/feature-libraries/modular-schema/modularChangeFamily.ts +22 -20
  297. package/src/feature-libraries/object-forest/objectForest.ts +7 -3
  298. package/src/packageVersion.ts +1 -1
  299. package/src/shared-tree/sharedTree.ts +8 -1
  300. package/src/shared-tree/sharedTreeChangeEnricher.ts +1 -1
  301. package/src/shared-tree/treeApi.ts +1 -1
  302. package/src/shared-tree/treeCheckout.ts +56 -5
  303. package/src/shared-tree/treeView.ts +5 -0
  304. package/src/shared-tree-core/branch.ts +9 -0
  305. package/src/shared-tree-core/sharedTreeCore.ts +7 -0
  306. package/src/simple-tree/arrayNode.ts +2 -2
  307. package/src/simple-tree/index.ts +3 -3
  308. package/src/simple-tree/proxies.ts +8 -29
  309. package/src/simple-tree/proxyBinding.ts +23 -0
  310. package/src/simple-tree/tree.ts +4 -1
  311. package/src/simple-tree/treeNodeApi.ts +14 -96
  312. package/src/simple-tree/treeNodeKernel.ts +91 -0
  313. package/src/simple-tree/types.ts +233 -2
  314. package/src/util/breakable.ts +1 -1
  315. package/src/util/index.ts +1 -0
@@ -387,6 +387,11 @@ export class TreeCheckout implements ITreeCheckoutFork {
387
387
  SharedTreeBranch<SharedTreeEditBuilder, SharedTreeChange>
388
388
  >();
389
389
 
390
+ /**
391
+ * copies of the removed roots used as snapshots for reverting to previous state when transactions are aborted
392
+ */
393
+ private readonly removedRootsSnapshots: DetachedFieldIndex[] = [];
394
+
390
395
  /**
391
396
  * The name of the telemetry event logged for calls to {@link TreeCheckout.revertRevertible}.
392
397
  * @privateRemarks Exposed for testing purposes.
@@ -405,7 +410,7 @@ export class TreeCheckout implements ITreeCheckoutFork {
405
410
  private readonly mintRevisionTag: () => RevisionTag,
406
411
  private readonly revisionTagCodec: RevisionTagCodec,
407
412
  private readonly idCompressor: IIdCompressor,
408
- private readonly removedRoots: DetachedFieldIndex = makeDetachedFieldIndex(
413
+ private removedRoots: DetachedFieldIndex = makeDetachedFieldIndex(
409
414
  "repair",
410
415
  revisionTagCodec,
411
416
  idCompressor,
@@ -413,18 +418,38 @@ export class TreeCheckout implements ITreeCheckoutFork {
413
418
  /** Optional logger for telemetry. */
414
419
  private readonly logger?: ITelemetryLoggerExt,
415
420
  ) {
421
+ // when a transaction is started, take a snapshot of the current state of removed roots
422
+ branch.on("transactionStarted", () => {
423
+ this.removedRootsSnapshots.push(this.removedRoots.clone());
424
+ });
425
+ // when a transaction is committed, the latest snapshot of removed roots can be discarded
426
+ branch.on("transactionCommitted", () => {
427
+ this.removedRootsSnapshots.pop();
428
+ });
429
+ // after a transaction is rolled back, revert removed roots back to the latest snapshot
430
+ branch.on("transactionRolledBack", () => {
431
+ const snapshot = this.removedRootsSnapshots.pop();
432
+ assert(snapshot !== undefined, 0x9ae /* a snapshot for removed roots does not exist */);
433
+ this.removedRoots = snapshot;
434
+ });
435
+
416
436
  // We subscribe to `beforeChange` rather than `afterChange` here because it's possible that the change is invalid WRT our forest.
417
437
  // For example, a bug in the editor might produce a malformed change object and thus applying the change to the forest will throw an error.
418
438
  // In such a case we will crash here, preventing the change from being added to the commit graph, and preventing `afterChange` from firing.
419
439
  // One important consequence of this is that we will not submit the op containing the invalid change, since op submissions happens in response to `afterChange`.
420
440
  branch.on("beforeChange", (event) => {
421
441
  if (event.change !== undefined) {
442
+ const revision =
443
+ event.type === "replace"
444
+ ? event.newCommits[event.newCommits.length - 1].revision
445
+ : event.change.revision;
446
+
422
447
  // Conflicts due to schema will be empty and thus are not applied.
423
448
  for (const change of event.change.change.changes) {
424
449
  if (change.type === "data") {
425
- const delta = intoDelta(tagChange(change.innerChange, event.change.revision));
450
+ const delta = intoDelta(tagChange(change.innerChange, revision));
426
451
  this.withCombinedVisitor((visitor) => {
427
- visitDelta(delta, visitor, this.removedRoots);
452
+ visitDelta(delta, visitor, this.removedRoots, revision);
428
453
  });
429
454
  } else if (change.type === "schema") {
430
455
  // Schema changes from a current to a new schema are expected to be backwards compatible.
@@ -518,6 +543,24 @@ export class TreeCheckout implements ITreeCheckoutFork {
518
543
  this.events.emit("commitApplied", data, getRevertible);
519
544
  withinEventContext = false;
520
545
  });
546
+
547
+ // When the branch is trimmed, we can garbage collect any repair data whose latest relevant revision is one of the
548
+ // trimmed revisions.
549
+ branch.on("ancestryTrimmed", (revisions) => {
550
+ this.withCombinedVisitor((visitor) => {
551
+ revisions.forEach((revision) => {
552
+ // get all the roots last created or used by the revision
553
+ const roots = this.removedRoots.getRootsLastTouchedByRevision(revision);
554
+
555
+ // get the detached field for the root and delete it from the removed roots
556
+ for (const root of roots) {
557
+ visitor.destroy(this.removedRoots.toFieldKey(root), 1);
558
+ }
559
+
560
+ this.removedRoots.deleteRootsLastTouchedByRevision(revision);
561
+ });
562
+ });
563
+ });
521
564
  }
522
565
 
523
566
  private withCombinedVisitor(fn: (visitor: DeltaVisitor) => void): void {
@@ -574,7 +617,7 @@ export class TreeCheckout implements ITreeCheckoutFork {
574
617
  this.checkNotDisposed();
575
618
  assert(
576
619
  !view.transaction.inProgress(),
577
- "A view cannot be rebased while it has a pending transaction",
620
+ 0x9af /* A view cannot be rebased while it has a pending transaction */,
578
621
  );
579
622
  view.branch.rebaseOnto(this.branch);
580
623
  }
@@ -590,7 +633,7 @@ export class TreeCheckout implements ITreeCheckoutFork {
590
633
  this.checkNotDisposed();
591
634
  assert(
592
635
  !this.transaction.inProgress(),
593
- "Views cannot be merged into a view while it has a pending transaction",
636
+ 0x9b0 /* Views cannot be merged into a view while it has a pending transaction */,
594
637
  );
595
638
  while (view.transaction.inProgress()) {
596
639
  view.transaction.commit();
@@ -629,6 +672,14 @@ export class TreeCheckout implements ITreeCheckoutFork {
629
672
  return trees;
630
673
  }
631
674
 
675
+ /**
676
+ * This sets the tip revision as the latest relevant revision for any removed roots that are loaded from a summary.
677
+ * This needs to be called right after loading {@link this.removedRoots} from a summary to allow loaded data to be garbage collected.
678
+ */
679
+ public setTipRevisionForLoadedData(revision: RevisionTag): void {
680
+ this.removedRoots.setRevisionsForLoadedData(revision);
681
+ }
682
+
632
683
  private purgeRevertibles(): void {
633
684
  for (const revertible of this.revertibles) {
634
685
  revertible.dispose();
@@ -12,6 +12,7 @@ import {
12
12
  type NodeKeyManager,
13
13
  getTreeContext,
14
14
  } from "../feature-libraries/index.js";
15
+ import { tryDisposeTreeNode } from "../simple-tree/index.js";
15
16
  import { type IDisposable, disposeSymbol } from "../util/index.js";
16
17
 
17
18
  import type { ITreeCheckout, ITreeCheckoutFork, TreeCheckout } from "./treeCheckout.js";
@@ -94,6 +95,10 @@ export class CheckoutFlexTreeView<
94
95
  }
95
96
 
96
97
  public [disposeSymbol](): void {
98
+ for (const anchorNode of this.checkout.forest.anchors) {
99
+ tryDisposeTreeNode(anchorNode);
100
+ }
101
+
97
102
  this.context[disposeSymbol]();
98
103
  this.onDispose?.();
99
104
  }
@@ -137,6 +137,13 @@ export interface SharedTreeBranchEvents<TEditor extends ChangeFamilyEditor, TCha
137
137
  */
138
138
  transactionAborted(isOuterTransaction: boolean): void;
139
139
 
140
+ /**
141
+ * Fired after the current transaction is completely rolled back.
142
+ * @param isOuterTransaction - true iff the transaction being aborted is the outermost transaction
143
+ * as opposed to a nested transaction.
144
+ */
145
+ transactionRolledBack(isOuterTransaction: boolean): void;
146
+
140
147
  /**
141
148
  * Fired after the current transaction is committed.
142
149
  * @param isOuterTransaction - true iff the transaction being committed is the outermost transaction
@@ -361,6 +368,7 @@ export class SharedTreeBranch<
361
368
 
362
369
  this.emit("transactionAborted", this.transactions.size === 0);
363
370
  if (commits.length === 0) {
371
+ this.emit("transactionRolledBack", this.transactions.size === 0);
364
372
  return [undefined, []];
365
373
  }
366
374
 
@@ -387,6 +395,7 @@ export class SharedTreeBranch<
387
395
  this.emit("beforeChange", changeEvent);
388
396
  this.head = startCommit;
389
397
  this.emit("afterChange", changeEvent);
398
+ this.emit("transactionRolledBack", this.transactions.size === 0);
390
399
  return [change, commits];
391
400
  }
392
401
 
@@ -95,6 +95,13 @@ export class SharedTreeCore<TEditor extends ChangeFamilyEditor, TChange>
95
95
  return this.getLocalBranch().editor;
96
96
  }
97
97
 
98
+ /**
99
+ * Gets the revision at the head of the trunk.
100
+ */
101
+ protected get trunkHeadRevision(): RevisionTag {
102
+ return this.editManager.getTrunkHead().revision;
103
+ }
104
+
98
105
  /**
99
106
  * Used to encode/decode messages sent to/received from the Fluid runtime.
100
107
  *
@@ -27,7 +27,7 @@ import {
27
27
  markContentType,
28
28
  prepareContentForHydration,
29
29
  } from "./proxies.js";
30
- import { getFlexNode } from "./proxyBinding.js";
30
+ import { getFlexNode, getKernel } from "./proxyBinding.js";
31
31
  import {
32
32
  NodeKind,
33
33
  type ImplicitAllowedTypes,
@@ -683,7 +683,7 @@ abstract class CustomArrayNodeBase<const T extends ImplicitAllowedTypes>
683
683
  input: Iterable<InsertableTreeNodeFromImplicitAllowedTypes<T>> | InternalTreeNode,
684
684
  ) {
685
685
  super(input);
686
- getFlexNode(this).on("nodeChanged", () => {
686
+ getKernel(this).on("nodeChanged", () => {
687
687
  this.#generationNumber += 1;
688
688
  });
689
689
  }
@@ -39,8 +39,8 @@ export {
39
39
  type ApplyKind,
40
40
  } from "./schemaTypes.js";
41
41
  export { SchemaFactory, type ScopedSchemaName } from "./schemaFactory.js";
42
- export { getFlexNode } from "./proxyBinding.js";
43
- export { treeNodeApi, type TreeNodeApi, type TreeChangeEvents } from "./treeNodeApi.js";
42
+ export { getFlexNode, tryDisposeTreeNode } from "./proxyBinding.js";
43
+ export { treeNodeApi, type TreeNodeApi } from "./treeNodeApi.js";
44
44
  export { toFlexSchema, cursorFromUnhydratedRoot } from "./toFlexSchema.js";
45
45
  export type {
46
46
  FieldHasDefaultUnsafe,
@@ -82,7 +82,7 @@ export {
82
82
 
83
83
  // TreeNode is only type exported, which prevents use of the class object for unsupported use-cases like direct sub-classing and instancof.
84
84
  // See docs on TreeNode for more details.
85
- export type { TreeNode, Unhydrated, InternalTreeNode } from "./types.js";
85
+ export type { TreeChangeEvents, TreeNode, Unhydrated, InternalTreeNode } from "./types.js";
86
86
  export {
87
87
  TreeArrayNode,
88
88
  IterableTreeArrayContent,
@@ -20,9 +20,7 @@ import {
20
20
  type FlexFieldSchema,
21
21
  type FlexTreeField,
22
22
  type FlexTreeNode,
23
- type FlexTreeNodeEvents,
24
23
  type FlexTreeTypedField,
25
- type MapTreeNode,
26
24
  tryGetMapTreeNode,
27
25
  typeNameSymbol,
28
26
  isFlexTreeNode,
@@ -32,7 +30,6 @@ import { type Mutable, fail, isReadonlyArray } from "../util/index.js";
32
30
  import { anchorProxy, tryGetFlexNode, tryGetProxy } from "./proxyBinding.js";
33
31
  import { tryGetSimpleNodeSchema } from "./schemaCaching.js";
34
32
  import type { TreeNode, Unhydrated } from "./types.js";
35
- import type { Off } from "../events/index.js";
36
33
 
37
34
  /**
38
35
  * Detects if the given 'candidate' is a TreeNode.
@@ -116,7 +113,6 @@ export function getOrCreateNodeProxy(flexNode: FlexTreeNode): TreeNode | TreeVal
116
113
  /** The path of a proxy, relative to the root of the content tree that the proxy belongs to */
117
114
  interface RelativeProxyPath {
118
115
  readonly path: UpPath;
119
- readonly mapTreeNode: MapTreeNode;
120
116
  readonly proxy: TreeNode;
121
117
  }
122
118
 
@@ -150,8 +146,8 @@ export function prepareContentForHydration(
150
146
  proxyPaths: [],
151
147
  };
152
148
 
153
- walkMapTree(content, proxies.rootPath, (p, mapTreeNode, proxy) => {
154
- proxies.proxyPaths.push({ path: p, mapTreeNode, proxy });
149
+ walkMapTree(content, proxies.rootPath, (p, proxy) => {
150
+ proxies.proxyPaths.push({ path: p, proxy });
155
151
  });
156
152
 
157
153
  bindProxies([proxies], forest);
@@ -172,8 +168,8 @@ function prepareArrayContentForHydration(
172
168
  },
173
169
  proxyPaths: [],
174
170
  });
175
- walkMapTree(content[i], proxies[i].rootPath, (p, mapTreeNode, proxy) => {
176
- proxies[i].proxyPaths.push({ path: p, mapTreeNode, proxy });
171
+ walkMapTree(content[i], proxies[i].rootPath, (p, proxy) => {
172
+ proxies[i].proxyPaths.push({ path: p, proxy });
177
173
  });
178
174
  }
179
175
 
@@ -183,13 +179,13 @@ function prepareArrayContentForHydration(
183
179
  function walkMapTree(
184
180
  mapTree: MapTree,
185
181
  path: UpPath,
186
- onVisitTreeNode: (path: UpPath, mapTreeNode: MapTreeNode, treeNode: TreeNode) => void,
182
+ onVisitTreeNode: (path: UpPath, treeNode: TreeNode) => void,
187
183
  ): void {
188
184
  const mapTreeNode = tryGetMapTreeNode(mapTree);
189
185
  if (mapTreeNode !== undefined) {
190
186
  const treeNode = tryGetProxy(mapTreeNode);
191
187
  if (treeNode !== undefined) {
192
- onVisitTreeNode(path, mapTreeNode, treeNode);
188
+ onVisitTreeNode(path, treeNode);
193
189
  }
194
190
  }
195
191
 
@@ -215,25 +211,8 @@ function bindProxies(proxies: RootedProxyPaths[], forest: IForestSubscription):
215
211
  let i = 0;
216
212
  const off = forest.on("afterRootFieldCreated", (fieldKey) => {
217
213
  (proxies[i].rootPath as Mutable<UpPath>).parentField = fieldKey;
218
- for (const { path, mapTreeNode, proxy } of proxies[i].proxyPaths) {
219
- const anchorNode = anchorProxy(forest.anchors, path, proxy);
220
- mapTreeNode.forwardEvents({
221
- on<K extends keyof FlexTreeNodeEvents>(
222
- eventName: K,
223
- listener: FlexTreeNodeEvents[K],
224
- ): Off {
225
- switch (eventName) {
226
- case "nodeChanged": {
227
- return anchorNode.on("childrenChangedAfterBatch", listener);
228
- }
229
- case "treeChanged": {
230
- return anchorNode.on("subtreeChangedAfterBatch", listener);
231
- }
232
- default:
233
- fail("Unexpected event subscription");
234
- }
235
- },
236
- });
214
+ for (const { path, proxy } of proxies[i].proxyPaths) {
215
+ anchorProxy(forest.anchors, path, proxy);
237
216
  }
238
217
  if (++i === proxies.length) {
239
218
  off();
@@ -29,6 +29,7 @@ import type { TreeNode } from "./types.js";
29
29
  // eslint-disable-next-line import/no-internal-modules
30
30
  import { makeTree } from "../feature-libraries/flex-tree/lazyNode.js";
31
31
  import type { TreeMapNode } from "./mapNode.js";
32
+ import { TreeNodeKernel } from "./treeNodeKernel.js";
32
33
 
33
34
  // This file contains various maps and helpers for supporting proxy binding (a.k.a. proxy hydration).
34
35
  // See ./ProxyBinding.md for a high-level overview of the process.
@@ -185,9 +186,31 @@ function bindProxyToAnchorNode(proxy: TreeNode, anchorNode: AnchorNode): void {
185
186
  proxyToAnchorNode.set(proxy, anchorNode);
186
187
  // However, it's fine for an anchor node to rotate through different proxies when the content at that place in the tree is replaced.
187
188
  anchorNode.slots.set(proxySlot, proxy);
189
+ getKernel(proxy).hydrate(anchorNode);
188
190
  }
189
191
 
190
192
  /**
191
193
  * Given a node's schema, return the corresponding object in the proxy-based API.
192
194
  */
193
195
  type TypedNode<TSchema extends FlexTreeNodeSchema> = TreeNode & WithType<TSchema["name"]>;
196
+
197
+ export function createKernel(node: TreeNode): void {
198
+ treeNodeToKernel.set(node, new TreeNodeKernel(node));
199
+ }
200
+
201
+ export function getKernel(node: TreeNode): TreeNodeKernel {
202
+ const kernel = treeNodeToKernel.get(node);
203
+ assert(kernel !== undefined, 0x9b1 /* Expected tree node to have kernel */);
204
+ return kernel;
205
+ }
206
+
207
+ export function tryDisposeTreeNode(anchorNode: AnchorNode): void {
208
+ const treeNode = anchorNode.slots.get(proxySlot);
209
+ if (treeNode !== undefined) {
210
+ const kernel = treeNodeToKernel.get(treeNode);
211
+ assert(kernel !== undefined, 0x9b2 /* Expected tree node to have kernel */);
212
+ kernel.dispose();
213
+ }
214
+ }
215
+
216
+ const treeNodeToKernel = new WeakMap<TreeNode, TreeNodeKernel>();
@@ -165,7 +165,10 @@ export function walkNodeSchema(
165
165
  walkAllowedTypes(field.allowedTypeSet, visitor, visitedSet);
166
166
  }
167
167
  } else {
168
- assert(schema.kind === NodeKind.Array || schema.kind === NodeKind.Map, "invalid schema");
168
+ assert(
169
+ schema.kind === NodeKind.Array || schema.kind === NodeKind.Map,
170
+ 0x9b3 /* invalid schema */,
171
+ );
169
172
  const childTypes = schema.info as ImplicitAllowedTypes;
170
173
  walkFieldSchema(childTypes, visitor, visitedSet);
171
174
  }
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
6
+ import { assert } from "@fluidframework/core-utils/internal";
7
7
 
8
8
  import { Multiplicity, rootFieldKey } from "../core/index.js";
9
9
  import {
@@ -16,7 +16,7 @@ import {
16
16
  import { fail, extractFromOpaque, isReadonlyArray } from "../util/index.js";
17
17
 
18
18
  import { getOrCreateNodeProxy, isTreeNode } from "./proxies.js";
19
- import { getFlexNode } from "./proxyBinding.js";
19
+ import { getFlexNode, getKernel } from "./proxyBinding.js";
20
20
  import { tryGetSimpleNodeSchema } from "./schemaCaching.js";
21
21
  import {
22
22
  NodeKind,
@@ -27,7 +27,7 @@ import {
27
27
  type ImplicitAllowedTypes,
28
28
  type TreeNodeFromImplicitAllowedTypes,
29
29
  } from "./schemaTypes.js";
30
- import type { TreeNode } from "./types.js";
30
+ import type { TreeNode, TreeChangeEvents } from "./types.js";
31
31
  import {
32
32
  booleanSchema,
33
33
  handleSchema,
@@ -37,6 +37,7 @@ import {
37
37
  } from "./leafNodeSchema.js";
38
38
  import { isFluidHandle } from "@fluidframework/runtime-utils/internal";
39
39
  import { UsageError } from "@fluidframework/telemetry-utils/internal";
40
+ import type { Off } from "../events/index.js";
40
41
 
41
42
  /**
42
43
  * Provides various functions for analyzing {@link TreeNode}s.
@@ -102,7 +103,7 @@ export interface TreeNodeApi {
102
103
  /**
103
104
  * Returns the {@link TreeStatus} of the given node.
104
105
  */
105
- readonly status: (node: TreeNode) => TreeStatus;
106
+ status(node: TreeNode): TreeStatus;
106
107
 
107
108
  /**
108
109
  * Returns the {@link SchemaFactory.identifier | identifier} of the given node in the most compressed form possible.
@@ -127,7 +128,7 @@ export interface TreeNodeApi {
127
128
  * The `Tree` object holds various functions for analyzing {@link TreeNode}s.
128
129
  */
129
130
  export const treeNodeApi: TreeNodeApi = {
130
- parent: (node: TreeNode): TreeNode | undefined => {
131
+ parent(node: TreeNode): TreeNode | undefined {
131
132
  const editNode = getFlexNode(node).parentField.parent.parent;
132
133
  if (editNode === undefined) {
133
134
  return undefined;
@@ -140,7 +141,7 @@ export const treeNodeApi: TreeNodeApi = {
140
141
  );
141
142
  return output;
142
143
  },
143
- key: (node: TreeNode) => {
144
+ key(node: TreeNode): string | number {
144
145
  // If the parent is undefined, then this node is under the root field,
145
146
  // so we know its key is the special root one.
146
147
  const parent = treeNodeApi.parent(node);
@@ -156,28 +157,20 @@ export const treeNodeApi: TreeNodeApi = {
156
157
  const viewKey = getViewKeyFromStoredKey(parentSchema, storedKey);
157
158
  return viewKey;
158
159
  },
159
- on: <K extends keyof TreeChangeEvents>(
160
+ on<K extends keyof TreeChangeEvents>(
160
161
  node: TreeNode,
161
162
  eventName: K,
162
163
  listener: TreeChangeEvents[K],
163
- ) => {
164
- const flex = getFlexNode(node);
165
- switch (eventName) {
166
- case "nodeChanged":
167
- return flex.on("nodeChanged", listener);
168
- case "treeChanged":
169
- return flex.on("treeChanged", listener);
170
- default:
171
- return unreachableCase(eventName);
172
- }
164
+ ): Off {
165
+ return getKernel(node).on(eventName, listener);
173
166
  },
174
- status: (node: TreeNode) => {
175
- return getFlexNode(node, true).treeStatus();
167
+ status(node: TreeNode): TreeStatus {
168
+ return getKernel(node).getStatus();
176
169
  },
177
- is: <TSchema extends ImplicitAllowedTypes>(
170
+ is<TSchema extends ImplicitAllowedTypes>(
178
171
  value: unknown,
179
172
  schema: TSchema,
180
- ): value is TreeNodeFromImplicitAllowedTypes<TSchema> => {
173
+ ): value is TreeNodeFromImplicitAllowedTypes<TSchema> {
181
174
  const actualSchema = tryGetSchema(value);
182
175
  if (actualSchema === undefined) {
183
176
  return false;
@@ -304,78 +297,3 @@ function getViewKeyFromStoredKey(
304
297
 
305
298
  return storedKey;
306
299
  }
307
-
308
- /**
309
- * A collection of events that can be emitted by a {@link TreeNode}.
310
- *
311
- * @privateRemarks
312
- * TODO: add a way to subscribe to a specific field (for nodeChanged and treeChanged).
313
- * Probably have object node and map node specific APIs for this.
314
- *
315
- * TODO: ensure that subscription API for fields aligns with API for subscribing to the root.
316
- *
317
- * TODO: add more wider area (avoid needing tons of nodeChanged registration) events for use-cases other than treeChanged.
318
- * Some ideas:
319
- *
320
- * - treeChanged, but with some subtrees/fields/paths excluded
321
- * - helper to batch several nodeChanged calls to a treeChanged scope
322
- * - parent change (ex: registration on the parent field for a specific index: maybe allow it for a range. Ex: node event takes optional field and optional index range?)
323
- * - new content inserted into subtree. Either provide event for this and/or enough info to treeChanged to find and search the new sub-trees.
324
- * Add separate (non event related) API to efficiently scan tree for given set of types (using low level cursor and schema based filtering)
325
- * to allow efficiently searching for new content (and initial content) of a given type.
326
- *
327
- * @sealed @public
328
- */
329
- export interface TreeChangeEvents {
330
- /**
331
- * Emitted by a node after a batch of changes has been applied to the tree, if a change affected the node, where a
332
- * change is:
333
- *
334
- * - For an object node, when the value of one of its properties changes (i.e., the property's value is set
335
- * to something else, including `undefined`).
336
- *
337
- * - For an array node, when an element is added, removed, or moved.
338
- *
339
- * - For a map node, when an entry is added, updated, or removed.
340
- *
341
- * @remarks
342
- * This event is not emitted when:
343
- *
344
- * - Properties of a child node change. Notably, updates to an array node or a map node (like adding or removing
345
- * elements/entries) will emit this event on the array/map node itself, but not on the node that contains the
346
- * array/map node as one of its properties.
347
- *
348
- * - The node is moved to a different location in the tree or removed from the tree.
349
- * In this case the event is emitted on the _parent_ node, not the node itself.
350
- *
351
- * For remote edits, this event is not guaranteed to occur in the same order or quantity that it did in
352
- * the client that made the original edit.
353
- *
354
- * When it is emitted, the tree is guaranteed to be in-schema.
355
- *
356
- * @privateRemarks
357
- * This event occurs whenever the apparent contents of the node instance change, regardless of what caused the change.
358
- * For example, it will fire when the local client reassigns a child, when part of a remote edit is applied to the
359
- * node, or when the node has to be updated due to resolution of a merge conflict
360
- * (for example a previously applied local change might be undone, then reapplied differently or not at all).
361
- */
362
- nodeChanged(): void;
363
-
364
- /**
365
- * Emitted by a node after a batch of changes has been applied to the tree, when something changed anywhere in the
366
- * subtree rooted at it.
367
- *
368
- * @remarks
369
- * This event is not emitted when the node itself is moved to a different location in the tree or removed from the tree.
370
- * In that case it is emitted on the _parent_ node, not the node itself.
371
- *
372
- * The node itself is part of the subtree, so this event will be emitted even if the only changes are to the properties
373
- * of the node itself.
374
- *
375
- * For remote edits, this event is not guaranteed to occur in the same order or quantity that it did in
376
- * the client that made the original edit.
377
- *
378
- * When it is emitted, the tree is guaranteed to be in-schema.
379
- */
380
- treeChanged(): void;
381
- }
@@ -0,0 +1,91 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert } from "@fluidframework/core-utils/internal";
7
+ import { createEmitter, type Listenable, type Off } from "../events/index.js";
8
+ import type { TreeChangeEvents, TreeNode } from "./types.js";
9
+ import type { AnchorNode } from "../core/index.js";
10
+ import {
11
+ flexTreeSlot,
12
+ isFreedSymbol,
13
+ LazyEntity,
14
+ TreeStatus,
15
+ treeStatusFromAnchorCache,
16
+ } from "../feature-libraries/index.js";
17
+
18
+ /**
19
+ * Contains state and an internal API for managing {@link TreeNode}s.
20
+ * @remarks All {@link TreeNode}s have an associated kernel object.
21
+ * The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states.
22
+ * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method.
23
+ */
24
+ export class TreeNodeKernel implements Listenable<TreeChangeEvents> {
25
+ #hydrated?: {
26
+ anchorNode: AnchorNode;
27
+ offAnchorNode: Off;
28
+ };
29
+ #events = createEmitter<TreeChangeEvents>();
30
+
31
+ public constructor(public readonly node: TreeNode) {}
32
+
33
+ public hydrate(anchorNode: AnchorNode): void {
34
+ const offChildrenChanged = anchorNode.on("childrenChangedAfterBatch", () => {
35
+ this.#events.emit("nodeChanged");
36
+ });
37
+
38
+ const offSubtreeChanged = anchorNode.on("subtreeChangedAfterBatch", () => {
39
+ this.#events.emit("treeChanged");
40
+ });
41
+
42
+ const offAfterDestroy = anchorNode.on("afterDestroy", () => this.dispose());
43
+
44
+ this.#hydrated = {
45
+ anchorNode,
46
+ offAnchorNode: () => {
47
+ offChildrenChanged();
48
+ offSubtreeChanged();
49
+ offAfterDestroy();
50
+ },
51
+ };
52
+ }
53
+
54
+ public dehydrate(): void {
55
+ this.#hydrated?.offAnchorNode?.();
56
+ this.#hydrated = undefined;
57
+ }
58
+
59
+ public isHydrated(): boolean {
60
+ return this.#hydrated !== undefined;
61
+ }
62
+
63
+ public getStatus(): TreeStatus {
64
+ if (this.#hydrated?.anchorNode === undefined) {
65
+ return TreeStatus.New;
66
+ }
67
+
68
+ // TODO: Replace this check with the proper check against the cursor state when the cursor becomes part of the kernel
69
+ const flex = this.#hydrated.anchorNode.slots.get(flexTreeSlot);
70
+ if (flex !== undefined) {
71
+ assert(flex instanceof LazyEntity, 0x9b4 /* Unexpected flex node implementation */);
72
+ if (flex[isFreedSymbol]()) {
73
+ return TreeStatus.Deleted;
74
+ }
75
+ }
76
+
77
+ return treeStatusFromAnchorCache(this.#hydrated.anchorNode);
78
+ }
79
+
80
+ public on<K extends keyof TreeChangeEvents>(
81
+ eventName: K,
82
+ listener: TreeChangeEvents[K],
83
+ ): Off {
84
+ return this.#events.on(eventName, listener);
85
+ }
86
+
87
+ public dispose(): void {
88
+ this.dehydrate();
89
+ // TODO: go to the context and remove myself from withAnchors
90
+ }
91
+ }