@fluidframework/tree 2.101.0 → 2.102.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 (291) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/tree.alpha.api.md +19 -0
  3. package/dist/codec/codec.d.ts +22 -53
  4. package/dist/codec/codec.d.ts.map +1 -1
  5. package/dist/codec/codec.js +7 -44
  6. package/dist/codec/codec.js.map +1 -1
  7. package/dist/codec/index.d.ts +1 -1
  8. package/dist/codec/index.d.ts.map +1 -1
  9. package/dist/codec/index.js +2 -2
  10. package/dist/codec/index.js.map +1 -1
  11. package/dist/codec/versioned/codec.d.ts +56 -28
  12. package/dist/codec/versioned/codec.d.ts.map +1 -1
  13. package/dist/codec/versioned/codec.js +29 -12
  14. package/dist/codec/versioned/codec.js.map +1 -1
  15. package/dist/codec/versioned/index.d.ts +1 -1
  16. package/dist/codec/versioned/index.d.ts.map +1 -1
  17. package/dist/codec/versioned/index.js +2 -2
  18. package/dist/codec/versioned/index.js.map +1 -1
  19. package/dist/core/tree/deltaUtil.d.ts +2 -2
  20. package/dist/core/tree/deltaUtil.js +2 -2
  21. package/dist/core/tree/deltaUtil.js.map +1 -1
  22. package/dist/core/tree/detachedFieldIndexCodecV1.d.ts.map +1 -1
  23. package/dist/core/tree/detachedFieldIndexCodecV1.js +3 -2
  24. package/dist/core/tree/detachedFieldIndexCodecV1.js.map +1 -1
  25. package/dist/core/tree/detachedFieldIndexCodecV2.d.ts.map +1 -1
  26. package/dist/core/tree/detachedFieldIndexCodecV2.js +4 -2
  27. package/dist/core/tree/detachedFieldIndexCodecV2.js.map +1 -1
  28. package/dist/core/tree/detachedFieldIndexCodecs.d.ts +3 -3
  29. package/dist/core/tree/detachedFieldIndexCodecs.d.ts.map +1 -1
  30. package/dist/core/tree/detachedFieldIndexCodecs.js +1 -1
  31. package/dist/core/tree/detachedFieldIndexCodecs.js.map +1 -1
  32. package/dist/feature-libraries/chunked-forest/chunkTree.d.ts +38 -1
  33. package/dist/feature-libraries/chunked-forest/chunkTree.d.ts.map +1 -1
  34. package/dist/feature-libraries/chunked-forest/chunkTree.js +62 -4
  35. package/dist/feature-libraries/chunked-forest/chunkTree.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 +18 -4
  38. package/dist/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  39. package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts +34 -2
  40. package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts.map +1 -1
  41. package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.js +106 -3
  42. package/dist/feature-libraries/chunked-forest/codec/chunkDecoding.js.map +1 -1
  43. package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts +5 -5
  44. package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
  45. package/dist/feature-libraries/chunked-forest/codec/codecs.js +2 -2
  46. package/dist/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
  47. package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +7 -7
  48. package/dist/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
  49. package/dist/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
  50. package/dist/feature-libraries/chunked-forest/codec/format/formatV2.d.ts +25 -5
  51. package/dist/feature-libraries/chunked-forest/codec/format/formatV2.d.ts.map +1 -1
  52. package/dist/feature-libraries/chunked-forest/codec/format/formatV2.js +9 -3
  53. package/dist/feature-libraries/chunked-forest/codec/format/formatV2.js.map +1 -1
  54. package/dist/feature-libraries/chunked-forest/codec/format/formatVText.d.ts +105 -0
  55. package/dist/feature-libraries/chunked-forest/codec/format/formatVText.d.ts.map +1 -0
  56. package/dist/feature-libraries/chunked-forest/codec/format/formatVText.js +44 -0
  57. package/dist/feature-libraries/chunked-forest/codec/format/formatVText.js.map +1 -0
  58. package/dist/feature-libraries/chunked-forest/codec/format/index.d.ts +2 -1
  59. package/dist/feature-libraries/chunked-forest/codec/format/index.d.ts.map +1 -1
  60. package/dist/feature-libraries/chunked-forest/codec/format/index.js +5 -1
  61. package/dist/feature-libraries/chunked-forest/codec/format/index.js.map +1 -1
  62. package/dist/feature-libraries/chunked-forest/codec/format/versions.d.ts +38 -4
  63. package/dist/feature-libraries/chunked-forest/codec/format/versions.d.ts.map +1 -1
  64. package/dist/feature-libraries/chunked-forest/codec/format/versions.js +7 -1
  65. package/dist/feature-libraries/chunked-forest/codec/format/versions.js.map +1 -1
  66. package/dist/feature-libraries/chunked-forest/codec/nodeEncoder.d.ts +7 -7
  67. package/dist/feature-libraries/chunked-forest/codec/nodeEncoder.d.ts.map +1 -1
  68. package/dist/feature-libraries/chunked-forest/codec/nodeEncoder.js +1 -2
  69. package/dist/feature-libraries/chunked-forest/codec/nodeEncoder.js.map +1 -1
  70. package/dist/feature-libraries/forest-summary/codec.d.ts +5 -4
  71. package/dist/feature-libraries/forest-summary/codec.d.ts.map +1 -1
  72. package/dist/feature-libraries/forest-summary/codec.js +2 -2
  73. package/dist/feature-libraries/forest-summary/codec.js.map +1 -1
  74. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts +1 -1
  75. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  76. package/dist/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  77. package/dist/feature-libraries/schema-index/codec.d.ts +2 -2
  78. package/dist/feature-libraries/schema-index/codec.d.ts.map +1 -1
  79. package/dist/feature-libraries/schema-index/codec.js +1 -1
  80. package/dist/feature-libraries/schema-index/codec.js.map +1 -1
  81. package/dist/index.d.ts +1 -1
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +3 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/packageVersion.d.ts +1 -1
  86. package/dist/packageVersion.js +1 -1
  87. package/dist/packageVersion.js.map +1 -1
  88. package/dist/shared-tree/isAuditableFromOutcome.d.ts +20 -0
  89. package/dist/shared-tree/isAuditableFromOutcome.d.ts.map +1 -0
  90. package/dist/shared-tree/isAuditableFromOutcome.js +36 -0
  91. package/dist/shared-tree/isAuditableFromOutcome.js.map +1 -0
  92. package/dist/shared-tree/treeCheckout.d.ts +2 -0
  93. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  94. package/dist/shared-tree/treeCheckout.js +37 -8
  95. package/dist/shared-tree/treeCheckout.js.map +1 -1
  96. package/dist/shared-tree-core/editManagerCodecs.d.ts +3 -3
  97. package/dist/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
  98. package/dist/shared-tree-core/editManagerCodecs.js +2 -2
  99. package/dist/shared-tree-core/editManagerCodecs.js.map +1 -1
  100. package/dist/shared-tree-core/messageCodecs.d.ts +3 -3
  101. package/dist/shared-tree-core/messageCodecs.d.ts.map +1 -1
  102. package/dist/shared-tree-core/messageCodecs.js +2 -2
  103. package/dist/shared-tree-core/messageCodecs.js.map +1 -1
  104. package/dist/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
  105. package/dist/simple-tree/core/treeNodeKernel.js +25 -11
  106. package/dist/simple-tree/core/treeNodeKernel.js.map +1 -1
  107. package/dist/text/codePointUtils.d.ts +48 -0
  108. package/dist/text/codePointUtils.d.ts.map +1 -0
  109. package/dist/text/codePointUtils.js +80 -0
  110. package/dist/text/codePointUtils.js.map +1 -0
  111. package/dist/text/index.d.ts +1 -0
  112. package/dist/text/index.d.ts.map +1 -1
  113. package/dist/text/index.js +4 -1
  114. package/dist/text/index.js.map +1 -1
  115. package/dist/text/textDomain.d.ts +93 -1
  116. package/dist/text/textDomain.d.ts.map +1 -1
  117. package/dist/text/textDomain.js +65 -8
  118. package/dist/text/textDomain.js.map +1 -1
  119. package/dist/text/textDomainFormatted.d.ts +24 -6
  120. package/dist/text/textDomainFormatted.d.ts.map +1 -1
  121. package/dist/text/textDomainFormatted.js +29 -1
  122. package/dist/text/textDomainFormatted.js.map +1 -1
  123. package/dist/treeFactory.d.ts.map +1 -1
  124. package/dist/treeFactory.js +9 -7
  125. package/dist/treeFactory.js.map +1 -1
  126. package/dist/util/breakable.d.ts +7 -1
  127. package/dist/util/breakable.d.ts.map +1 -1
  128. package/dist/util/breakable.js +18 -4
  129. package/dist/util/breakable.js.map +1 -1
  130. package/lib/codec/codec.d.ts +22 -53
  131. package/lib/codec/codec.d.ts.map +1 -1
  132. package/lib/codec/codec.js +7 -44
  133. package/lib/codec/codec.js.map +1 -1
  134. package/lib/codec/index.d.ts +1 -1
  135. package/lib/codec/index.d.ts.map +1 -1
  136. package/lib/codec/index.js +1 -1
  137. package/lib/codec/index.js.map +1 -1
  138. package/lib/codec/versioned/codec.d.ts +56 -28
  139. package/lib/codec/versioned/codec.d.ts.map +1 -1
  140. package/lib/codec/versioned/codec.js +27 -10
  141. package/lib/codec/versioned/codec.js.map +1 -1
  142. package/lib/codec/versioned/index.d.ts +1 -1
  143. package/lib/codec/versioned/index.d.ts.map +1 -1
  144. package/lib/codec/versioned/index.js +1 -1
  145. package/lib/codec/versioned/index.js.map +1 -1
  146. package/lib/core/tree/deltaUtil.d.ts +2 -2
  147. package/lib/core/tree/deltaUtil.js +2 -2
  148. package/lib/core/tree/deltaUtil.js.map +1 -1
  149. package/lib/core/tree/detachedFieldIndexCodecV1.d.ts.map +1 -1
  150. package/lib/core/tree/detachedFieldIndexCodecV1.js +3 -2
  151. package/lib/core/tree/detachedFieldIndexCodecV1.js.map +1 -1
  152. package/lib/core/tree/detachedFieldIndexCodecV2.d.ts.map +1 -1
  153. package/lib/core/tree/detachedFieldIndexCodecV2.js +5 -3
  154. package/lib/core/tree/detachedFieldIndexCodecV2.js.map +1 -1
  155. package/lib/core/tree/detachedFieldIndexCodecs.d.ts +3 -3
  156. package/lib/core/tree/detachedFieldIndexCodecs.d.ts.map +1 -1
  157. package/lib/core/tree/detachedFieldIndexCodecs.js +2 -2
  158. package/lib/core/tree/detachedFieldIndexCodecs.js.map +1 -1
  159. package/lib/feature-libraries/chunked-forest/chunkTree.d.ts +38 -1
  160. package/lib/feature-libraries/chunked-forest/chunkTree.d.ts.map +1 -1
  161. package/lib/feature-libraries/chunked-forest/chunkTree.js +61 -4
  162. package/lib/feature-libraries/chunked-forest/chunkTree.js.map +1 -1
  163. package/lib/feature-libraries/chunked-forest/chunkedForest.d.ts.map +1 -1
  164. package/lib/feature-libraries/chunked-forest/chunkedForest.js +20 -6
  165. package/lib/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  166. package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts +34 -2
  167. package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.d.ts.map +1 -1
  168. package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.js +102 -2
  169. package/lib/feature-libraries/chunked-forest/codec/chunkDecoding.js.map +1 -1
  170. package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts +5 -5
  171. package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
  172. package/lib/feature-libraries/chunked-forest/codec/codecs.js +3 -3
  173. package/lib/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
  174. package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts +7 -7
  175. package/lib/feature-libraries/chunked-forest/codec/compressedEncode.d.ts.map +1 -1
  176. package/lib/feature-libraries/chunked-forest/codec/compressedEncode.js.map +1 -1
  177. package/lib/feature-libraries/chunked-forest/codec/format/formatV2.d.ts +25 -5
  178. package/lib/feature-libraries/chunked-forest/codec/format/formatV2.d.ts.map +1 -1
  179. package/lib/feature-libraries/chunked-forest/codec/format/formatV2.js +8 -2
  180. package/lib/feature-libraries/chunked-forest/codec/format/formatV2.js.map +1 -1
  181. package/lib/feature-libraries/chunked-forest/codec/format/formatVText.d.ts +105 -0
  182. package/lib/feature-libraries/chunked-forest/codec/format/formatVText.d.ts.map +1 -0
  183. package/lib/feature-libraries/chunked-forest/codec/format/formatVText.js +41 -0
  184. package/lib/feature-libraries/chunked-forest/codec/format/formatVText.js.map +1 -0
  185. package/lib/feature-libraries/chunked-forest/codec/format/index.d.ts +2 -1
  186. package/lib/feature-libraries/chunked-forest/codec/format/index.d.ts.map +1 -1
  187. package/lib/feature-libraries/chunked-forest/codec/format/index.js +2 -1
  188. package/lib/feature-libraries/chunked-forest/codec/format/index.js.map +1 -1
  189. package/lib/feature-libraries/chunked-forest/codec/format/versions.d.ts +38 -4
  190. package/lib/feature-libraries/chunked-forest/codec/format/versions.d.ts.map +1 -1
  191. package/lib/feature-libraries/chunked-forest/codec/format/versions.js +6 -0
  192. package/lib/feature-libraries/chunked-forest/codec/format/versions.js.map +1 -1
  193. package/lib/feature-libraries/chunked-forest/codec/nodeEncoder.d.ts +7 -7
  194. package/lib/feature-libraries/chunked-forest/codec/nodeEncoder.d.ts.map +1 -1
  195. package/lib/feature-libraries/chunked-forest/codec/nodeEncoder.js +2 -3
  196. package/lib/feature-libraries/chunked-forest/codec/nodeEncoder.js.map +1 -1
  197. package/lib/feature-libraries/forest-summary/codec.d.ts +5 -4
  198. package/lib/feature-libraries/forest-summary/codec.d.ts.map +1 -1
  199. package/lib/feature-libraries/forest-summary/codec.js +3 -3
  200. package/lib/feature-libraries/forest-summary/codec.js.map +1 -1
  201. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts +1 -1
  202. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  203. package/lib/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  204. package/lib/feature-libraries/schema-index/codec.d.ts +2 -2
  205. package/lib/feature-libraries/schema-index/codec.d.ts.map +1 -1
  206. package/lib/feature-libraries/schema-index/codec.js +2 -2
  207. package/lib/feature-libraries/schema-index/codec.js.map +1 -1
  208. package/lib/index.d.ts +1 -1
  209. package/lib/index.d.ts.map +1 -1
  210. package/lib/index.js +1 -1
  211. package/lib/index.js.map +1 -1
  212. package/lib/packageVersion.d.ts +1 -1
  213. package/lib/packageVersion.js +1 -1
  214. package/lib/packageVersion.js.map +1 -1
  215. package/lib/shared-tree/isAuditableFromOutcome.d.ts +20 -0
  216. package/lib/shared-tree/isAuditableFromOutcome.d.ts.map +1 -0
  217. package/lib/shared-tree/isAuditableFromOutcome.js +32 -0
  218. package/lib/shared-tree/isAuditableFromOutcome.js.map +1 -0
  219. package/lib/shared-tree/treeCheckout.d.ts +2 -0
  220. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  221. package/lib/shared-tree/treeCheckout.js +37 -8
  222. package/lib/shared-tree/treeCheckout.js.map +1 -1
  223. package/lib/shared-tree-core/editManagerCodecs.d.ts +3 -3
  224. package/lib/shared-tree-core/editManagerCodecs.d.ts.map +1 -1
  225. package/lib/shared-tree-core/editManagerCodecs.js +3 -3
  226. package/lib/shared-tree-core/editManagerCodecs.js.map +1 -1
  227. package/lib/shared-tree-core/messageCodecs.d.ts +3 -3
  228. package/lib/shared-tree-core/messageCodecs.d.ts.map +1 -1
  229. package/lib/shared-tree-core/messageCodecs.js +3 -3
  230. package/lib/shared-tree-core/messageCodecs.js.map +1 -1
  231. package/lib/simple-tree/core/treeNodeKernel.d.ts.map +1 -1
  232. package/lib/simple-tree/core/treeNodeKernel.js +25 -11
  233. package/lib/simple-tree/core/treeNodeKernel.js.map +1 -1
  234. package/lib/text/codePointUtils.d.ts +48 -0
  235. package/lib/text/codePointUtils.d.ts.map +1 -0
  236. package/lib/text/codePointUtils.js +75 -0
  237. package/lib/text/codePointUtils.js.map +1 -0
  238. package/lib/text/index.d.ts +1 -0
  239. package/lib/text/index.d.ts.map +1 -1
  240. package/lib/text/index.js +1 -0
  241. package/lib/text/index.js.map +1 -1
  242. package/lib/text/textDomain.d.ts +93 -1
  243. package/lib/text/textDomain.d.ts.map +1 -1
  244. package/lib/text/textDomain.js +56 -0
  245. package/lib/text/textDomain.js.map +1 -1
  246. package/lib/text/textDomainFormatted.d.ts +24 -6
  247. package/lib/text/textDomainFormatted.d.ts.map +1 -1
  248. package/lib/text/textDomainFormatted.js +30 -2
  249. package/lib/text/textDomainFormatted.js.map +1 -1
  250. package/lib/treeFactory.d.ts.map +1 -1
  251. package/lib/treeFactory.js +2 -0
  252. package/lib/treeFactory.js.map +1 -1
  253. package/lib/util/breakable.d.ts +7 -1
  254. package/lib/util/breakable.d.ts.map +1 -1
  255. package/lib/util/breakable.js +18 -4
  256. package/lib/util/breakable.js.map +1 -1
  257. package/package.json +24 -24
  258. package/src/codec/codec.ts +82 -73
  259. package/src/codec/index.ts +2 -1
  260. package/src/codec/versioned/codec.ts +173 -73
  261. package/src/codec/versioned/index.ts +2 -1
  262. package/src/core/tree/deltaUtil.ts +2 -2
  263. package/src/core/tree/detachedFieldIndexCodecV1.ts +3 -2
  264. package/src/core/tree/detachedFieldIndexCodecV2.ts +5 -3
  265. package/src/core/tree/detachedFieldIndexCodecs.ts +2 -2
  266. package/src/feature-libraries/chunked-forest/chunkTree.ts +85 -1
  267. package/src/feature-libraries/chunked-forest/chunkedForest.ts +27 -7
  268. package/src/feature-libraries/chunked-forest/codec/chunkDecoding.ts +143 -7
  269. package/src/feature-libraries/chunked-forest/codec/codecs.ts +30 -28
  270. package/src/feature-libraries/chunked-forest/codec/compressedEncode.ts +7 -7
  271. package/src/feature-libraries/chunked-forest/codec/format/formatV2.ts +11 -7
  272. package/src/feature-libraries/chunked-forest/codec/format/formatVText.ts +83 -0
  273. package/src/feature-libraries/chunked-forest/codec/format/index.ts +6 -1
  274. package/src/feature-libraries/chunked-forest/codec/format/versions.ts +25 -4
  275. package/src/feature-libraries/chunked-forest/codec/nodeEncoder.ts +14 -18
  276. package/src/feature-libraries/forest-summary/codec.ts +9 -4
  277. package/src/feature-libraries/modular-schema/modularChangeTypes.ts +1 -1
  278. package/src/feature-libraries/schema-index/codec.ts +2 -5
  279. package/src/index.ts +6 -1
  280. package/src/packageVersion.ts +1 -1
  281. package/src/shared-tree/isAuditableFromOutcome.ts +34 -0
  282. package/src/shared-tree/treeCheckout.ts +52 -9
  283. package/src/shared-tree-core/editManagerCodecs.ts +4 -6
  284. package/src/shared-tree-core/messageCodecs.ts +4 -4
  285. package/src/simple-tree/core/treeNodeKernel.ts +27 -13
  286. package/src/text/codePointUtils.ts +81 -0
  287. package/src/text/index.ts +1 -0
  288. package/src/text/textDomain.ts +155 -2
  289. package/src/text/textDomainFormatted.ts +73 -2
  290. package/src/treeFactory.ts +5 -0
  291. package/src/util/breakable.ts +27 -6
@@ -10,6 +10,7 @@ import { strictEnum, type Values } from "../../../../util/index.js";
10
10
  import { EncodedFieldBatchGeneric } from "./formatGeneric.js";
11
11
  import { EncodedChunkShapeV1 } from "./formatV1.js";
12
12
  import { EncodedChunkShapeV2 } from "./formatV2.js";
13
+ import { EncodedChunkShapeVTextExperimental } from "./formatVText.js";
13
14
 
14
15
  /**
15
16
  * The format version for the field batch.
@@ -29,6 +30,10 @@ export const FieldBatchFormatVersion = strictEnum("FieldBatchFormatVersion", {
29
30
  * {@link EncodedIncrementalChunkShape} was added in this version.
30
31
  */
31
32
  v2: 2,
33
+ /**
34
+ * Experimental codec with optimizations for text.
35
+ */
36
+ vTextExperimental: "text",
32
37
  });
33
38
 
34
39
  /**
@@ -68,6 +73,17 @@ export const EncodedFieldBatchV2 = EncodedFieldBatchGeneric(
68
73
  EncodedChunkShapeV2,
69
74
  );
70
75
 
76
+ /**
77
+ * Encoded {@link FieldBatch} using the experimental text optimized format.
78
+ */
79
+ export type EncodedFieldBatchVTextExperimental = Static<
80
+ typeof EncodedFieldBatchVTextExperimental
81
+ >;
82
+ export const EncodedFieldBatchVTextExperimental = EncodedFieldBatchGeneric(
83
+ FieldBatchFormatVersion.vTextExperimental,
84
+ EncodedChunkShapeVTextExperimental,
85
+ );
86
+
71
87
  /**
72
88
  * Encoded {@link FieldBatch}, which might use V2 features, but might also have been from a V1 encoder.
73
89
  * @remarks
@@ -88,9 +104,14 @@ export type EncodedFieldBatchV1OrV2 = EncodedFieldBatchV1 | EncodedFieldBatchV2;
88
104
  export type EncodedFieldBatchV1AndV2 = EncodedFieldBatchV1 & EncodedFieldBatchV2;
89
105
 
90
106
  /**
91
- * Encoded chunk shape, which might use V2 features, but might also have been from a V1 encoder.
107
+ * An encoded chunk shape from any known {@link FieldBatchFormatVersion}.
108
+ *
92
109
  * @remarks
93
- * Type wise, equivalent to V2, as that is a superset of V1.
94
- * Used instead of just V2 for clarity.
110
+ * Use this when working with chunk shapes uniformly across versions — for example, in the
111
+ * shared decoder dispatcher and in encoder shape base classes. New format versions should
112
+ * add their chunk shape variant to this union.
95
113
  */
96
- export type EncodedChunkShapeV1OrV2 = EncodedChunkShapeV1 | EncodedChunkShapeV2;
114
+ export type EncodedChunkShape =
115
+ | EncodedChunkShapeV1
116
+ | EncodedChunkShapeV2
117
+ | EncodedChunkShapeVTextExperimental;
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { assert, fail } from "@fluidframework/core-utils/internal";
7
- import { isStableId } from "@fluidframework/id-compressor/internal";
7
+ import { isFinalId, isStableId } from "@fluidframework/id-compressor/internal";
8
8
 
9
9
  import {
10
10
  type FieldKey,
@@ -25,7 +25,7 @@ import {
25
25
  encodeValue,
26
26
  } from "./compressedEncode.js";
27
27
  import type {
28
- EncodedChunkShapeV1OrV2,
28
+ EncodedChunkShape,
29
29
  EncodedFieldShape,
30
30
  EncodedValueShape,
31
31
  } from "./format/index.js";
@@ -36,10 +36,7 @@ import type {
36
36
  * The fact this is also a Shape is an implementation detail of the encoder: that allows the shape it uses to be itself,
37
37
  * which is an easy way to keep all the related code together without extra objects.
38
38
  */
39
- export class NodeShapeBasedEncoder
40
- extends Shape<EncodedChunkShapeV1OrV2>
41
- implements NodeEncoder
42
- {
39
+ export class NodeShapeBasedEncoder extends Shape<EncodedChunkShape> implements NodeEncoder {
43
40
  /**
44
41
  * Set of keys for fields that are encoded using {@link NodeShapeBasedEncoder.specializedFieldEncoders}.
45
42
  * TODO: Ensure uniform chunks, encoding and identifier generation sort fields the same.
@@ -82,8 +79,7 @@ export class NodeShapeBasedEncoder
82
79
  // This is not the case for forest summaries at the time of writing, so non-finalized ids are instead written using
83
80
  // their long form (by falling through to the original cursor value).
84
81
  // A scenario where such ids can appear in the summary is in the attach summary of a tree being attached to an already-attached container.
85
- // TODO: isFinalId should probably be exported from id-compressor and that could be used to do the narrowing here.
86
- if (!context.isSummary || opSpaceId >= 0) {
82
+ if (!context.isSummary || isFinalId(opSpaceId)) {
87
83
  return opSpaceId;
88
84
  }
89
85
  }
@@ -95,7 +91,7 @@ export class NodeShapeBasedEncoder
95
91
  public encodeNode(
96
92
  cursor: ITreeCursorSynchronous,
97
93
  context: EncoderContext,
98
- outputBuffer: BufferFormat<EncodedChunkShapeV1OrV2>,
94
+ outputBuffer: BufferFormat<EncodedChunkShape>,
99
95
  ): void {
100
96
  if (this.type === undefined) {
101
97
  outputBuffer.push(new IdentifierToken(cursor.type));
@@ -109,7 +105,7 @@ export class NodeShapeBasedEncoder
109
105
  cursor.exitField();
110
106
  }
111
107
 
112
- const otherFieldsBuffer: BufferFormat<EncodedChunkShapeV1OrV2> = [];
108
+ const otherFieldsBuffer: BufferFormat<EncodedChunkShape> = [];
113
109
 
114
110
  forEachField(cursor, () => {
115
111
  const key = cursor.getFieldKey();
@@ -130,8 +126,8 @@ export class NodeShapeBasedEncoder
130
126
 
131
127
  public encodeShape(
132
128
  identifiers: DeduplicationTable<string>,
133
- shapes: DeduplicationTable<Shape<EncodedChunkShapeV1OrV2>>,
134
- ): EncodedChunkShapeV1OrV2 {
129
+ shapes: DeduplicationTable<Shape<EncodedChunkShape>>,
130
+ ): EncodedChunkShape {
135
131
  return {
136
132
  c: {
137
133
  type: encodeOptionalIdentifier(this.type, identifiers),
@@ -144,7 +140,7 @@ export class NodeShapeBasedEncoder
144
140
 
145
141
  public countReferencedShapesAndIdentifiers(
146
142
  identifiers: Counter<string>,
147
- shapeDiscovered: (shape: Shape<EncodedChunkShapeV1OrV2>) => void,
143
+ shapeDiscovered: (shape: Shape<EncodedChunkShape>) => void,
148
144
  ): void {
149
145
  if (this.type !== undefined) {
150
146
  identifiers.add(this.type);
@@ -160,7 +156,7 @@ export class NodeShapeBasedEncoder
160
156
  }
161
157
  }
162
158
 
163
- public get shape(): Shape<EncodedChunkShapeV1OrV2> {
159
+ public get shape(): Shape<EncodedChunkShape> {
164
160
  return this;
165
161
  }
166
162
  }
@@ -168,7 +164,7 @@ export class NodeShapeBasedEncoder
168
164
  export function encodeFieldShapes(
169
165
  fieldEncoders: readonly KeyedFieldEncoder[],
170
166
  identifiers: DeduplicationTable<string>,
171
- shapes: DeduplicationTable<Shape<EncodedChunkShapeV1OrV2>>,
167
+ shapes: DeduplicationTable<Shape<EncodedChunkShape>>,
172
168
  ): EncodedFieldShape[] | undefined {
173
169
  if (fieldEncoders.length === 0) {
174
170
  return undefined;
@@ -197,14 +193,14 @@ function encodeOptionalIdentifier(
197
193
 
198
194
  function encodeOptionalFieldShape(
199
195
  encoder: FieldEncoder | undefined,
200
- shapes: DeduplicationTable<Shape<EncodedChunkShapeV1OrV2>>,
196
+ shapes: DeduplicationTable<Shape<EncodedChunkShape>>,
201
197
  ): number | undefined {
202
198
  return encoder === undefined ? undefined : dedupShape(encoder.shape, shapes);
203
199
  }
204
200
 
205
201
  function dedupShape(
206
- shape: Shape<EncodedChunkShapeV1OrV2>,
207
- shapes: DeduplicationTable<Shape<EncodedChunkShapeV1OrV2>>,
202
+ shape: Shape<EncodedChunkShape>,
203
+ shapes: DeduplicationTable<Shape<EncodedChunkShape>>,
208
204
  ): number {
209
205
  return shapes.valueToIndex.get(shape) ?? fail(0xb51 /* missing shape */);
210
206
  }
@@ -7,9 +7,10 @@ import { assert, oob } from "@fluidframework/core-utils/internal";
7
7
  import { lowestMinVersionForCollab } from "@fluidframework/runtime-utils/internal";
8
8
 
9
9
  import {
10
- ClientVersionDispatchingCodecBuilder,
10
+ VersionDispatchingCodecBuilder,
11
11
  type CodecAndSchema,
12
12
  type CodecWriteOptions,
13
+ type VersionDispatchingCodec,
13
14
  FluidClientVersion,
14
15
  } from "../../codec/index.js";
15
16
  import type { FieldKey, ITreeCursorSynchronous } from "../../core/index.js";
@@ -24,7 +25,11 @@ import { ForestFormatVersion, FormatCommon } from "./formatCommon.js";
24
25
  * Uses field cursors
25
26
  */
26
27
  export type FieldSet = ReadonlyMap<FieldKey, ITreeCursorSynchronous>;
27
- export type ForestCodec = ReturnType<typeof forestCodecBuilder.build>;
28
+ export type ForestCodec = VersionDispatchingCodec<
29
+ FieldSet,
30
+ FieldBatchEncodingContext,
31
+ ForestFormatVersion
32
+ >;
28
33
 
29
34
  function makeForestSummarizerCodec(
30
35
  options: CodecWriteOptions,
@@ -64,9 +69,9 @@ function makeForestSummarizerCodec(
64
69
  }
65
70
 
66
71
  /**
67
- * {@link ClientVersionDispatchingCodecBuilder} for forest summarizer codecs.
72
+ * {@link VersionDispatchingCodecBuilder} for forest summarizer codecs.
68
73
  */
69
- export const forestCodecBuilder = ClientVersionDispatchingCodecBuilder.build("Forest", [
74
+ export const forestCodecBuilder = VersionDispatchingCodecBuilder.build("Forest", [
70
75
  {
71
76
  minVersionForCollab: lowestMinVersionForCollab,
72
77
  formatVersion: ForestFormatVersion.v1,
@@ -17,7 +17,7 @@ import type { TreeChunk } from "../chunked-forest/index.js";
17
17
 
18
18
  import type { CrossFieldTarget } from "./crossFieldQueries.js";
19
19
 
20
- export interface ModularChangeset extends HasFieldChanges {
20
+ export interface ModularChangeset extends Readonly<HasFieldChanges> {
21
21
  /**
22
22
  * The numerically highest `ChangesetLocalId` used in this changeset.
23
23
  * If undefined then this changeset contains no IDs.
@@ -6,10 +6,7 @@
6
6
  import { fail } from "@fluidframework/core-utils/internal";
7
7
  import { lowestMinVersionForCollab } from "@fluidframework/runtime-utils/internal";
8
8
 
9
- import {
10
- ClientVersionDispatchingCodecBuilder,
11
- FluidClientVersion,
12
- } from "../../codec/index.js";
9
+ import { VersionDispatchingCodecBuilder, FluidClientVersion } from "../../codec/index.js";
13
10
  import {
14
11
  SchemaFormatVersion,
15
12
  type TreeNodeSchemaIdentifier,
@@ -100,7 +97,7 @@ function decodeV2(f: FormatV2): TreeStoredSchema {
100
97
  /**
101
98
  * Creates a codec which performs synchronous monolithic encoding of schema content.
102
99
  */
103
- export const schemaCodecBuilder = ClientVersionDispatchingCodecBuilder.build("Schema", [
100
+ export const schemaCodecBuilder = VersionDispatchingCodecBuilder.build("Schema", [
104
101
  {
105
102
  minVersionForCollab: lowestMinVersionForCollab,
106
103
  formatVersion: SchemaFormatVersion.v1,
package/src/index.ts CHANGED
@@ -403,5 +403,10 @@ export { FluidSerializableAsTree } from "./serializableDomainSchema.js";
403
403
  export { TableSchema, type System_TableSchema } from "./tableSchema.js";
404
404
  export { asAlpha, asBeta } from "./api.js";
405
405
 
406
- export { TextAsTree, FormattedTextAsTree } from "./text/index.js";
406
+ export {
407
+ TextAsTree,
408
+ FormattedTextAsTree,
409
+ codePointCount,
410
+ utf16LengthForCodePoints,
411
+ } from "./text/index.js";
407
412
  export { ExtensibleUnionNode } from "./extensibleUnionNode.js";
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tree";
9
- export const pkgVersion = "2.101.0";
9
+ export const pkgVersion = "2.102.0";
@@ -0,0 +1,34 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import type { SharedTreeChange } from "./sharedTreeChangeTypes.js";
7
+
8
+ /**
9
+ * Determines whether a {@link SharedTreeChange} is auditable for HitL purposes
10
+ * by inspecting the resulting state after the change is applied.
11
+ *
12
+ * Returns `false` if any of the following is true:
13
+ * - The change contains more than one inner change.
14
+ * - The change contains a schema change.
15
+ * - The change contains a data change with violated constraints.
16
+ *
17
+ * Otherwise returns `true` (including for an empty change, which has nothing
18
+ * a viewer of the post-apply state would be unable to see).
19
+ *
20
+ */
21
+ export function isAuditableFromOutcome(change: SharedTreeChange): boolean {
22
+ if (change.changes.length > 1) {
23
+ return false;
24
+ }
25
+ for (const inner of change.changes) {
26
+ if (inner.type === "schema") {
27
+ return false;
28
+ }
29
+ if ((inner.innerChange.constraintViolationCount ?? 0) > 0) {
30
+ return false;
31
+ }
32
+ }
33
+ return true;
34
+ }
@@ -134,6 +134,20 @@ function* collectTreeLabels(node: LabelTree): IterableIterator<unknown> {
134
134
  }
135
135
  }
136
136
 
137
+ /**
138
+ * Deep-clones a {@link LabelTree} so the result is independent of the source.
139
+ * @remarks
140
+ * Used when capturing the label tree on a revertible so that subsequent mutations
141
+ * (by the framework or by external listeners reading {@link TransactionLabels.tree})
142
+ * cannot affect the labels the revertible will emit.
143
+ */
144
+ function cloneLabelTree(tree: LabelTree): LabelTree {
145
+ return {
146
+ label: tree.label,
147
+ sublabels: tree.sublabels.map(cloneLabelTree),
148
+ };
149
+ }
150
+
137
151
  /**
138
152
  * Builds the labels set for a change event from the label tree.
139
153
  * If the tree exists and contains at least one defined label, returns a set of all
@@ -713,6 +727,12 @@ export class TreeCheckout implements ITreeCheckout {
713
727
  const kind = event.type === "append" ? event.kind : CommitKind.Default;
714
728
  const { change, revision } = commit;
715
729
 
730
+ // Snapshot the label tree for this commit before any listener runs, so the captured
731
+ // value is stable against mutations to `metadata.labels.tree` (which aliases the
732
+ // live `labelTreeNode`).
733
+ const commitLabelTree =
734
+ this.labelTreeNode === undefined ? undefined : cloneLabelTree(this.labelTreeNode);
735
+
716
736
  const getRevertible = hasSchemaChange(change)
717
737
  ? undefined
718
738
  : (onRevertibleDisposed?: (revertible: RevertibleAlpha) => void) => {
@@ -731,6 +751,7 @@ export class TreeCheckout implements ITreeCheckout {
731
751
  kind,
732
752
  this,
733
753
  onRevertibleDisposed,
754
+ commitLabelTree,
734
755
  );
735
756
  this.revertibleCommitBranches.set(
736
757
  revision,
@@ -1010,6 +1031,8 @@ export class TreeCheckout implements ITreeCheckout {
1010
1031
  * @param kind - The {@link CommitKind} that produced this revertible (e.g., Default, Undo, Redo).
1011
1032
  * @param checkout - The {@link TreeCheckout} instance this revertible belongs to.
1012
1033
  * @param onRevertibleDisposed - Callback function that will be called when the revertible is disposed.
1034
+ * @param labelTree - The {@link LabelTree} (if any) active when the original commit was produced.
1035
+ * The revert commit inherits these labels so that commits and their reverts can be associated.
1013
1036
  * @returns A {@link RevertibleAlpha} object.
1014
1037
  */
1015
1038
  private createRevertible(
@@ -1017,6 +1040,7 @@ export class TreeCheckout implements ITreeCheckout {
1017
1040
  kind: CommitKind,
1018
1041
  checkout: TreeCheckout,
1019
1042
  onRevertibleDisposed: ((revertible: RevertibleAlpha) => void) | undefined,
1043
+ labelTree: LabelTree | undefined,
1020
1044
  ): RevertibleAlpha {
1021
1045
  const commitBranches = checkout.revertibleCommitBranches;
1022
1046
 
@@ -1032,7 +1056,7 @@ export class TreeCheckout implements ITreeCheckout {
1032
1056
  throw new UsageError("Unable to revert a revertible that has been disposed.");
1033
1057
  }
1034
1058
 
1035
- const revertMetrics = checkout.revertRevertible(revision, kind);
1059
+ const revertMetrics = checkout.revertRevertible(revision, kind, labelTree);
1036
1060
  checkout.logger?.sendTelemetryEvent({
1037
1061
  eventName: TreeCheckout.revertTelemetryEventName,
1038
1062
  ...revertMetrics,
@@ -1062,7 +1086,13 @@ export class TreeCheckout implements ITreeCheckout {
1062
1086
 
1063
1087
  targetCheckout.revertibleCommitBranches.set(revision, revertibleBranch.fork());
1064
1088
 
1065
- return this.createRevertible(revision, kind, targetCheckout, onRevertibleDisposed);
1089
+ return this.createRevertible(
1090
+ revision,
1091
+ kind,
1092
+ targetCheckout,
1093
+ onRevertibleDisposed,
1094
+ labelTree,
1095
+ );
1066
1096
  },
1067
1097
  dispose: () => {
1068
1098
  if (revertible.status === RevertibleStatus.Disposed) {
@@ -1323,7 +1353,11 @@ export class TreeCheckout implements ITreeCheckout {
1323
1353
  this.revertibles.delete(revertible);
1324
1354
  }
1325
1355
 
1326
- private revertRevertible(revision: RevisionTag, kind: CommitKind): RevertMetrics {
1356
+ private revertRevertible(
1357
+ revision: RevisionTag,
1358
+ kind: CommitKind,
1359
+ labelTree: LabelTree | undefined,
1360
+ ): RevertMetrics {
1327
1361
  this.editLock.checkUnlocked("Reverting a commit");
1328
1362
  if (this.transaction.size > 0) {
1329
1363
  throw new UsageError("Undo is not yet supported during transactions.");
@@ -1366,12 +1400,21 @@ export class TreeCheckout implements ITreeCheckout {
1366
1400
  );
1367
1401
  }
1368
1402
 
1369
- this.#transaction.activeBranch.apply(
1370
- change,
1371
- kind === CommitKind.Default || kind === CommitKind.Redo
1372
- ? CommitKind.Undo
1373
- : CommitKind.Redo,
1374
- );
1403
+ // Install the original commit's label tree so the revert commit's metadata inherits
1404
+ // the same labels. Reusing the captured tree (rather than wrapping it) ensures
1405
+ // revert-of-revert does not introduce new nesting.
1406
+ const previousLabelTreeNode = this.labelTreeNode;
1407
+ this.labelTreeNode = labelTree;
1408
+ try {
1409
+ this.#transaction.activeBranch.apply(
1410
+ change,
1411
+ kind === CommitKind.Default || kind === CommitKind.Redo
1412
+ ? CommitKind.Undo
1413
+ : CommitKind.Redo,
1414
+ );
1415
+ } finally {
1416
+ this.labelTreeNode = previousLabelTreeNode;
1417
+ }
1375
1418
 
1376
1419
  // Derive some stats about the reversion to return to the caller.
1377
1420
  let revertAge = 0;
@@ -8,7 +8,7 @@ import type { MinimumVersionForCollab } from "@fluidframework/runtime-definition
8
8
  import { lowestMinVersionForCollab } from "@fluidframework/runtime-utils/internal";
9
9
 
10
10
  import {
11
- ClientVersionDispatchingCodecBuilder,
11
+ VersionDispatchingCodecBuilder,
12
12
  type CodecTree,
13
13
  type CodecVersion,
14
14
  type DependentFormatVersion,
@@ -64,11 +64,9 @@ interface EditManagerCodecOptions<TChangeset> extends ICodecOptions {
64
64
  }
65
65
 
66
66
  /**
67
- * Creates a {@link ClientVersionDispatchingCodecBuilder} encoding for {@link SummaryData}.
67
+ * Creates a {@link VersionDispatchingCodecBuilder} encoding for {@link SummaryData}.
68
68
  */
69
- export function makeEditManagerCodecBuilder<
70
- TChangeset,
71
- >(): ClientVersionDispatchingCodecBuilder<
69
+ export function makeEditManagerCodecBuilder<TChangeset>(): VersionDispatchingCodecBuilder<
72
70
  EditManagerCodecOptions<TChangeset>,
73
71
  SummaryData<TChangeset>,
74
72
  EditManagerEncodingContext,
@@ -137,7 +135,7 @@ export function makeEditManagerCodecBuilder<
137
135
  },
138
136
  ];
139
137
 
140
- return ClientVersionDispatchingCodecBuilder.build(editManagerCodecName, versions);
138
+ return VersionDispatchingCodecBuilder.build(editManagerCodecName, versions);
141
139
  }
142
140
 
143
141
  /**
@@ -8,7 +8,7 @@ import type { MinimumVersionForCollab } from "@fluidframework/runtime-definition
8
8
  import { lowestMinVersionForCollab } from "@fluidframework/runtime-utils/internal";
9
9
 
10
10
  import {
11
- ClientVersionDispatchingCodecBuilder,
11
+ VersionDispatchingCodecBuilder,
12
12
  type CodecTree,
13
13
  type CodecVersion,
14
14
  type DependentFormatVersion,
@@ -58,9 +58,9 @@ interface MessageCodecBuilderOptions<TChangeset> extends ICodecOptions {
58
58
  }
59
59
 
60
60
  /**
61
- * Creates a {@link ClientVersionDispatchingCodecBuilder} for encoding/decoding messages.
61
+ * Creates a {@link VersionDispatchingCodecBuilder} for encoding/decoding messages.
62
62
  */
63
- export function makeMessageCodecBuilder<TChangeset>(): ClientVersionDispatchingCodecBuilder<
63
+ export function makeMessageCodecBuilder<TChangeset>(): VersionDispatchingCodecBuilder<
64
64
  MessageCodecBuilderOptions<TChangeset>,
65
65
  DecodedMessage<TChangeset>,
66
66
  MessageEncodingContext,
@@ -129,7 +129,7 @@ export function makeMessageCodecBuilder<TChangeset>(): ClientVersionDispatchingC
129
129
  },
130
130
  ];
131
131
 
132
- return ClientVersionDispatchingCodecBuilder.build(messageCodecName, versions);
132
+ return VersionDispatchingCodecBuilder.build(messageCodecName, versions);
133
133
  }
134
134
 
135
135
  export function getCodecTreeForMessageFormatWithChange(
@@ -120,14 +120,16 @@ export class TreeNodeKernel {
120
120
  #hydrationState: HydrationState;
121
121
 
122
122
  /**
123
- * Events registered before hydration.
123
+ * Handler for events listeners registered with the kernel.
124
+ *
124
125
  * @remarks
125
- * Since these are usually not used, they are allocated lazily as an optimization.
126
- * The laziness also avoids extra forwarding overhead for events from this kernel's anchor node and also avoids registering for events that are unneeded.
127
- * This means optimizations like skipping processing data in subtrees where no subtreeChanged events are subscribed to would be able to work,
128
- * since the kernel does not unconditionally subscribe to those events (like a design which simply forwards all events would).
126
+ * Supports event buffering via {@link withBufferedEvents}.
127
+ *
128
+ * Allocated lazily on first access to {@link TreeNodeKernel.events}.
129
+ * We expect the majority of nodes to never have event listeners registered, so
130
+ * deferring construction avoids per-kernel allocations.
129
131
  */
130
- readonly #eventBuffer: KernelEventBuffer;
132
+ #eventBuffer: KernelEventBuffer | undefined;
131
133
 
132
134
  /**
133
135
  * Create a TreeNodeKernel which can be looked up with {@link getKernel}.
@@ -157,12 +159,9 @@ export class TreeNodeKernel {
157
159
  this.#hydrationState = {
158
160
  innerNode,
159
161
  };
160
-
161
- this.#eventBuffer = new KernelEventBuffer(innerNode.events);
162
162
  } else {
163
163
  // Hydrated case
164
164
  this.#hydrationState = this.createHydratedState(innerNode);
165
- this.#eventBuffer = new KernelEventBuffer(innerNode.anchorNode.events);
166
165
  }
167
166
  }
168
167
 
@@ -190,8 +189,10 @@ export class TreeNodeKernel {
190
189
 
191
190
  this.#hydrationState = this.createHydratedState(inner);
192
191
 
193
- // Lazily migrate existing event listeners to the anchor node
194
- this.#eventBuffer.migrateEventSource(inner.anchorNode.events);
192
+ // Lazily migrate existing event listeners to the anchor node.
193
+ // If no one ever subscribed to this kernel's events, the buffer was never allocated
194
+ // and there is nothing to migrate.
195
+ this.#eventBuffer?.migrateEventSource(inner.anchorNode.events);
195
196
  }
196
197
 
197
198
  private createHydratedState(innerNode: HydratedFlexTreeNode): HydratedState {
@@ -233,6 +234,14 @@ export class TreeNodeKernel {
233
234
  }
234
235
 
235
236
  public get events(): Listenable<KernelEvents> {
237
+ assert(!this.disposed, 0xcfa /* Cannot register events on a disposed node */);
238
+ // Allocate the buffer on first access. See {@link TreeNodeKernel.#eventBuffer} for rationale.
239
+ if (this.#eventBuffer === undefined) {
240
+ const eventSource = isHydrated(this.#hydrationState)
241
+ ? this.#hydrationState.innerNode.anchorNode.events
242
+ : this.#hydrationState.innerNode.events;
243
+ this.#eventBuffer = new KernelEventBuffer(eventSource);
244
+ }
236
245
  return this.#eventBuffer;
237
246
  }
238
247
 
@@ -244,7 +253,7 @@ export class TreeNodeKernel {
244
253
  off();
245
254
  }
246
255
  }
247
- this.#eventBuffer.dispose();
256
+ this.#eventBuffer?.dispose();
248
257
  // TODO: go to the context and remove myself from withAnchors
249
258
  }
250
259
 
@@ -430,6 +439,8 @@ class KernelEventBuffer implements Listenable<KernelEvents> {
430
439
  }
431
440
 
432
441
  public on(eventName: keyof KernelEvents, listener: KernelEvents[typeof eventName]): Off {
442
+ this.#assertNotDisposed();
443
+
433
444
  // Lazily bind event listeners to the source.
434
445
  // If we do not have any existing listeners for this event, then we need to bind to the source.
435
446
  if (!this.#events.hasListeners(eventName)) {
@@ -446,7 +457,10 @@ class KernelEventBuffer implements Listenable<KernelEvents> {
446
457
  }
447
458
 
448
459
  this.#events.on(eventName, listener);
449
- return () => this.off(eventName, listener);
460
+ // Return a bound method instead of an arrow closure. A bound function captures
461
+ // (target, thisArg, ...boundArgs) in a fixed shape that V8 can optimize more
462
+ // uniformly than a closure that captures its lexical context.
463
+ return this.off.bind(this, eventName, listener);
450
464
  }
451
465
 
452
466
  public off(eventName: keyof KernelEvents, listener: KernelEvents[typeof eventName]): void {
@@ -0,0 +1,81 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { UsageError } from "@fluidframework/telemetry-utils/internal";
7
+
8
+ /**
9
+ * Returns the number of Unicode code points in `value`.
10
+ * @remarks
11
+ * Use this to translate a JavaScript string length (which is in UTF-16 code units) into
12
+ * the atom/code-point space used by {@link TextAsTree}.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * codePointCount(""); // 0
17
+ * codePointCount("abc"); // 3
18
+ * codePointCount("a😀b"); // 3 — emoji is one code point, but "a😀b".length === 4 (UTF-16 surrogate pair)
19
+ * ```
20
+ *
21
+ * @internal
22
+ */
23
+ export function codePointCount(value: string): number {
24
+ // Iterate instead of spreading to avoid allocating an intermediate array.
25
+ let count = 0;
26
+ for (const _ of value) {
27
+ count++;
28
+ }
29
+ return count;
30
+ }
31
+
32
+ /**
33
+ * Returns the number of UTF-16 code units occupied by the first `count` Unicode code points in `value`,
34
+ * starting at UTF-16 index `start`.
35
+ * @remarks
36
+ * Use this to translate {@link TextAsTree}-space counts (code points) into JavaScript string indices (UTF-16).
37
+ * One code point outside the Basic Multilingual Plane (e.g. most emoji) occupies two UTF-16 code units.
38
+ *
39
+ * Validates that the requested `count` code points are fully consumable from `start`; silent truncation
40
+ * would misalign delta offsets applied to strings rather than surface the drift to the caller.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * utf16LengthForCodePoints("abc", 0, 3); // 3 — three single-unit characters
45
+ * utf16LengthForCodePoints("a😀b", 0, 3); // 4 — a (1) + 😀 (2) + b (1)
46
+ * utf16LengthForCodePoints("a😀b", 1, 1); // 2 — just the emoji
47
+ * utf16LengthForCodePoints("abc", 0, 0); // 0 — no code points requested
48
+ * ```
49
+ *
50
+ * @param value - The string to measure.
51
+ * @param start - The UTF-16 index in `value` to start measuring from. Must be in `[0, value.length]`.
52
+ * @param count - The number of Unicode code points to measure. Must be non-negative, and there must
53
+ * be at least `count` code points available in `value` starting at `start`.
54
+ * @throws A {@link @fluidframework/telemetry-utils#UsageError} if `start` is out of range,
55
+ * `count` is negative, or fewer than `count` code points are available from `start`.
56
+ * @internal
57
+ */
58
+ export function utf16LengthForCodePoints(value: string, start: number, count: number): number {
59
+ if (start < 0 || start > value.length) {
60
+ throw new UsageError(
61
+ `start (${start}) must be within [0, ${value.length}] (value.length).`,
62
+ );
63
+ }
64
+ if (count < 0) {
65
+ throw new UsageError(`count (${count}) must be non-negative.`);
66
+ }
67
+ let utf16 = 0;
68
+ let counted = 0;
69
+ while (counted < count && start + utf16 < value.length) {
70
+ // Code points above 0xFFFF are encoded in UTF-16 as a surrogate pair (2 units);
71
+ // everything else takes a single UTF-16 unit.
72
+ utf16 += (value.codePointAt(start + utf16) ?? 0) > 0xffff ? 2 : 1;
73
+ counted++;
74
+ }
75
+ if (counted !== count) {
76
+ throw new UsageError(
77
+ `count (${count}) exceeds the ${counted} code point(s) available from start (${start}) in a value of length ${value.length}.`,
78
+ );
79
+ }
80
+ return utf16;
81
+ }
package/src/text/index.ts CHANGED
@@ -5,3 +5,4 @@
5
5
 
6
6
  export { TextAsTree } from "./textDomain.js";
7
7
  export { FormattedTextAsTree } from "./textDomainFormatted.js";
8
+ export { codePointCount, utf16LengthForCodePoints } from "./codePointUtils.js";