@fluidframework/tree 2.33.0-333010 → 2.33.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 (292) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/api-report/tree.alpha.api.md +169 -192
  3. package/api-report/tree.beta.api.md +135 -164
  4. package/api-report/tree.legacy.alpha.api.md +138 -167
  5. package/api-report/tree.legacy.public.api.md +135 -164
  6. package/api-report/tree.public.api.md +135 -164
  7. package/dist/alpha.d.ts +3 -11
  8. package/dist/beta.d.ts +2 -11
  9. package/dist/core/index.d.ts +1 -1
  10. package/dist/core/index.d.ts.map +1 -1
  11. package/dist/core/index.js +1 -2
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/core/schema-stored/index.d.ts +1 -1
  14. package/dist/core/schema-stored/index.d.ts.map +1 -1
  15. package/dist/core/schema-stored/index.js +1 -2
  16. package/dist/core/schema-stored/index.js.map +1 -1
  17. package/dist/core/schema-stored/schema.d.ts +4 -11
  18. package/dist/core/schema-stored/schema.d.ts.map +1 -1
  19. package/dist/core/schema-stored/schema.js +7 -14
  20. package/dist/core/schema-stored/schema.js.map +1 -1
  21. package/dist/core/tree/anchorSet.d.ts.map +1 -1
  22. package/dist/core/tree/anchorSet.js +31 -24
  23. package/dist/core/tree/anchorSet.js.map +1 -1
  24. package/dist/core/tree/deltaUtil.d.ts +1 -4
  25. package/dist/core/tree/deltaUtil.d.ts.map +1 -1
  26. package/dist/core/tree/deltaUtil.js +1 -13
  27. package/dist/core/tree/deltaUtil.js.map +1 -1
  28. package/dist/core/tree/visitDelta.d.ts +6 -29
  29. package/dist/core/tree/visitDelta.d.ts.map +1 -1
  30. package/dist/core/tree/visitDelta.js +11 -50
  31. package/dist/core/tree/visitDelta.js.map +1 -1
  32. package/dist/core/tree/visitorUtils.d.ts +12 -9
  33. package/dist/core/tree/visitorUtils.d.ts.map +1 -1
  34. package/dist/core/tree/visitorUtils.js +19 -32
  35. package/dist/core/tree/visitorUtils.js.map +1 -1
  36. package/dist/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
  37. package/dist/feature-libraries/chunked-forest/chunkedForest.js +6 -11
  38. package/dist/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  39. package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +1 -1
  40. package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
  41. package/dist/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
  42. package/dist/feature-libraries/chunked-forest/codec/nodeShape.d.ts +41 -5
  43. package/dist/feature-libraries/chunked-forest/codec/nodeShape.d.ts.map +1 -1
  44. package/dist/feature-libraries/chunked-forest/codec/nodeShape.js +43 -26
  45. package/dist/feature-libraries/chunked-forest/codec/nodeShape.js.map +1 -1
  46. package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.js +1 -1
  47. package/dist/feature-libraries/chunked-forest/codec/schemaBasedEncode.js.map +1 -1
  48. package/dist/feature-libraries/indexing/anchorTreeIndex.d.ts.map +1 -1
  49. package/dist/feature-libraries/indexing/anchorTreeIndex.js +15 -22
  50. package/dist/feature-libraries/indexing/anchorTreeIndex.js.map +1 -1
  51. package/dist/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
  52. package/dist/feature-libraries/object-forest/objectForest.js +1 -7
  53. package/dist/feature-libraries/object-forest/objectForest.js.map +1 -1
  54. package/dist/feature-libraries/schema-index/codec.d.ts.map +1 -1
  55. package/dist/feature-libraries/schema-index/codec.js +1 -1
  56. package/dist/feature-libraries/schema-index/codec.js.map +1 -1
  57. package/dist/index.d.ts +1 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +3 -2
  60. package/dist/index.js.map +1 -1
  61. package/dist/internalTypes.d.ts +1 -1
  62. package/dist/internalTypes.d.ts.map +1 -1
  63. package/dist/internalTypes.js.map +1 -1
  64. package/dist/jsonDomainSchema.d.ts +6 -6
  65. package/dist/jsonDomainSchema.d.ts.map +1 -1
  66. package/dist/jsonDomainSchema.js.map +1 -1
  67. package/dist/legacy.d.ts +2 -11
  68. package/dist/packageVersion.d.ts +1 -1
  69. package/dist/packageVersion.d.ts.map +1 -1
  70. package/dist/packageVersion.js +1 -1
  71. package/dist/packageVersion.js.map +1 -1
  72. package/dist/public.d.ts +2 -11
  73. package/dist/serializableDomainSchema.d.ts +5 -5
  74. package/dist/shared-tree/checkoutFlexTreeView.js +2 -2
  75. package/dist/shared-tree/checkoutFlexTreeView.js.map +1 -1
  76. package/dist/shared-tree/index.d.ts +3 -2
  77. package/dist/shared-tree/index.d.ts.map +1 -1
  78. package/dist/shared-tree/index.js +2 -2
  79. package/dist/shared-tree/index.js.map +1 -1
  80. package/dist/shared-tree/sharedTree.d.ts +4 -35
  81. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  82. package/dist/shared-tree/sharedTree.js +4 -54
  83. package/dist/shared-tree/sharedTree.js.map +1 -1
  84. package/dist/simple-tree/api/index.d.ts +2 -1
  85. package/dist/simple-tree/api/index.d.ts.map +1 -1
  86. package/dist/simple-tree/api/index.js +3 -1
  87. package/dist/simple-tree/api/index.js.map +1 -1
  88. package/dist/simple-tree/api/schemaFactory.d.ts +28 -28
  89. package/dist/simple-tree/api/schemaFactory.d.ts.map +1 -1
  90. package/dist/simple-tree/api/schemaFactory.js +11 -11
  91. package/dist/simple-tree/api/schemaFactory.js.map +1 -1
  92. package/dist/simple-tree/api/schemaFactoryAlpha.d.ts +6 -7
  93. package/dist/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
  94. package/dist/simple-tree/api/schemaFactoryAlpha.js.map +1 -1
  95. package/dist/simple-tree/api/schemaFactoryRecursive.d.ts +24 -5
  96. package/dist/simple-tree/api/schemaFactoryRecursive.d.ts.map +1 -1
  97. package/dist/simple-tree/api/schemaFactoryRecursive.js +14 -1
  98. package/dist/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
  99. package/dist/simple-tree/api/testRecursiveDomain.d.ts +10 -10
  100. package/dist/simple-tree/api/tree.d.ts +1 -14
  101. package/dist/simple-tree/api/tree.d.ts.map +1 -1
  102. package/dist/simple-tree/api/tree.js.map +1 -1
  103. package/dist/simple-tree/api/typesUnsafe.d.ts +237 -220
  104. package/dist/simple-tree/api/typesUnsafe.d.ts.map +1 -1
  105. package/dist/simple-tree/api/typesUnsafe.js.map +1 -1
  106. package/dist/simple-tree/arrayNode.d.ts +2 -2
  107. package/dist/simple-tree/arrayNode.d.ts.map +1 -1
  108. package/dist/simple-tree/arrayNode.js.map +1 -1
  109. package/dist/simple-tree/core/types.d.ts +14 -4
  110. package/dist/simple-tree/core/types.d.ts.map +1 -1
  111. package/dist/simple-tree/core/types.js +14 -4
  112. package/dist/simple-tree/core/types.js.map +1 -1
  113. package/dist/simple-tree/index.d.ts +1 -1
  114. package/dist/simple-tree/index.d.ts.map +1 -1
  115. package/dist/simple-tree/index.js +3 -2
  116. package/dist/simple-tree/index.js.map +1 -1
  117. package/dist/simple-tree/objectNode.d.ts +9 -2
  118. package/dist/simple-tree/objectNode.d.ts.map +1 -1
  119. package/dist/simple-tree/objectNode.js.map +1 -1
  120. package/dist/simple-tree/schemaTypes.d.ts +10 -5
  121. package/dist/simple-tree/schemaTypes.d.ts.map +1 -1
  122. package/dist/simple-tree/schemaTypes.js +5 -2
  123. package/dist/simple-tree/schemaTypes.js.map +1 -1
  124. package/dist/tableSchema.d.ts +58 -20
  125. package/dist/tableSchema.d.ts.map +1 -1
  126. package/dist/tableSchema.js +40 -15
  127. package/dist/tableSchema.js.map +1 -1
  128. package/dist/treeFactory.d.ts +6 -12
  129. package/dist/treeFactory.d.ts.map +1 -1
  130. package/dist/treeFactory.js +56 -5
  131. package/dist/treeFactory.js.map +1 -1
  132. package/lib/alpha.d.ts +3 -11
  133. package/lib/beta.d.ts +2 -11
  134. package/lib/core/index.d.ts +1 -1
  135. package/lib/core/index.d.ts.map +1 -1
  136. package/lib/core/index.js +1 -1
  137. package/lib/core/index.js.map +1 -1
  138. package/lib/core/schema-stored/index.d.ts +1 -1
  139. package/lib/core/schema-stored/index.d.ts.map +1 -1
  140. package/lib/core/schema-stored/index.js +1 -1
  141. package/lib/core/schema-stored/index.js.map +1 -1
  142. package/lib/core/schema-stored/schema.d.ts +4 -11
  143. package/lib/core/schema-stored/schema.d.ts.map +1 -1
  144. package/lib/core/schema-stored/schema.js +6 -12
  145. package/lib/core/schema-stored/schema.js.map +1 -1
  146. package/lib/core/tree/anchorSet.d.ts.map +1 -1
  147. package/lib/core/tree/anchorSet.js +31 -24
  148. package/lib/core/tree/anchorSet.js.map +1 -1
  149. package/lib/core/tree/deltaUtil.d.ts +1 -4
  150. package/lib/core/tree/deltaUtil.d.ts.map +1 -1
  151. package/lib/core/tree/deltaUtil.js +0 -9
  152. package/lib/core/tree/deltaUtil.js.map +1 -1
  153. package/lib/core/tree/visitDelta.d.ts +6 -29
  154. package/lib/core/tree/visitDelta.d.ts.map +1 -1
  155. package/lib/core/tree/visitDelta.js +12 -51
  156. package/lib/core/tree/visitDelta.js.map +1 -1
  157. package/lib/core/tree/visitorUtils.d.ts +12 -9
  158. package/lib/core/tree/visitorUtils.d.ts.map +1 -1
  159. package/lib/core/tree/visitorUtils.js +19 -32
  160. package/lib/core/tree/visitorUtils.js.map +1 -1
  161. package/lib/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
  162. package/lib/feature-libraries/chunked-forest/chunkedForest.js +6 -11
  163. package/lib/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  164. package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +1 -1
  165. package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
  166. package/lib/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
  167. package/lib/feature-libraries/chunked-forest/codec/nodeShape.d.ts +41 -5
  168. package/lib/feature-libraries/chunked-forest/codec/nodeShape.d.ts.map +1 -1
  169. package/lib/feature-libraries/chunked-forest/codec/nodeShape.js +43 -26
  170. package/lib/feature-libraries/chunked-forest/codec/nodeShape.js.map +1 -1
  171. package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.js +1 -1
  172. package/lib/feature-libraries/chunked-forest/codec/schemaBasedEncode.js.map +1 -1
  173. package/lib/feature-libraries/indexing/anchorTreeIndex.d.ts.map +1 -1
  174. package/lib/feature-libraries/indexing/anchorTreeIndex.js +15 -22
  175. package/lib/feature-libraries/indexing/anchorTreeIndex.js.map +1 -1
  176. package/lib/feature-libraries/object-forest/objectForest.d.ts.map +1 -1
  177. package/lib/feature-libraries/object-forest/objectForest.js +1 -7
  178. package/lib/feature-libraries/object-forest/objectForest.js.map +1 -1
  179. package/lib/feature-libraries/schema-index/codec.d.ts.map +1 -1
  180. package/lib/feature-libraries/schema-index/codec.js +2 -2
  181. package/lib/feature-libraries/schema-index/codec.js.map +1 -1
  182. package/lib/index.d.ts +1 -1
  183. package/lib/index.d.ts.map +1 -1
  184. package/lib/index.js +1 -1
  185. package/lib/index.js.map +1 -1
  186. package/lib/internalTypes.d.ts +1 -1
  187. package/lib/internalTypes.d.ts.map +1 -1
  188. package/lib/internalTypes.js.map +1 -1
  189. package/lib/jsonDomainSchema.d.ts +6 -6
  190. package/lib/jsonDomainSchema.d.ts.map +1 -1
  191. package/lib/jsonDomainSchema.js.map +1 -1
  192. package/lib/legacy.d.ts +2 -11
  193. package/lib/packageVersion.d.ts +1 -1
  194. package/lib/packageVersion.d.ts.map +1 -1
  195. package/lib/packageVersion.js +1 -1
  196. package/lib/packageVersion.js.map +1 -1
  197. package/lib/public.d.ts +2 -11
  198. package/lib/serializableDomainSchema.d.ts +5 -5
  199. package/lib/shared-tree/checkoutFlexTreeView.js +2 -2
  200. package/lib/shared-tree/checkoutFlexTreeView.js.map +1 -1
  201. package/lib/shared-tree/index.d.ts +3 -2
  202. package/lib/shared-tree/index.d.ts.map +1 -1
  203. package/lib/shared-tree/index.js +1 -1
  204. package/lib/shared-tree/index.js.map +1 -1
  205. package/lib/shared-tree/sharedTree.d.ts +4 -35
  206. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  207. package/lib/shared-tree/sharedTree.js +2 -51
  208. package/lib/shared-tree/sharedTree.js.map +1 -1
  209. package/lib/simple-tree/api/index.d.ts +2 -1
  210. package/lib/simple-tree/api/index.d.ts.map +1 -1
  211. package/lib/simple-tree/api/index.js +1 -0
  212. package/lib/simple-tree/api/index.js.map +1 -1
  213. package/lib/simple-tree/api/schemaFactory.d.ts +28 -28
  214. package/lib/simple-tree/api/schemaFactory.d.ts.map +1 -1
  215. package/lib/simple-tree/api/schemaFactory.js +11 -11
  216. package/lib/simple-tree/api/schemaFactory.js.map +1 -1
  217. package/lib/simple-tree/api/schemaFactoryAlpha.d.ts +6 -7
  218. package/lib/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
  219. package/lib/simple-tree/api/schemaFactoryAlpha.js.map +1 -1
  220. package/lib/simple-tree/api/schemaFactoryRecursive.d.ts +24 -5
  221. package/lib/simple-tree/api/schemaFactoryRecursive.d.ts.map +1 -1
  222. package/lib/simple-tree/api/schemaFactoryRecursive.js +12 -0
  223. package/lib/simple-tree/api/schemaFactoryRecursive.js.map +1 -1
  224. package/lib/simple-tree/api/testRecursiveDomain.d.ts +10 -10
  225. package/lib/simple-tree/api/tree.d.ts +1 -14
  226. package/lib/simple-tree/api/tree.d.ts.map +1 -1
  227. package/lib/simple-tree/api/tree.js.map +1 -1
  228. package/lib/simple-tree/api/typesUnsafe.d.ts +237 -220
  229. package/lib/simple-tree/api/typesUnsafe.d.ts.map +1 -1
  230. package/lib/simple-tree/api/typesUnsafe.js.map +1 -1
  231. package/lib/simple-tree/arrayNode.d.ts +2 -2
  232. package/lib/simple-tree/arrayNode.d.ts.map +1 -1
  233. package/lib/simple-tree/arrayNode.js.map +1 -1
  234. package/lib/simple-tree/core/types.d.ts +14 -4
  235. package/lib/simple-tree/core/types.d.ts.map +1 -1
  236. package/lib/simple-tree/core/types.js +14 -4
  237. package/lib/simple-tree/core/types.js.map +1 -1
  238. package/lib/simple-tree/index.d.ts +1 -1
  239. package/lib/simple-tree/index.d.ts.map +1 -1
  240. package/lib/simple-tree/index.js +1 -1
  241. package/lib/simple-tree/index.js.map +1 -1
  242. package/lib/simple-tree/objectNode.d.ts +9 -2
  243. package/lib/simple-tree/objectNode.d.ts.map +1 -1
  244. package/lib/simple-tree/objectNode.js.map +1 -1
  245. package/lib/simple-tree/schemaTypes.d.ts +10 -5
  246. package/lib/simple-tree/schemaTypes.d.ts.map +1 -1
  247. package/lib/simple-tree/schemaTypes.js +5 -2
  248. package/lib/simple-tree/schemaTypes.js.map +1 -1
  249. package/lib/tableSchema.d.ts +58 -20
  250. package/lib/tableSchema.d.ts.map +1 -1
  251. package/lib/tableSchema.js +40 -15
  252. package/lib/tableSchema.js.map +1 -1
  253. package/lib/treeFactory.d.ts +6 -12
  254. package/lib/treeFactory.d.ts.map +1 -1
  255. package/lib/treeFactory.js +55 -3
  256. package/lib/treeFactory.js.map +1 -1
  257. package/lib/tsdoc-metadata.json +1 -1
  258. package/package.json +22 -23
  259. package/src/core/index.ts +0 -2
  260. package/src/core/schema-stored/index.ts +0 -2
  261. package/src/core/schema-stored/schema.ts +10 -29
  262. package/src/core/tree/anchorSet.ts +32 -24
  263. package/src/core/tree/deltaUtil.ts +1 -13
  264. package/src/core/tree/visitDelta.ts +24 -81
  265. package/src/core/tree/visitorUtils.ts +43 -53
  266. package/src/feature-libraries/chunked-forest/chunkedForest.ts +7 -22
  267. package/src/feature-libraries/chunked-forest/codec/compressedEncode.ts +1 -1
  268. package/src/feature-libraries/chunked-forest/codec/nodeShape.ts +45 -27
  269. package/src/feature-libraries/chunked-forest/codec/schemaBasedEncode.ts +1 -1
  270. package/src/feature-libraries/indexing/anchorTreeIndex.ts +17 -28
  271. package/src/feature-libraries/object-forest/objectForest.ts +1 -18
  272. package/src/feature-libraries/schema-index/codec.ts +1 -2
  273. package/src/index.ts +3 -11
  274. package/src/internalTypes.ts +0 -19
  275. package/src/jsonDomainSchema.ts +1 -3
  276. package/src/packageVersion.ts +1 -1
  277. package/src/shared-tree/checkoutFlexTreeView.ts +2 -2
  278. package/src/shared-tree/index.ts +8 -3
  279. package/src/shared-tree/sharedTree.ts +5 -133
  280. package/src/simple-tree/api/index.ts +2 -22
  281. package/src/simple-tree/api/schemaFactory.ts +40 -54
  282. package/src/simple-tree/api/schemaFactoryAlpha.ts +9 -14
  283. package/src/simple-tree/api/schemaFactoryRecursive.ts +25 -5
  284. package/src/simple-tree/api/tree.ts +1 -14
  285. package/src/simple-tree/api/typesUnsafe.ts +359 -341
  286. package/src/simple-tree/arrayNode.ts +2 -2
  287. package/src/simple-tree/core/types.ts +14 -4
  288. package/src/simple-tree/index.ts +2 -22
  289. package/src/simple-tree/objectNode.ts +9 -2
  290. package/src/simple-tree/schemaTypes.ts +11 -7
  291. package/src/tableSchema.ts +122 -54
  292. package/src/treeFactory.ts +151 -8
@@ -10,13 +10,7 @@ import type { FieldKey } from "../schema-stored/index.js";
10
10
 
11
11
  import { mapCursorField, type ITreeCursorSynchronous } from "./cursor.js";
12
12
  import type * as Delta from "./delta.js";
13
- import {
14
- areDetachedNodeIdsEqual,
15
- isAttachMark,
16
- isDetachMark,
17
- isReplaceMark,
18
- offsetDetachId,
19
- } from "./deltaUtil.js";
13
+ import { areDetachedNodeIdsEqual, offsetDetachId } from "./deltaUtil.js";
20
14
  import type { DetachedFieldIndex } from "./detachedFieldIndex.js";
21
15
  import type { ForestRootId, Major, Minor } from "./detachedFieldIndexTypes.js";
22
16
  import type { NodeIndex, PlaceIndex, Range } from "./pathTree.js";
@@ -32,24 +26,11 @@ import type { RevisionTag, TreeChunk } from "../index.js";
32
26
  * 4. root destructions
33
27
  *
34
28
  * The core idea is that before content can be attached, it must first exist and be in a detached field.
35
- * The detach pass is therefore responsible for making sure that all roots that needs to be attached during the
36
- * attach pass are detached.
37
- * In practice, this means the detach pass must:
38
- * - Create all subtrees that need to be created
39
- * - Detach all moved nodes
40
- *
41
- * In addition to that, the detach pass also detaches nodes that need removing, with the exception of nodes that get
42
- * replaced. The reason for this exception is that we need to be able to communicate replaces as atomic operations.
43
- * In order to do that, we need to wait until we are sure that the content to attach is available as a detached root.
44
- * Replaces are therefore handled during the attach pass.
45
- * Note that this could theoretically lead to a situation where, in the attach pass, one replace wants to attach
46
- * a node that has yet to be detached by another replace. This does not occur in practice because we do not support
47
- * editing operations that would lead to this situation.
48
29
  *
49
30
  * While the detach pass ensures that nodes to be attached are in a detached state, it does not guarantee that they
50
- * reside in the correct detach field. That is the responsibility of the root transfers phase.
31
+ * reside in the correct detached field. That is the responsibility of the root transfers phase.
51
32
  *
52
- * The attach phase carries out attaches and replaces.
33
+ * The attach phase carries out attaches.
53
34
  *
54
35
  * After the attach phase, roots destruction is carried out.
55
36
  * This needs to happen last to allow modifications to detached roots to be applied before they are destroyed.
@@ -57,9 +38,6 @@ import type { RevisionTag, TreeChunk } from "../index.js";
57
38
  * The details of the delta visit algorithm can impact how/when events are emitted by the objects that own the visitors.
58
39
  * For example, as of 2024-03-27, the subtreeChanged event of an AnchorNode is emitted when exiting a node during a
59
40
  * delta visit, and thus the two-pass nature of the algorithm means the event fires twice for any given change.
60
- * This two-pass nature also means that the event may fire at a time where no change is visible in the tree. E.g.,
61
- * if a node is being replaced, when the event fires during the detach pass no change in the tree has happened so the
62
- * listener won't see any; then when it fires during the attach pass, the change will be visible in the event listener.
63
41
  */
64
42
 
65
43
  /**
@@ -233,7 +211,7 @@ function transferRoots(
233
211
  const oldField = detachedFieldIndex.toFieldKey(oldRootId);
234
212
  const newField = detachedFieldIndex.toFieldKey(newRootId);
235
213
  visitor.enterField(oldField);
236
- visitor.detach({ start: 0, end: 1 }, newField, newId);
214
+ visitor.detach({ start: 0, end: 1 }, newField, newId, false);
237
215
  visitor.exitField(oldField);
238
216
  detachedFieldIndex.deleteEntry(oldId);
239
217
  }
@@ -285,22 +263,15 @@ export interface DeltaVisitor {
285
263
  * @param destination - The key for a new detached field.
286
264
  * A field with this key must not already exist.
287
265
  * @param id - The ID assigned to the first detached node as a result of the detach. The other nodes in the detached range are assigned subsequent IDs.
266
+ * @param isReplaced - Whether the detached content will be replaced by a later attach.
267
+ * This is not guaranteed to be true in all cases where it could be true,
268
+ * but it is guaranteed to be true in all cases where a later attach is needed to keep the data compliant with the schema.
288
269
  */
289
- detach(source: Range, destination: FieldKey, id: Delta.DetachedNodeId): void;
290
- /**
291
- * Replaces a range of nodes in the current field by transferring them out to a new detached field
292
- * and transferring in all the nodes from an existing detached field in their place.
293
- * The number of nodes being detached must match the number of nodes being attached.
294
- * @param newContentSource - The detached field to transfer the new nodes from.
295
- * @param range - The bounds of the range of nodes to replace.
296
- * @param oldContentDestination - The key for a new detached field to transfer the old nodes to.
297
- * @param oldContentId - The ID assigned to the first replaced node as a result of the replace. The other nodes in the replaced range are assigned subsequent IDs.
298
- */
299
- replace(
300
- newContentSource: FieldKey,
301
- range: Range,
302
- oldContentDestination: FieldKey,
303
- oldContentId: Delta.DetachedNodeId,
270
+ detach(
271
+ source: Range,
272
+ destination: FieldKey,
273
+ id: Delta.DetachedNodeId,
274
+ isReplaced: boolean,
304
275
  ): void;
305
276
 
306
277
  /**
@@ -417,13 +388,8 @@ function visitNode(
417
388
 
418
389
  /**
419
390
  * Performs the following:
420
- * - Performs all root creations
421
- * - Collects all roots that may need a detach pass
422
391
  * - Collects all roots that may need an attach pass
423
- * - Collects all relocates
424
- * - Collects all destructions
425
- * - Executes detaches (bottom-up) provided they are not part of a replace
426
- * (because we want to wait until we are sure content to attach is available as a root)
392
+ * - Executes detaches (bottom-up)
427
393
  */
428
394
  function detachPass(
429
395
  fieldChanges: Delta.FieldChanges,
@@ -439,18 +405,18 @@ function detachPass(
439
405
  );
440
406
  visitNode(index, mark.fields, visitor, config);
441
407
  }
442
- if (isDetachMark(mark)) {
408
+ if (mark.detach !== undefined) {
443
409
  for (let i = 0; i < mark.count; i += 1) {
444
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
445
- const id = offsetDetachId(mark.detach!, i);
410
+ const id = offsetDetachId(mark.detach, i);
446
411
  const root = config.detachedFieldIndex.createEntry(id, config.latestRevision);
447
412
  if (mark.fields !== undefined) {
448
413
  config.attachPassRoots.set(root, mark.fields);
449
414
  }
450
415
  const field = config.detachedFieldIndex.toFieldKey(root);
451
- visitor.detach({ start: index, end: index + 1 }, field, id);
416
+ visitor.detach({ start: index, end: index + 1 }, field, id, mark.attach !== undefined);
452
417
  }
453
- } else if (!isAttachMark(mark)) {
418
+ }
419
+ if (mark.detach === undefined && mark.attach === undefined) {
454
420
  index += mark.count;
455
421
  }
456
422
  }
@@ -534,8 +500,6 @@ function collectDestroys(
534
500
  /**
535
501
  * Preforms the following:
536
502
  * - Executes attaches (top-down) applying nested changes on the attached nodes
537
- * - Executes replaces (top-down) applying nested changes on the attached nodes
538
- * - Collects detached roots (from replaces) that need an attach pass
539
503
  */
540
504
  function attachPass(
541
505
  fieldChanges: Delta.FieldChanges,
@@ -544,10 +508,9 @@ function attachPass(
544
508
  ): void {
545
509
  let index = 0;
546
510
  for (const mark of fieldChanges) {
547
- if (isAttachMark(mark) || isReplaceMark(mark)) {
511
+ if (mark.attach !== undefined) {
548
512
  for (let i = 0; i < mark.count; i += 1) {
549
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
550
- const offsetAttachId = offsetDetachId(mark.attach!, i);
513
+ const offsetAttachId = offsetDetachId(mark.attach, i);
551
514
  let sourceRoot = config.detachedFieldIndex.tryGetEntry(offsetAttachId);
552
515
  if (sourceRoot === undefined) {
553
516
  const tree = tryGetFromNestedMap(
@@ -567,28 +530,7 @@ function attachPass(
567
530
  }
568
531
  const sourceField = config.detachedFieldIndex.toFieldKey(sourceRoot);
569
532
  const offsetIndex = index + i;
570
- if (isReplaceMark(mark)) {
571
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
572
- const destinationId = offsetDetachId(mark.detach!, i);
573
- const rootDestination = config.detachedFieldIndex.createEntry(
574
- destinationId,
575
- config.latestRevision,
576
- );
577
- const destinationField = config.detachedFieldIndex.toFieldKey(rootDestination);
578
- visitor.replace(
579
- sourceField,
580
- { start: offsetIndex, end: offsetIndex + 1 },
581
- destinationField,
582
- destinationId,
583
- );
584
- // We may need to do a second pass on the detached nodes
585
- if (mark.fields !== undefined) {
586
- config.attachPassRoots.set(rootDestination, mark.fields);
587
- }
588
- } else {
589
- // This a simple attach
590
- visitor.attach(sourceField, 1, offsetIndex);
591
- }
533
+ visitor.attach(sourceField, 1, offsetIndex);
592
534
  config.detachedFieldIndex.deleteEntry(offsetAttachId);
593
535
  const fields = config.attachPassRoots.get(sourceRoot);
594
536
  if (fields !== undefined) {
@@ -596,10 +538,11 @@ function attachPass(
596
538
  visitNode(offsetIndex, fields, visitor, config);
597
539
  }
598
540
  }
599
- } else if (!isDetachMark(mark) && mark.fields !== undefined) {
541
+ }
542
+ if (mark.detach === undefined && mark.fields !== undefined) {
600
543
  visitNode(index, mark.fields, visitor, config);
601
544
  }
602
- if (!isDetachMark(mark)) {
545
+ if (mark.detach === undefined || mark.attach !== undefined) {
603
546
  index += mark.count;
604
547
  }
605
548
  }
@@ -3,8 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert } from "@fluidframework/core-utils/internal";
7
-
8
6
  import type { ICodecOptions } from "../../codec/index.js";
9
7
  import { type IdAllocator, idAllocatorFromMaxId } from "../../util/index.js";
10
8
  import type { RevisionTag, RevisionTagCodec } from "../rebase/index.js";
@@ -51,41 +49,46 @@ export function announceDelta(
51
49
  detachedFieldIndex: DetachedFieldIndex,
52
50
  ): void {
53
51
  const visitor = deltaProcessor.acquireVisitor();
54
- visitDelta(delta, combineVisitors([visitor], [visitor]), detachedFieldIndex, latestRevision);
52
+ visitDelta(delta, combineVisitors([visitor]), detachedFieldIndex, latestRevision);
55
53
  visitor.free();
56
54
  }
57
55
 
56
+ export interface CombinedVisitor extends DeltaVisitor {
57
+ readonly type: "Combined";
58
+
59
+ readonly visitors: readonly CombinableVisitor[];
60
+ }
61
+
62
+ export type CombinableVisitor =
63
+ | (DeltaVisitor & { type?: never })
64
+ | AnnouncedVisitor
65
+ | CombinedVisitor;
66
+
58
67
  /**
68
+ * Combines multiple visitors into a single visitor.
59
69
  * @param visitors - The returned visitor invokes the corresponding events for all these visitors, in order.
60
- * @param announcedVisitors - Subset of `visitors` to also call {@link AnnouncedVisitor} methods on.
61
- * This must be a subset of `visitors`: if not the visitor will not have its path correctly set when the events are triggered.
62
- * When `visitors` are making changes to data, `announcedVisitors` can be used to get extra events before or after all the changes from all the visitors have been made.
63
- * This can, for example, enable visitors to have access to the tree in these extra events despite multiple separate visitors updating different tree related data-structures.
64
70
  * @returns a DeltaVisitor combining all `visitors`.
65
71
  */
66
- export function combineVisitors(
67
- visitors: readonly DeltaVisitor[],
68
- announcedVisitors: readonly AnnouncedVisitor[] = [],
69
- ): DeltaVisitor {
70
- {
71
- const set = new Set(visitors);
72
- for (const item of announcedVisitors) {
73
- assert(set.has(item), 0x8c8 /* AnnouncedVisitor would not get traversed */);
74
- }
75
- }
72
+ export function combineVisitors(visitors: readonly CombinableVisitor[]): CombinedVisitor {
73
+ const allVisitors = visitors.flatMap((v) => (v.type === "Combined" ? v.visitors : [v]));
74
+ const announcedVisitors = allVisitors.filter(
75
+ (v): v is AnnouncedVisitor => v.type === "Announced",
76
+ );
76
77
  return {
78
+ type: "Combined",
79
+ visitors: allVisitors,
77
80
  free: () => visitors.forEach((v) => v.free()),
78
81
  create: (...args) => {
79
- visitors.forEach((v) => v.create(...args));
82
+ allVisitors.forEach((v) => v.create(...args));
80
83
  announcedVisitors.forEach((v) => v.afterCreate(...args));
81
84
  },
82
85
  destroy: (...args) => {
83
86
  announcedVisitors.forEach((v) => v.beforeDestroy(...args));
84
- visitors.forEach((v) => v.destroy(...args));
87
+ allVisitors.forEach((v) => v.destroy(...args));
85
88
  },
86
89
  attach: (source: FieldKey, count: number, destination: PlaceIndex) => {
87
90
  announcedVisitors.forEach((v) => v.beforeAttach(source, count, destination));
88
- visitors.forEach((v) => v.attach(source, count, destination));
91
+ allVisitors.forEach((v) => v.attach(source, count, destination));
89
92
  announcedVisitors.forEach((v) =>
90
93
  v.afterAttach(source, {
91
94
  start: destination,
@@ -93,33 +96,22 @@ export function combineVisitors(
93
96
  }),
94
97
  );
95
98
  },
96
- detach: (source: Range, destination: FieldKey, id: DetachedNodeId) => {
97
- announcedVisitors.forEach((v) => v.beforeDetach(source, destination));
98
- visitors.forEach((v) => v.detach(source, destination, id));
99
- announcedVisitors.forEach((v) =>
100
- v.afterDetach(source.start, source.end - source.start, destination),
101
- );
102
- },
103
- replace: (
104
- newContent: FieldKey,
105
- oldContent: Range,
106
- oldContentDestination: FieldKey,
107
- oldContentId: DetachedNodeId,
99
+ detach: (
100
+ source: Range,
101
+ destination: FieldKey,
102
+ id: DetachedNodeId,
103
+ isReplaced: boolean,
108
104
  ) => {
105
+ announcedVisitors.forEach((v) => v.beforeDetach(source, destination, isReplaced));
106
+ allVisitors.forEach((v) => v.detach(source, destination, id, isReplaced));
109
107
  announcedVisitors.forEach((v) =>
110
- v.beforeReplace(newContent, oldContent, oldContentDestination),
111
- );
112
- visitors.forEach((v) =>
113
- v.replace(newContent, oldContent, oldContentDestination, oldContentId),
114
- );
115
- announcedVisitors.forEach((v) =>
116
- v.afterReplace(newContent, oldContent, oldContentDestination),
108
+ v.afterDetach(source.start, source.end - source.start, destination, isReplaced),
117
109
  );
118
110
  },
119
- enterNode: (...args) => visitors.forEach((v) => v.enterNode(...args)),
120
- exitNode: (...args) => visitors.forEach((v) => v.exitNode(...args)),
121
- enterField: (...args) => visitors.forEach((v) => v.enterField(...args)),
122
- exitField: (...args) => visitors.forEach((v) => v.exitField(...args)),
111
+ enterNode: (...args) => allVisitors.forEach((v) => v.enterNode(...args)),
112
+ exitNode: (...args) => allVisitors.forEach((v) => v.exitNode(...args)),
113
+ enterField: (...args) => allVisitors.forEach((v) => v.enterField(...args)),
114
+ exitField: (...args) => allVisitors.forEach((v) => v.exitField(...args)),
123
115
  };
124
116
  }
125
117
 
@@ -128,6 +120,7 @@ export function combineVisitors(
128
120
  * Must be freed after use.
129
121
  */
130
122
  export interface AnnouncedVisitor extends DeltaVisitor {
123
+ readonly type: "Announced";
131
124
  /**
132
125
  * A hook that is called after all nodes have been created.
133
126
  */
@@ -135,14 +128,13 @@ export interface AnnouncedVisitor extends DeltaVisitor {
135
128
  beforeDestroy(field: FieldKey, count: number): void;
136
129
  beforeAttach(source: FieldKey, count: number, destination: PlaceIndex): void;
137
130
  afterAttach(source: FieldKey, destination: Range): void;
138
- beforeDetach(source: Range, destination: FieldKey): void;
139
- afterDetach(source: PlaceIndex, count: number, destination: FieldKey): void;
140
- beforeReplace(
141
- newContent: FieldKey,
142
- oldContent: Range,
143
- oldContentDestination: FieldKey,
131
+ beforeDetach(source: Range, destination: FieldKey, isReplaced: boolean): void;
132
+ afterDetach(
133
+ source: PlaceIndex,
134
+ count: number,
135
+ destination: FieldKey,
136
+ isReplaced: boolean,
144
137
  ): void;
145
- afterReplace(newContentSource: FieldKey, newContent: Range, oldContent: FieldKey): void;
146
138
  }
147
139
 
148
140
  /**
@@ -154,6 +146,7 @@ export function createAnnouncedVisitor(
154
146
  ): AnnouncedVisitor {
155
147
  const noOp = (): void => {};
156
148
  return {
149
+ type: "Announced",
157
150
  free: visitorFunctions.free ?? noOp,
158
151
  create: visitorFunctions.create ?? noOp,
159
152
  afterCreate: visitorFunctions.afterCreate ?? noOp,
@@ -165,9 +158,6 @@ export function createAnnouncedVisitor(
165
158
  beforeDetach: visitorFunctions.beforeDetach ?? noOp,
166
159
  detach: visitorFunctions.detach ?? noOp,
167
160
  afterDetach: visitorFunctions.afterDetach ?? noOp,
168
- beforeReplace: visitorFunctions.beforeReplace ?? noOp,
169
- replace: visitorFunctions.replace ?? noOp,
170
- afterReplace: visitorFunctions.afterReplace ?? noOp,
171
161
  enterNode: visitorFunctions.enterNode ?? noOp,
172
162
  exitNode: visitorFunctions.exitNode ?? noOp,
173
163
  enterField: visitorFunctions.enterField ?? noOp,
@@ -183,6 +183,12 @@ export class ChunkedForest implements IEditableForest {
183
183
  * If not specified, the detached range is destroyed.
184
184
  */
185
185
  detachEdit(source: Range, destination: FieldKey | undefined): void {
186
+ // TODO: optimize this to perform in-place replace in uniform chunks when attach edits bring the chunk back to its original shape.
187
+ // This should result in 3 cases:
188
+ // 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks.
189
+ // 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy.
190
+ // 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed).
191
+
186
192
  this.forest.#events.emit("beforeChange");
187
193
  const parent = this.getParent();
188
194
  const sourceField = parent.mutableChunk.fields.get(parent.key) ?? [];
@@ -209,24 +215,6 @@ export class ChunkedForest implements IEditableForest {
209
215
  parent.mutableChunk.fields.delete(parent.key);
210
216
  }
211
217
  },
212
- replace(
213
- newContentSource: FieldKey,
214
- range: Range,
215
- oldContentDestination: FieldKey,
216
- oldContentId: DeltaDetachedNodeId,
217
- ): void {
218
- assert(
219
- newContentSource !== oldContentDestination,
220
- 0x7b0 /* Replace detached source field and detached destination field must be different */,
221
- );
222
- // TODO: optimize this to: perform in-place replace in uniform chunks when possible.
223
- // This should result in 3 cases:
224
- // 1. In-place update of uniform chunk. No allocations, no ref count changes, no new TreeChunks.
225
- // 2. Uniform chunk is shared: copy it (and parent path as needed), and update the copy.
226
- // 3. Fallback to detach then attach (Which will copy parents and convert to basic chunks as needed).
227
- this.detachEdit(range, oldContentDestination);
228
- this.attachEdit(newContentSource, range.end - range.start, range.start);
229
- },
230
218
  enterNode(index: number): void {
231
219
  assert(this.mutableChunk === undefined, 0x535 /* should be in field */);
232
220
  const parent = this.getParent();
@@ -289,10 +277,7 @@ export class ChunkedForest implements IEditableForest {
289
277
 
290
278
  const announcedVisitors: AnnouncedVisitor[] = [];
291
279
  this.deltaVisitors.forEach((getVisitor) => announcedVisitors.push(getVisitor()));
292
- const combinedVisitor = combineVisitors(
293
- [forestVisitor, ...announcedVisitors],
294
- announcedVisitors,
295
- );
280
+ const combinedVisitor = combineVisitors([forestVisitor, ...announcedVisitors]);
296
281
  this.activeVisitor = combinedVisitor;
297
282
  return combinedVisitor;
298
283
  }
@@ -66,7 +66,7 @@ export type Shape = ShapeGeneric<EncodedChunkShape>;
66
66
  */
67
67
  export interface KeyedFieldEncoder {
68
68
  readonly key: FieldKey;
69
- readonly shape: FieldEncoder;
69
+ readonly encoder: FieldEncoder;
70
70
  }
71
71
 
72
72
  /**
@@ -27,17 +27,35 @@ import type { EncodedChunkShape, EncodedFieldShape, EncodedValueShape } from "./
27
27
  import { isStableId } from "@fluidframework/id-compressor/internal";
28
28
 
29
29
  export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
30
- // TODO: Ensure uniform chunks, encoding and identifier generation sort fields the same.
31
- private readonly explicitKeys: Set<FieldKey>;
30
+ /**
31
+ * Set of keys for fields that are encoded using {@link NodeShape.specializedFieldEncoders}.
32
+ * TODO: Ensure uniform chunks, encoding and identifier generation sort fields the same.
33
+ */
34
+ private readonly specializedFieldKeys: Set<FieldKey>;
32
35
 
33
36
  public constructor(
34
37
  public readonly type: undefined | TreeNodeSchemaIdentifier,
35
38
  public readonly value: EncodedValueShape,
36
- public readonly fields: readonly KeyedFieldEncoder[],
37
- public readonly extraLocal: undefined | FieldEncoder,
39
+ /**
40
+ * Encoders for a specific set of fields, by key, in the order they will be encoded.
41
+ * These are fields for which specialized encoding is provided as an optimization.
42
+ * Using these for a given field instead of falling back to {@link NodeShape.specializedFieldEncoders} is often more efficient:
43
+ * this avoids the need to explicitly include the key and shape in the encoded data for each node instance.
44
+ * Instead, this information is here, and thus is encoded only once as part of the node shape.
45
+ * These encoders will be used, even if the field they apply to is empty (which can add overhead for fields which are usually empty).
46
+ *
47
+ * Any fields not included here will be encoded using {@link NodeShape.otherFieldsEncoder}.
48
+ * If {@link NodeShape.otherFieldsEncoder} is undefined, then this must handle all non-empty fields.
49
+ */
50
+ public readonly specializedFieldEncoders: readonly KeyedFieldEncoder[],
51
+ /**
52
+ * Encoder for all other fields that are not in {@link NodeShape.specializedFieldEncoders}. These fields must
53
+ * be encoded after the specialized fields.
54
+ */
55
+ public readonly otherFieldsEncoder: undefined | FieldEncoder,
38
56
  ) {
39
57
  super();
40
- this.explicitKeys = new Set(this.fields.map((f) => f.key));
58
+ this.specializedFieldKeys = new Set(this.specializedFieldEncoders.map((f) => f.key));
41
59
  }
42
60
 
43
61
  private getValueToEncode(cursor: ITreeCursorSynchronous, cache: EncoderCache): Value {
@@ -64,28 +82,28 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
64
82
  assert(cursor.type === this.type, 0x741 /* type must match shape */);
65
83
  }
66
84
  encodeValue(this.getValueToEncode(cursor, cache), this.value, outputBuffer);
67
- for (const field of this.fields) {
68
- cursor.enterField(brand(field.key));
69
- field.shape.encodeField(cursor, cache, outputBuffer);
85
+ for (const fieldEncoder of this.specializedFieldEncoders) {
86
+ cursor.enterField(brand(fieldEncoder.key));
87
+ fieldEncoder.encoder.encodeField(cursor, cache, outputBuffer);
70
88
  cursor.exitField();
71
89
  }
72
90
 
73
- const localBuffer: BufferFormat<EncodedChunkShape> = [];
91
+ const otherFieldsBuffer: BufferFormat<EncodedChunkShape> = [];
74
92
 
75
93
  forEachField(cursor, () => {
76
94
  const key = cursor.getFieldKey();
77
- if (!this.explicitKeys.has(key)) {
95
+ if (!this.specializedFieldKeys.has(key)) {
78
96
  assert(
79
- this.extraLocal !== undefined,
97
+ this.otherFieldsEncoder !== undefined,
80
98
  0x742 /* had extra local fields when shape does not support them */,
81
99
  );
82
- localBuffer.push(new IdentifierToken(key));
83
- this.extraLocal.encodeField(cursor, cache, localBuffer);
100
+ otherFieldsBuffer.push(new IdentifierToken(key));
101
+ this.otherFieldsEncoder.encodeField(cursor, cache, otherFieldsBuffer);
84
102
  }
85
103
  });
86
104
 
87
- if (this.extraLocal !== undefined) {
88
- outputBuffer.push(localBuffer);
105
+ if (this.otherFieldsEncoder !== undefined) {
106
+ outputBuffer.push(otherFieldsBuffer);
89
107
  }
90
108
  }
91
109
 
@@ -97,8 +115,8 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
97
115
  c: {
98
116
  type: encodeOptionalIdentifier(this.type, identifiers),
99
117
  value: this.value,
100
- fields: encodeFieldShapes(this.fields, identifiers, shapes),
101
- extraFields: encodeOptionalFieldShape(this.extraLocal, shapes),
118
+ fields: encodeFieldShapes(this.specializedFieldEncoders, identifiers, shapes),
119
+ extraFields: encodeOptionalFieldShape(this.otherFieldsEncoder, shapes),
102
120
  },
103
121
  };
104
122
  }
@@ -111,13 +129,13 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
111
129
  identifiers.add(this.type);
112
130
  }
113
131
 
114
- for (const field of this.fields) {
115
- identifiers.add(field.key);
116
- shapes(field.shape.shape);
132
+ for (const fieldEncoder of this.specializedFieldEncoders) {
133
+ identifiers.add(fieldEncoder.key);
134
+ shapes(fieldEncoder.encoder.shape);
117
135
  }
118
136
 
119
- if (this.extraLocal !== undefined) {
120
- shapes(this.extraLocal.shape);
137
+ if (this.otherFieldsEncoder !== undefined) {
138
+ shapes(this.otherFieldsEncoder.shape);
121
139
  }
122
140
  }
123
141
 
@@ -127,18 +145,18 @@ export class NodeShape extends Shape<EncodedChunkShape> implements NodeEncoder {
127
145
  }
128
146
 
129
147
  export function encodeFieldShapes(
130
- fields: readonly KeyedFieldEncoder[],
148
+ fieldEncoders: readonly KeyedFieldEncoder[],
131
149
  identifiers: DeduplicationTable<string>,
132
150
  shapes: DeduplicationTable<Shape<EncodedChunkShape>>,
133
151
  ): EncodedFieldShape[] | undefined {
134
- if (fields.length === 0) {
152
+ if (fieldEncoders.length === 0) {
135
153
  return undefined;
136
154
  }
137
- return fields.map((field) => [
155
+ return fieldEncoders.map((fieldEncoder) => [
138
156
  // key
139
- encodeIdentifier(field.key, identifiers),
157
+ encodeIdentifier(fieldEncoder.key, identifiers),
140
158
  // shape
141
- shapes.valueToIndex.get(field.shape.shape) ?? fail(0xb50 /* missing shape */),
159
+ shapes.valueToIndex.get(fieldEncoder.encoder.shape) ?? fail(0xb50 /* missing shape */),
142
160
  ]);
143
161
  }
144
162
 
@@ -122,7 +122,7 @@ export function treeShaper(
122
122
 
123
123
  const objectNodeFields: KeyedFieldEncoder[] = [];
124
124
  for (const [key, field] of schema.objectNodeFields ?? []) {
125
- objectNodeFields.push({ key, shape: fieldHandler.shapeFromField(field) });
125
+ objectNodeFields.push({ key, encoder: fieldHandler.shapeFromField(field) });
126
126
  }
127
127
 
128
128
  const shape = new NodeShape(schemaName, false, objectNodeFields, undefined);
@@ -139,27 +139,14 @@ export class AnchorTreeIndex<TKey extends TreeIndexKey, TValue>
139
139
  assert(parent !== undefined, 0xa99 /* must have a parent */);
140
140
  this.reIndexSpine(parent);
141
141
  },
142
- afterDetach: () => {
143
- assert(parent !== undefined, 0xa9a /* must have a parent */);
144
- this.reIndexSpine(parent);
145
- },
146
- // when a replace happens, the keys of previously indexed nodes could be changed so we must re-index them
147
- afterReplace: () => {
148
- assert(parent !== undefined, 0xa8b /* must have a parent */);
149
- const cursor = this.forest.allocateCursor();
150
- this.forest.moveCursorToPath(parent, cursor);
151
- assert(
152
- cursor.mode === CursorLocationType.Nodes,
153
- 0xa8c /* replace should happen in a node */,
154
- );
155
- cursor.exitNode();
156
- this.indexField(cursor);
157
- if (!this.isShallowIndex) {
158
- // we must also re-index the spine if the key finders allow for any value under a subtree to be the key
159
- // this means that a replace can cause the key for any node up its spine to be changed
160
- this.indexSpine(cursor);
142
+ afterDetach: (_source, _count_, _destination, isReplaced) => {
143
+ if (isReplaced) {
144
+ // If the node will be replaced, we defer re-indexing until the corresponding attach event.
145
+ // This has performance benefits but is also required to avoid experiencing the error case where the field that is used as the indexing key is empty.
146
+ } else {
147
+ assert(parent !== undefined, 0xa9a /* must have a parent */);
148
+ this.reIndexSpine(parent);
161
149
  }
162
- cursor.clear();
163
150
  },
164
151
  // the methods below are used to keep track of the path that has been traversed by the visitor
165
152
  // this is required so that cursors can be moved to the correct location when index updates are required
@@ -315,17 +302,19 @@ export class AnchorTreeIndex<TKey extends TreeIndexKey, TValue>
315
302
  * Checks if the spine needs to be re-indexed and if so, re-indexes it starting from the given path.
316
303
  */
317
304
  private reIndexSpine(path: UpPath): void {
305
+ const cursor = this.forest.allocateCursor();
306
+ this.forest.moveCursorToPath(path, cursor);
307
+ assert(
308
+ cursor.mode === CursorLocationType.Nodes,
309
+ 0xa9b /* attach should happen in a node */,
310
+ );
311
+ cursor.exitNode();
312
+ // TODO ADO:36390 avoid re-indexing the whole field when not necessary
313
+ this.indexField(cursor);
318
314
  if (!this.isShallowIndex) {
319
- const cursor = this.forest.allocateCursor();
320
- this.forest.moveCursorToPath(path, cursor);
321
- assert(
322
- cursor.mode === CursorLocationType.Nodes,
323
- 0xa9b /* attach should happen in a node */,
324
- );
325
- cursor.exitNode();
326
315
  this.indexSpine(cursor);
327
- cursor.clear();
328
316
  }
317
+ cursor.clear();
329
318
  }
330
319
 
331
320
  private checkNotDisposed(errorMessage?: string): void {
@@ -236,20 +236,6 @@ export class ObjectForest implements IEditableForest {
236
236
  parent.fields.delete(key);
237
237
  }
238
238
  }
239
- public replace(
240
- newContentSource: FieldKey,
241
- range: Range,
242
- oldContentDestination: FieldKey,
243
- oldContentId: DeltaDetachedNodeId,
244
- ): void {
245
- preEdit();
246
- assert(
247
- newContentSource !== oldContentDestination,
248
- 0x7ba /* Replace detached source field and detached destination field must be different */,
249
- );
250
- this.detachEdit(range, oldContentDestination);
251
- this.attachEdit(newContentSource, range.end - range.start, range.start);
252
- }
253
239
  public enterNode(index: number): void {
254
240
  cursor.enterNode(index);
255
241
  }
@@ -267,10 +253,7 @@ export class ObjectForest implements IEditableForest {
267
253
  const forestVisitor = new Visitor(this);
268
254
  const announcedVisitors: AnnouncedVisitor[] = [];
269
255
  this.deltaVisitors.forEach((getVisitor) => announcedVisitors.push(getVisitor()));
270
- const combinedVisitor = combineVisitors(
271
- [forestVisitor, ...announcedVisitors],
272
- announcedVisitors,
273
- );
256
+ const combinedVisitor = combineVisitors([forestVisitor, ...announcedVisitors]);
274
257
  this.activeVisitor = combinedVisitor;
275
258
  return combinedVisitor;
276
259
  }