@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
@@ -7,43 +7,66 @@ import { assert } from "@fluidframework/core-utils/internal";
7
7
 
8
8
  import { type ICodecOptions, type IJsonCodec, noopValidator } from "../../codec/index.js";
9
9
  import {
10
- type Brand,
11
10
  type IdAllocator,
12
11
  type JsonCompatibleReadOnly,
13
12
  type NestedMap,
14
13
  brand,
15
14
  deleteFromNestedMap,
15
+ forEachInNestedMap,
16
16
  idAllocatorFromMaxId,
17
17
  populateNestedMap,
18
18
  setInNestedMap,
19
19
  tryGetFromNestedMap,
20
20
  } from "../../util/index.js";
21
- import type { RevisionTagCodec } from "../rebase/index.js";
21
+ import type { RevisionTag, RevisionTagCodec } from "../rebase/index.js";
22
22
  import type { FieldKey } from "../schema-stored/index.js";
23
23
 
24
24
  import type * as Delta from "./delta.js";
25
25
  import { makeDetachedNodeToFieldCodec } from "./detachedFieldIndexCodec.js";
26
26
  import type { Format } from "./detachedFieldIndexFormat.js";
27
- import type { DetachedFieldSummaryData, Major, Minor } from "./detachedFieldIndexTypes.js";
27
+ import type {
28
+ DetachedField,
29
+ DetachedFieldSummaryData,
30
+ ForestRootId,
31
+ Major,
32
+ Minor,
33
+ } from "./detachedFieldIndexTypes.js";
28
34
  import type { IIdCompressor } from "@fluidframework/id-compressor";
29
35
 
30
- /**
31
- * ID used to create a detached field key for a removed subtree.
32
- *
33
- * TODO: Move to Forest once forests can support multiple roots.
34
- * @internal
35
- */
36
- export type ForestRootId = Brand<number, "tree.ForestRootId">;
37
-
38
36
  /**
39
37
  * The tree index records detached field IDs and associates them with a change atom ID.
40
38
  */
41
39
  export class DetachedFieldIndex {
42
- // TODO: don't store the field key in the index, it can be derived from the root ID
43
- private detachedNodeToField: NestedMap<Major, Minor, ForestRootId> = new Map();
40
+ /**
41
+ * A mapping from detached node ids to detached fields.
42
+ */
43
+ private detachedNodeToField: NestedMap<Major, Minor, DetachedField> = new Map();
44
+ /**
45
+ * A map from revisions to all detached fields for which the revision is the latest relevant revision.
46
+ * See {@link DetachedField.latestRelevantRevision}.
47
+ *
48
+ * @remarks
49
+ * undefined revisions are tolerated but any roots not associated with a revision must be disposed manually
50
+ */
51
+ private latestRelevantRevisionToFields: NestedMap<
52
+ RevisionTag | undefined,
53
+ ForestRootId,
54
+ Delta.DetachedNodeId
55
+ > = new Map();
56
+
44
57
  private readonly codec: IJsonCodec<DetachedFieldSummaryData, Format>;
45
58
  private readonly options: ICodecOptions;
46
59
 
60
+ /**
61
+ * The process for loading `DetachedFieldIndex` data from a summary is split into two steps:
62
+ * 1. Call {@link loadData}
63
+ * 2. Call {@link setRevisionsForLoadedData}
64
+ *
65
+ * This flag is only set to `false` after calling `loadData` and is set back to `true` after calling `setRevisionsForLoadedData`.
66
+ * This helps ensure that `setRevisionsForLoadedData` is only called after `loadData` and only called once.
67
+ */
68
+ private isFullyLoaded = true;
69
+
47
70
  /**
48
71
  * @param name - A name for the index, used as a prefix for the generated field keys.
49
72
  * @param rootIdAllocator - An ID allocator used to generate unique field keys.
@@ -68,18 +91,31 @@ export class DetachedFieldIndex {
68
91
  this.options,
69
92
  );
70
93
  populateNestedMap(this.detachedNodeToField, clone.detachedNodeToField, true);
94
+ populateNestedMap(
95
+ this.latestRelevantRevisionToFields,
96
+ clone.latestRelevantRevisionToFields,
97
+ true,
98
+ );
71
99
  return clone;
72
100
  }
73
101
 
74
- public *entries(): Generator<{ root: ForestRootId } & { id: Delta.DetachedNodeId }> {
102
+ public *entries(): Generator<
103
+ { root: ForestRootId; latestRelevantRevision?: RevisionTag } & {
104
+ id: Delta.DetachedNodeId;
105
+ }
106
+ > {
75
107
  for (const [major, innerMap] of this.detachedNodeToField) {
76
108
  if (major !== undefined) {
77
- for (const [minor, entry] of innerMap) {
78
- yield { id: { major, minor }, root: entry };
109
+ for (const [minor, { root, latestRelevantRevision }] of innerMap) {
110
+ yield latestRelevantRevision !== undefined
111
+ ? { id: { major, minor }, root, latestRelevantRevision }
112
+ : { id: { major, minor }, root };
79
113
  }
80
114
  } else {
81
- for (const [minor, entry] of innerMap) {
82
- yield { id: { minor }, root: entry };
115
+ for (const [minor, { root, latestRelevantRevision }] of innerMap) {
116
+ yield latestRelevantRevision !== undefined
117
+ ? { id: { minor }, root, latestRelevantRevision }
118
+ : { id: { minor }, root };
83
119
  }
84
120
  }
85
121
  }
@@ -90,22 +126,61 @@ export class DetachedFieldIndex {
90
126
  */
91
127
  public purge(): void {
92
128
  this.detachedNodeToField.clear();
129
+ this.latestRelevantRevisionToFields.clear();
93
130
  }
94
131
 
95
132
  public updateMajor(current: Major, updated: Major): void {
96
- const innerCurrent = this.detachedNodeToField.get(current);
97
- if (innerCurrent !== undefined) {
98
- this.detachedNodeToField.delete(current);
99
- const innerUpdated = this.detachedNodeToField.get(updated);
100
- if (innerUpdated === undefined) {
101
- this.detachedNodeToField.set(updated, innerCurrent);
102
- } else {
133
+ // Update latestRelevantRevision information corresponding to `current`
134
+ {
135
+ const inner = this.latestRelevantRevisionToFields.get(current);
136
+ if (inner !== undefined) {
137
+ for (const nodeId of inner.values()) {
138
+ const entry = tryGetFromNestedMap(
139
+ this.detachedNodeToField,
140
+ nodeId.major,
141
+ nodeId.minor,
142
+ );
143
+ assert(
144
+ entry !== undefined,
145
+ 0x9b8 /* Inconsistent data: missing detached node entry */,
146
+ );
147
+ setInNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor, {
148
+ ...entry,
149
+ latestRelevantRevision: updated,
150
+ });
151
+ }
152
+ this.latestRelevantRevisionToFields.delete(current);
153
+ this.latestRelevantRevisionToFields.set(updated, inner);
154
+ }
155
+ }
156
+
157
+ // Update the major keys corresponding to `current`
158
+ {
159
+ const innerCurrent = this.detachedNodeToField.get(current);
160
+ if (innerCurrent !== undefined) {
161
+ this.detachedNodeToField.delete(current);
162
+ const innerUpdated = this.detachedNodeToField.get(updated);
163
+ if (innerUpdated === undefined) {
164
+ this.detachedNodeToField.set(updated, innerCurrent);
165
+ } else {
166
+ for (const [minor, entry] of innerCurrent) {
167
+ assert(
168
+ innerUpdated.get(minor) === undefined,
169
+ 0x7a9 /* Collision during index update */,
170
+ );
171
+ innerUpdated.set(minor, entry);
172
+ }
173
+ }
174
+
103
175
  for (const [minor, entry] of innerCurrent) {
176
+ const entryInLatest = this.latestRelevantRevisionToFields.get(
177
+ entry.latestRelevantRevision,
178
+ );
104
179
  assert(
105
- innerUpdated.get(minor) === undefined,
106
- 0x7a9 /* Collision during index update */,
180
+ entryInLatest !== undefined,
181
+ 0x9b9 /* Inconsistent data: missing node entry in latestRelevantRevision */,
107
182
  );
108
- innerUpdated.set(minor, entry);
183
+ entryInLatest.set(entry.root, { major: updated, minor });
109
184
  }
110
185
  }
111
186
  }
@@ -124,7 +199,7 @@ export class DetachedFieldIndex {
124
199
  * Returns undefined if no such id is known to the index.
125
200
  */
126
201
  public tryGetEntry(id: Delta.DetachedNodeId): ForestRootId | undefined {
127
- return tryGetFromNestedMap(this.detachedNodeToField, id.major, id.minor);
202
+ return tryGetFromNestedMap(this.detachedNodeToField, id.major, id.minor)?.root;
128
203
  }
129
204
 
130
205
  /**
@@ -137,15 +212,60 @@ export class DetachedFieldIndex {
137
212
  return key;
138
213
  }
139
214
 
215
+ /**
216
+ * Returns the detached root IDs for all the trees that were detached or last modified by the given revision.
217
+ */
218
+ public *getRootsLastTouchedByRevision(revision: RevisionTag): Iterable<ForestRootId> {
219
+ const roots = this.latestRelevantRevisionToFields.get(revision);
220
+ if (roots !== undefined) {
221
+ yield* roots.keys();
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Removes the detached roots for all the trees that were detached or last modified by the given revision.
227
+ */
228
+ public deleteRootsLastTouchedByRevision(revision: RevisionTag): void {
229
+ const entries = this.latestRelevantRevisionToFields.get(revision);
230
+ if (entries === undefined) {
231
+ return;
232
+ }
233
+
234
+ this.latestRelevantRevisionToFields.delete(revision);
235
+ for (const detachedNodeId of entries.values()) {
236
+ const found = deleteFromNestedMap(
237
+ this.detachedNodeToField,
238
+ detachedNodeId.major,
239
+ detachedNodeId.minor,
240
+ );
241
+ assert(found, 0x9ba /* Unable to delete unknown entry */);
242
+ }
243
+ }
244
+
140
245
  public deleteEntry(nodeId: Delta.DetachedNodeId): void {
141
- const found = deleteFromNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor);
142
- assert(found, 0x7ab /* Unable to delete unknown entry */);
246
+ const entry = tryGetFromNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor);
247
+ assert(entry !== undefined, 0x9bb /* Unable to delete unknown entry */);
248
+ deleteFromNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor);
249
+ deleteFromNestedMap(
250
+ this.latestRelevantRevisionToFields,
251
+ entry.latestRelevantRevision,
252
+ entry.root,
253
+ );
143
254
  }
144
255
 
145
256
  /**
146
257
  * Associates the DetachedNodeId with a field key and creates an entry for it in the index.
258
+ * @param nodeId - The ID of the detached node.
259
+ * @param revision - The revision that last detached the root.
260
+ * See {@link DetachedField.latestRelevantRevision} for details.
261
+ * @param count - The number of entries to create. These entries will have consecutive minor IDs.
262
+ * @returns The atomic ID that the `DetachedFieldIndex` uses to uniquely identify the first root.
147
263
  */
148
- public createEntry(nodeId?: Delta.DetachedNodeId, count: number = 1): ForestRootId {
264
+ public createEntry(
265
+ nodeId?: Delta.DetachedNodeId,
266
+ revision?: RevisionTag,
267
+ count: number = 1,
268
+ ): ForestRootId {
149
269
  const root = this.rootIdAllocator.allocate(count);
150
270
  if (nodeId !== undefined) {
151
271
  for (let i = 0; i < count; i++) {
@@ -154,17 +274,46 @@ export class DetachedFieldIndex {
154
274
  undefined,
155
275
  0x7ce /* Detached node ID already exists in index */,
156
276
  );
157
- setInNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor + i, root + i);
277
+ setInNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor + i, {
278
+ root: brand<ForestRootId>(root + i),
279
+ latestRelevantRevision: revision,
280
+ });
281
+ setInNestedMap(this.latestRelevantRevisionToFields, revision, root, nodeId);
158
282
  }
159
283
  }
160
284
  return root;
161
285
  }
162
286
 
287
+ /**
288
+ * Updates the latest revision that is relevant to the provided root
289
+ */
290
+ public updateLatestRevision(
291
+ id: Delta.DetachedNodeId,
292
+ revision: RevisionTag | undefined,
293
+ ): void {
294
+ const fieldEntry = tryGetFromNestedMap(this.detachedNodeToField, id.major, id.minor);
295
+ assert(
296
+ fieldEntry !== undefined,
297
+ 0x9bc /* detached node id does not exist in the detached field index */,
298
+ );
299
+ const { root, latestRelevantRevision: previousRevision } = fieldEntry;
300
+
301
+ // remove this root from the set of roots for the previous latest revision
302
+ deleteFromNestedMap(this.latestRelevantRevisionToFields, previousRevision, root);
303
+
304
+ // add this root to the set of roots for the new latest revision
305
+ setInNestedMap(this.latestRelevantRevisionToFields, revision, root, id);
306
+ setInNestedMap(this.detachedNodeToField, id.major, id.minor, {
307
+ root,
308
+ latestRelevantRevision: revision,
309
+ });
310
+ }
311
+
163
312
  public encode(): JsonCompatibleReadOnly {
164
313
  return this.codec.encode({
165
314
  data: this.detachedNodeToField,
166
315
  maxId: this.rootIdAllocator.getMaxId(),
167
- }) as JsonCompatibleReadOnly;
316
+ });
168
317
  }
169
318
 
170
319
  /**
@@ -176,6 +325,39 @@ export class DetachedFieldIndex {
176
325
  this.rootIdAllocator = idAllocatorFromMaxId(
177
326
  detachedFieldIndex.maxId,
178
327
  ) as IdAllocator<ForestRootId>;
179
- this.detachedNodeToField = detachedFieldIndex.data;
328
+
329
+ this.detachedNodeToField = new Map();
330
+ this.latestRelevantRevisionToFields = new Map();
331
+ this.isFullyLoaded = false;
332
+ const rootMap = new Map<ForestRootId, Delta.DetachedNodeId>();
333
+ forEachInNestedMap(detachedFieldIndex.data, ({ root }, major, minor) => {
334
+ rootMap.set(root, { major, minor });
335
+ });
336
+
337
+ this.latestRelevantRevisionToFields.set(undefined, rootMap);
338
+ }
339
+
340
+ /**
341
+ * Sets the latest relevant revision for any roots that have an undefined latest relevant revision.
342
+ * This occurs when the detached field index is loaded from a summary and can only be called once after
343
+ * the summary has been loaded.
344
+ */
345
+ public setRevisionsForLoadedData(latestRevision: RevisionTag): void {
346
+ assert(
347
+ !this.isFullyLoaded,
348
+ 0x9bd /* revisions should only be set once using this function after loading data from a summary */,
349
+ );
350
+
351
+ const newDetachedNodeToField = new Map();
352
+ const rootMap = new Map();
353
+ forEachInNestedMap(this.detachedNodeToField, ({ root }, major, minor) => {
354
+ setInNestedMap(newDetachedNodeToField, major, minor, { root, latestRevision });
355
+ rootMap.set(root, { major, minor });
356
+ });
357
+
358
+ this.detachedNodeToField = newDetachedNodeToField;
359
+ this.latestRelevantRevisionToFields.delete(undefined);
360
+ this.latestRelevantRevisionToFields.set(latestRevision, rootMap);
361
+ this.isFullyLoaded = true;
180
362
  }
181
363
  }
@@ -12,14 +12,17 @@ import {
12
12
  } from "../../codec/index.js";
13
13
  import type { EncodedRevisionTag, RevisionTagCodec, RevisionTag } from "../rebase/index.js";
14
14
 
15
- import type { ForestRootId } from "./detachedFieldIndex.js";
16
15
  import {
17
16
  type EncodedRootsForRevision,
18
17
  Format,
19
18
  type RootRanges,
20
19
  version,
21
20
  } from "./detachedFieldIndexFormat.js";
22
- import type { DetachedFieldSummaryData, Major } from "./detachedFieldIndexTypes.js";
21
+ import type {
22
+ DetachedField,
23
+ DetachedFieldSummaryData,
24
+ Major,
25
+ } from "./detachedFieldIndexTypes.js";
23
26
  import type { IIdCompressor } from "@fluidframework/id-compressor";
24
27
 
25
28
  class MajorCodec implements IJsonCodec<Major> {
@@ -76,7 +79,10 @@ export function makeDetachedNodeToFieldCodec(
76
79
  const rootsForRevisions: EncodedRootsForRevision[] = [];
77
80
  for (const [major, innerMap] of data.data) {
78
81
  const encodedRevision = majorCodec.encode(major);
79
- const rootRanges: RootRanges = [...innerMap];
82
+ const rootRanges: RootRanges = [];
83
+ for (const [minor, detachedField] of innerMap) {
84
+ rootRanges.push([minor, detachedField.root]);
85
+ }
80
86
  if (rootRanges.length === 1) {
81
87
  const rootsForRevision: EncodedRootsForRevision = [
82
88
  encodedRevision,
@@ -99,11 +105,14 @@ export function makeDetachedNodeToFieldCodec(
99
105
  decode: (parsed: Format): DetachedFieldSummaryData => {
100
106
  const map = new Map();
101
107
  for (const rootsForRevision of parsed.data) {
102
- const innerMap = new Map<number, ForestRootId>(
103
- rootsForRevision.length === 2
104
- ? rootsForRevision[1]
105
- : [[rootsForRevision[1], rootsForRevision[2]]],
106
- );
108
+ const innerMap = new Map<number, DetachedField>();
109
+ if (rootsForRevision.length === 2) {
110
+ for (const [minor, root] of rootsForRevision[1]) {
111
+ innerMap.set(minor, { root });
112
+ }
113
+ } else {
114
+ innerMap.set(rootsForRevision[1], { root: rootsForRevision[2] });
115
+ }
107
116
  map.set(majorCodec.decode(rootsForRevision[0]), innerMap);
108
117
  }
109
118
  return {
@@ -8,7 +8,7 @@ import { type Static, Type } from "@sinclair/typebox";
8
8
  import { brandedNumberType } from "../../util/index.js";
9
9
  import { RevisionTagSchema } from "../rebase/index.js";
10
10
 
11
- import type { ForestRootId } from "./detachedFieldIndex.js";
11
+ import type { ForestRootId } from "./detachedFieldIndexTypes.js";
12
12
 
13
13
  export const version = 1.0;
14
14
 
@@ -3,15 +3,51 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import type { NestedMap } from "../../util/index.js";
6
+ import type { Brand, ReadonlyNestedMap } from "../../util/index.js";
7
7
  import type { RevisionTag } from "../rebase/index.js";
8
8
 
9
- import type { ForestRootId } from "./detachedFieldIndex.js";
10
-
11
9
  export type Major = RevisionTag | undefined;
12
10
  export type Minor = number;
13
11
 
14
12
  export interface DetachedFieldSummaryData {
15
- data: NestedMap<Major, Minor, ForestRootId>;
16
- maxId: ForestRootId;
13
+ readonly data: ReadonlyNestedMap<Major, Minor, DetachedField>;
14
+ readonly maxId: ForestRootId;
15
+ }
16
+
17
+ /**
18
+ * ID used to create a detached field key for a removed subtree.
19
+ *
20
+ * TODO: Move to Forest once forests can support multiple roots.
21
+ * @internal
22
+ */
23
+ export type ForestRootId = Brand<number, "tree.ForestRootId">;
24
+
25
+ /**
26
+ * A field that is detached from the main document tree.
27
+ */
28
+ export interface DetachedField {
29
+ /**
30
+ * The atomic ID that the `DetachedFieldIndex` uses to uniquely identify the first (and only) root in the field.
31
+ * This ID is scoped to the specific `DetachedFieldIndex` from witch this object was retrieved.
32
+ *
33
+ * The current implementation only supports a single root per field.
34
+ * This will be changed in the future for performance reasons.
35
+ */
36
+ readonly root: ForestRootId;
37
+ /**
38
+ * The revision that last detached the root node or modified its contents (including its descendant's contents).
39
+ *
40
+ * Once this revision is trimmed from the ancestry on which a `TreeCheckout` is moored,
41
+ * the contents of the associated subtree (and the very fact of its past existence) can be erased.
42
+ *
43
+ * @remarks
44
+ * undefined revisions are tolerated but any roots not associated with a revision must be disposed manually.
45
+ * Current usages of undefined are:
46
+ * - When loading a {@link DetachedFieldIndex} from a snapshot,
47
+ * until {@link DetachedFieldIndex.setRevisionsForLoadedData} is called.
48
+ * - When applying a rollback changeset.
49
+ * This only occurs within the context of {@link DefaultResubmitMachine} whose repair data is GC-ed when its
50
+ * `DetachedField` and `Forest` are GC-ed.
51
+ */
52
+ readonly latestRelevantRevision?: RevisionTag;
17
53
  }
@@ -110,4 +110,5 @@ export {
110
110
  emptyDelta,
111
111
  } from "./deltaUtil.js";
112
112
 
113
- export { DetachedFieldIndex, type ForestRootId } from "./detachedFieldIndex.js";
113
+ export { DetachedFieldIndex } from "./detachedFieldIndex.js";
114
+ export { type ForestRootId } from "./detachedFieldIndexTypes.js";
@@ -21,9 +21,10 @@ import {
21
21
  isReplaceMark,
22
22
  offsetDetachId,
23
23
  } from "./deltaUtil.js";
24
- import type { DetachedFieldIndex, ForestRootId } from "./detachedFieldIndex.js";
25
- import type { Major, Minor } from "./detachedFieldIndexTypes.js";
24
+ import type { DetachedFieldIndex } from "./detachedFieldIndex.js";
25
+ import type { ForestRootId, Major, Minor } from "./detachedFieldIndexTypes.js";
26
26
  import type { NodeIndex, PlaceIndex, Range } from "./pathTree.js";
27
+ import type { RevisionTag } from "../index.js";
27
28
 
28
29
  /**
29
30
  * Implementation notes:
@@ -74,11 +75,13 @@ import type { NodeIndex, PlaceIndex, Range } from "./pathTree.js";
74
75
  * @param delta - The delta to be crawled.
75
76
  * @param visitor - The object to notify of the changes encountered.
76
77
  * @param detachedFieldIndex - Index responsible for keeping track of the existing detached fields.
78
+ * @param latestRevision - The latest revision tag associated with this delta.
77
79
  */
78
80
  export function visitDelta(
79
81
  delta: Delta.Root,
80
82
  visitor: DeltaVisitor,
81
83
  detachedFieldIndex: DetachedFieldIndex,
84
+ latestRevision: RevisionTag | undefined,
82
85
  ): void {
83
86
  const detachPassRoots: Map<ForestRootId, Delta.FieldMap> = new Map();
84
87
  const attachPassRoots: Map<ForestRootId, Delta.FieldMap> = new Map();
@@ -93,6 +96,7 @@ export function visitDelta(
93
96
  });
94
97
  const detachConfig: PassConfig = {
95
98
  func: detachPass,
99
+ latestRevision,
96
100
  refreshers,
97
101
  detachedFieldIndex,
98
102
  detachPassRoots,
@@ -103,9 +107,17 @@ export function visitDelta(
103
107
  processBuilds(delta.build, detachConfig, visitor);
104
108
  visitFieldMarks(delta.fields, visitor, detachConfig);
105
109
  fixedPointVisitOfRoots(visitor, detachPassRoots, detachConfig);
106
- transferRoots(rootTransfers, attachPassRoots, detachedFieldIndex, visitor);
110
+ transferRoots(
111
+ rootTransfers,
112
+ attachPassRoots,
113
+ detachedFieldIndex,
114
+ visitor,
115
+ refreshers,
116
+ latestRevision,
117
+ );
107
118
  const attachConfig: PassConfig = {
108
119
  func: attachPass,
120
+ latestRevision,
109
121
  refreshers,
110
122
  detachedFieldIndex,
111
123
  detachPassRoots,
@@ -154,6 +166,10 @@ function fixedPointVisitOfRoots(
154
166
 
155
167
  /**
156
168
  * Transfers roots from one detached field to another.
169
+ * This occurs in the following circumstances:
170
+ * - A changeset moves then removes a node
171
+ * - A changeset restores then moves a node
172
+ * - A changeset restores then removes a node
157
173
  * TODO#5481: update the DetachedFieldIndex instead of moving the nodes around.
158
174
  *
159
175
  * @param rootTransfers - The transfers to perform.
@@ -166,6 +182,8 @@ function transferRoots(
166
182
  mapToUpdate: Map<ForestRootId, unknown>,
167
183
  detachedFieldIndex: DetachedFieldIndex,
168
184
  visitor: DeltaVisitor,
185
+ refreshers: NestedMap<Major, Minor, ITreeCursorSynchronous>,
186
+ revision?: RevisionTag,
169
187
  ): void {
170
188
  type AtomizedNodeRename = Omit<Delta.DetachedNodeRename, "count">;
171
189
  let nextBatch = rootTransfers.flatMap(({ oldId, newId, count }) => {
@@ -186,7 +204,14 @@ function transferRoots(
186
204
  const delayed: AtomizedNodeRename[] = [];
187
205
  const priorSize = nextBatch.length;
188
206
  for (const { oldId, newId } of nextBatch) {
189
- const oldRootId = detachedFieldIndex.tryGetEntry(oldId);
207
+ let oldRootId = detachedFieldIndex.tryGetEntry(oldId);
208
+ if (oldRootId === undefined) {
209
+ const tree = tryGetFromNestedMap(refreshers, oldId.major, oldId.minor);
210
+ if (tree !== undefined) {
211
+ buildTrees(oldId, [tree], detachedFieldIndex, revision, visitor);
212
+ oldRootId = detachedFieldIndex.getEntry(oldId);
213
+ }
214
+ }
190
215
  if (oldRootId === undefined) {
191
216
  // The source field is not populated.
192
217
  // This can happen when another rename needs to be performed first.
@@ -200,7 +225,7 @@ function transferRoots(
200
225
  delayed.push({ oldId, newId });
201
226
  continue;
202
227
  }
203
- newRootId = detachedFieldIndex.createEntry(newId);
228
+ newRootId = detachedFieldIndex.createEntry(newId, revision);
204
229
  const fields = mapToUpdate.get(oldRootId);
205
230
  if (fields !== undefined) {
206
231
  mapToUpdate.delete(oldRootId);
@@ -311,6 +336,13 @@ export interface DeltaVisitor {
311
336
 
312
337
  interface PassConfig {
313
338
  readonly func: Pass;
339
+
340
+ /**
341
+ * The latest revision tag associated with the given delta. This is used to keep track
342
+ * of when repair data should be garbage collected.
343
+ */
344
+ readonly latestRevision: RevisionTag | undefined;
345
+
314
346
  readonly detachedFieldIndex: DetachedFieldIndex;
315
347
  /**
316
348
  * A mapping between forest root id and trees that represent refresher data. Each entry is only
@@ -394,9 +426,11 @@ function detachPass(
394
426
  if (root === undefined) {
395
427
  const tree = tryGetFromNestedMap(config.refreshers, id.major, id.minor);
396
428
  assert(tree !== undefined, 0x928 /* refresher data not found */);
397
- buildTrees(id, [tree], config, visitor);
429
+ buildTrees(id, [tree], config.detachedFieldIndex, config.latestRevision, visitor);
398
430
  root = config.detachedFieldIndex.getEntry(id);
399
431
  }
432
+ // the revision is updated for any refresher data included in the delta that is used
433
+ config.detachedFieldIndex.updateLatestRevision(id, config.latestRevision);
400
434
  config.detachPassRoots.set(root, fields);
401
435
  config.attachPassRoots.set(root, fields);
402
436
  }
@@ -416,10 +450,9 @@ function detachPass(
416
450
  }
417
451
  if (isDetachMark(mark)) {
418
452
  for (let i = 0; i < mark.count; i += 1) {
419
- const root = config.detachedFieldIndex.createEntry(
420
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
421
- offsetDetachId(mark.detach!, i),
422
- );
453
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
454
+ const id = offsetDetachId(mark.detach!, i);
455
+ const root = config.detachedFieldIndex.createEntry(id, config.latestRevision);
423
456
  if (mark.fields !== undefined) {
424
457
  config.attachPassRoots.set(root, mark.fields);
425
458
  }
@@ -436,15 +469,16 @@ function detachPass(
436
469
  function buildTrees(
437
470
  id: Delta.DetachedNodeId,
438
471
  trees: readonly ITreeCursorSynchronous[],
439
- config: PassConfig,
472
+ detachedFieldIndex: DetachedFieldIndex,
473
+ latestRevision: RevisionTag | undefined,
440
474
  visitor: DeltaVisitor,
441
475
  ): void {
442
476
  for (let i = 0; i < trees.length; i += 1) {
443
477
  const offsettedId = offsetDetachId(id, i);
444
- let root = config.detachedFieldIndex.tryGetEntry(offsettedId);
478
+ let root = detachedFieldIndex.tryGetEntry(offsettedId);
445
479
  assert(root === undefined, 0x929 /* Unable to build tree that already exists */);
446
- root = config.detachedFieldIndex.createEntry(offsettedId);
447
- const field = config.detachedFieldIndex.toFieldKey(root);
480
+ root = detachedFieldIndex.createEntry(offsettedId, latestRevision);
481
+ const field = detachedFieldIndex.toFieldKey(root);
448
482
  visitor.create([trees[i]], field);
449
483
  }
450
484
  }
@@ -456,7 +490,7 @@ function processBuilds(
456
490
  ): void {
457
491
  if (builds !== undefined) {
458
492
  for (const { id, trees } of builds) {
459
- buildTrees(id, trees, config, visitor);
493
+ buildTrees(id, trees, config.detachedFieldIndex, config.latestRevision, visitor);
460
494
  }
461
495
  }
462
496
  }
@@ -496,7 +530,13 @@ function attachPass(
496
530
  offsetAttachId.minor,
497
531
  );
498
532
  assert(tree !== undefined, 0x92a /* refresher data not found */);
499
- buildTrees(offsetAttachId, [tree], config, visitor);
533
+ buildTrees(
534
+ offsetAttachId,
535
+ [tree],
536
+ config.detachedFieldIndex,
537
+ config.latestRevision,
538
+ visitor,
539
+ );
500
540
  sourceRoot = config.detachedFieldIndex.getEntry(offsetAttachId);
501
541
  }
502
542
  const sourceField = config.detachedFieldIndex.toFieldKey(sourceRoot);
@@ -505,6 +545,7 @@ function attachPass(
505
545
  const rootDestination = config.detachedFieldIndex.createEntry(
506
546
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
507
547
  offsetDetachId(mark.detach!, i),
548
+ config.latestRevision,
508
549
  );
509
550
  const destinationField = config.detachedFieldIndex.toFieldKey(rootDestination);
510
551
  visitor.replace(