@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
@@ -25,7 +25,7 @@ import {
25
25
  type SchemaAndPolicy,
26
26
  type SchemaPolicy,
27
27
  } from "../../core/index.js";
28
- import { getOrCreate } from "../../util/index.js";
28
+ import { assertNonNegativeSafeInteger, getOrCreate } from "../../util/index.js";
29
29
  import { isStableNodeIdentifier } from "../node-identifier/index.js";
30
30
 
31
31
  import { BasicChunk } from "./basicChunk.js";
@@ -53,6 +53,7 @@ export function makeTreeChunker(
53
53
  defaultChunkPolicy.sequenceChunkInlineThreshold,
54
54
  defaultChunkPolicy.sequenceChunkInlineThreshold,
55
55
  defaultChunkPolicy.uniformChunkNodeCount,
56
+ defaultChunkPolicy.uniformChunkNodeCountDynamicTargetMax,
56
57
  (type: TreeNodeSchemaIdentifier, shapes: Map<TreeNodeSchemaIdentifier, ShapeInfo>) =>
57
58
  tryShapeFromNodeSchema(
58
59
  {
@@ -117,6 +118,7 @@ export class Chunker implements IChunker {
117
118
  public readonly sequenceChunkSplitThreshold: number,
118
119
  public readonly sequenceChunkInlineThreshold: number,
119
120
  public readonly uniformChunkNodeCount: number,
121
+ public readonly uniformChunkNodeCountDynamicTargetMax: number,
120
122
  // eslint-disable-next-line @typescript-eslint/no-shadow
121
123
  private readonly tryShapeFromNodeSchema: (
122
124
  type: TreeNodeSchemaIdentifier,
@@ -133,6 +135,7 @@ export class Chunker implements IChunker {
133
135
  this.sequenceChunkSplitThreshold,
134
136
  this.sequenceChunkInlineThreshold,
135
137
  this.uniformChunkNodeCount,
138
+ this.uniformChunkNodeCountDynamicTargetMax,
136
139
  this.tryShapeFromNodeSchema,
137
140
  );
138
141
  }
@@ -379,6 +382,7 @@ export const defaultChunkPolicy: ChunkPolicy = {
379
382
  sequenceChunkInlineThreshold: Number.POSITIVE_INFINITY,
380
383
  // Current UniformChunk handling doesn't scale well to large chunks, so set a modest size limit:
381
384
  uniformChunkNodeCount: 400,
385
+ uniformChunkNodeCountDynamicTargetMax: 25,
382
386
  // Without knowing what the schema is, all shapes are possible.
383
387
  // Use `makeTreeChunker` to do better.
384
388
  shapeFromSchema: () => polymorphic,
@@ -388,6 +392,7 @@ export const basicOnlyChunkPolicy: ChunkPolicy = {
388
392
  sequenceChunkSplitThreshold: Number.POSITIVE_INFINITY,
389
393
  sequenceChunkInlineThreshold: Number.POSITIVE_INFINITY,
390
394
  uniformChunkNodeCount: 0,
395
+ uniformChunkNodeCountDynamicTargetMax: 0,
391
396
  shapeFromSchema: () => polymorphic,
392
397
  };
393
398
 
@@ -413,6 +418,24 @@ export interface ChunkPolicy {
413
418
  */
414
419
  readonly uniformChunkNodeCount: number;
415
420
 
421
+ /**
422
+ * Target maximum top level length for a UniformChunk while a field is being edited.
423
+ *
424
+ * @remarks
425
+ * When {@link splitFieldAtIndex} has to split a chunk to land an attach/detach on a chunk
426
+ * boundary, chunks whose {@link TreeChunk.topLevelLength} exceeds this value are bisected
427
+ * recursively until each piece is at or below it, and only the piece holding the target index
428
+ * is split exactly. This bounds N splits inside an M-sized chunk at the cost of producing a
429
+ * few extra intermediate chunks.
430
+ *
431
+ * Future merge/extend logic for adjacent small chunks could use the same value as the
432
+ * upper bound it tries to stay under, so dynamic chunk sizes settle around this target.
433
+ *
434
+ * Independent of {@link ChunkPolicy.uniformChunkNodeCount}, which only bounds the size of
435
+ * chunks produced by the initial chunking pass.
436
+ */
437
+ readonly uniformChunkNodeCountDynamicTargetMax: number;
438
+
416
439
  /**
417
440
  * Returns information about the shapes trees of type `schema` can take.
418
441
  */
@@ -556,6 +579,67 @@ export function chunkRange(
556
579
 
557
580
  return output;
558
581
  }
582
+
583
+ /**
584
+ * Walks the `chunks` array of a field and splits a chunk if needed so that `nodeIndex` sits on
585
+ * a chunk boundary. After the call, inserting a chunk at the returned index would place its
586
+ * first top-level node at index `nodeIndex` when treating `chunks` as a field.
587
+ *
588
+ * @remarks
589
+ * When splitting chunks, large chunks are split evenly so that repeated calls to this method (or similar operations)
590
+ * avoid poor worst-case behavior. See {@link ChunkPolicy.uniformChunkNodeCountDynamicTargetMax} for details.
591
+ *
592
+ * @param chunks - The array of {@link TreeChunk}s for the field to split. Mutated in place.
593
+ * @param nodeIndex - The index to split at, measured in top-level nodes within the field.
594
+ * Must be in `[0, totalNodes]`, where `totalNodes` is the sum of {@link TreeChunk.topLevelLength}
595
+ * across all chunks.
596
+ * @param policy - The {@link ChunkCompressor} to use when splitting chunks and re-chunking each side
597
+ * of the split via {@link chunkRange}.
598
+ *
599
+ * @returns The index in `chunks` (after modifications made by this function) where if a chunk were inserted at that index its first top level node would have index `nodeIndex` when treating `chunks` as a field.
600
+ */
601
+ export function splitFieldAtIndex(
602
+ chunks: TreeChunk[],
603
+ nodeIndex: number,
604
+ policy: ChunkCompressor,
605
+ ): number {
606
+ assertNonNegativeSafeInteger(nodeIndex);
607
+ const bisectThreshold = policy.policy.uniformChunkNodeCountDynamicTargetMax;
608
+ let remaining = nodeIndex;
609
+ let chunkIndex = 0;
610
+ while (chunkIndex < chunks.length) {
611
+ if (remaining === 0) {
612
+ return chunkIndex;
613
+ }
614
+ const chunk = chunks[chunkIndex] ?? oob();
615
+ const total = chunk.topLevelLength;
616
+ if (remaining >= total) {
617
+ // nodeIndex is not in this chunk, so move forward one chunk and continue.
618
+ remaining -= total;
619
+ chunkIndex++;
620
+ continue;
621
+ }
622
+
623
+ // nodeIndex falls within this chunk, so split the chunk.
624
+ // This does not move the chunkIndex forward: the next iteration might need to split again at the same index.
625
+ //
626
+ // For chunks above the bisect threshold, cut at the midpoint and let the loop descend
627
+ // into whichever half holds nodeIndex. The other half is left untouched.
628
+ const splitPoint = total > bisectThreshold ? Math.floor(total / 2) : remaining;
629
+ const cursor = chunk.cursor();
630
+ cursor.firstNode();
631
+ const before = chunkRange(cursor, policy, splitPoint, false);
632
+ const after = chunkRange(cursor, policy, total - splitPoint, true);
633
+ // TODO: this could fail for really long chunks being split (due to argument count limits).
634
+ chunks.splice(chunkIndex, 1, ...before, ...after);
635
+ // The spliced-out slot held a ref to the original chunk. The two new chunks come with
636
+ // their own refs from chunkRange, so the slot's ref to the original needs to be released.
637
+ chunk.referenceRemoved();
638
+ }
639
+ assert(remaining === 0, 0xcf9 /* nodeIndex exceeds total node count in field */);
640
+ return chunks.length;
641
+ }
642
+
559
643
  /**
560
644
  * Extracts values from the current cursor position according to the provided tree shape.
561
645
  *
@@ -36,7 +36,6 @@ import {
36
36
  type DeltaDetachedNodeId,
37
37
  } from "../../core/index.js";
38
38
  import {
39
- assertValidRange,
40
39
  brand,
41
40
  getLast,
42
41
  getOrAddEmptyToMap,
@@ -45,7 +44,14 @@ import {
45
44
  } from "../../util/index.js";
46
45
 
47
46
  import { BasicChunk, BasicChunkCursor, type SiblingsOrKey } from "./basicChunk.js";
48
- import { type IChunker, basicChunkTree, chunkField, chunkTree } from "./chunkTree.js";
47
+ import {
48
+ type ChunkCompressor,
49
+ type IChunker,
50
+ basicChunkTree,
51
+ chunkField,
52
+ chunkTree,
53
+ splitFieldAtIndex,
54
+ } from "./chunkTree.js";
49
55
 
50
56
  function makeRoot(): BasicChunk {
51
57
  return new BasicChunk(aboveRootPlaceholder, new Map());
@@ -173,8 +179,12 @@ export class ChunkedForest implements IEditableForest {
173
179
 
174
180
  const parent = this.getParent();
175
181
  const destinationField = getOrAddEmptyToMap(parent.mutableChunk.fields, parent.key);
182
+ const destinationChunkIndex = splitFieldAtIndex(destinationField, destination, {
183
+ policy: this.forest.chunker,
184
+ idCompressor: this.forest.idCompressor,
185
+ });
176
186
  // TODO: this will fail for very large moves due to argument limits.
177
- destinationField.splice(destination, 0, ...sourceField);
187
+ destinationField.splice(destinationChunkIndex, 0, ...sourceField);
178
188
  },
179
189
  /**
180
190
  * Detaches the range from the current field and transfers it to the given destination if any.
@@ -193,9 +203,19 @@ export class ChunkedForest implements IEditableForest {
193
203
  this.forest.#events.emit("beforeChange");
194
204
  const parent = this.getParent();
195
205
  const sourceField = parent.mutableChunk.fields.get(parent.key) ?? [];
196
-
197
- assertValidRange(source, sourceField);
198
- const newField = sourceField.splice(source.start, source.end - source.start);
206
+ assert(source.start <= source.end, 0xcf8 /* detach range start must not exceed end */);
207
+
208
+ const policy: ChunkCompressor = {
209
+ policy: this.forest.chunker,
210
+ idCompressor: this.forest.idCompressor,
211
+ };
212
+ // Split start first: splitting end later only expands chunks at positions >= startChunkIndex,
213
+ // which leaves startChunkIndex valid. The reverse order would shift endChunkIndex when
214
+ // source.start and source.end land in different chunks.
215
+ // Performance: It's practical to have a variant of splitFieldAtIndex which can split at multiple locations in a single pass if the performance of this becomes worth optimizing.
216
+ const startChunkIndex = splitFieldAtIndex(sourceField, source.start, policy);
217
+ const endChunkIndex = splitFieldAtIndex(sourceField, source.end, policy);
218
+ const newField = sourceField.splice(startChunkIndex, endChunkIndex - startChunkIndex);
199
219
 
200
220
  if (destination === undefined) {
201
221
  for (const child of newField) {
@@ -225,12 +245,12 @@ export class ChunkedForest implements IEditableForest {
225
245
  let indexOfChunk = 0;
226
246
  let chunk = chunks[indexOfChunk] ?? oob();
227
247
  while (indexWithinChunk >= chunk.topLevelLength) {
228
- chunk = chunks[indexOfChunk] ?? oob();
229
248
  indexWithinChunk -= chunk.topLevelLength;
230
249
  indexOfChunk++;
231
250
  if (indexOfChunk === chunks.length) {
232
251
  fail(0xaf7 /* missing edited node */);
233
252
  }
253
+ chunk = chunks[indexOfChunk] ?? oob();
234
254
  }
235
255
  let found = chunks[indexOfChunk] ?? oob();
236
256
  if (!(found instanceof BasicChunk)) {
@@ -9,6 +9,7 @@ import type {
9
9
  OpSpaceCompressedId,
10
10
  SessionId,
11
11
  } from "@fluidframework/id-compressor";
12
+ import { isFinalId } from "@fluidframework/id-compressor/internal";
12
13
  import { v5 as uuidV5 } from "uuid";
13
14
 
14
15
  import { DiscriminatedUnionDispatcher } from "../../../codec/index.js";
@@ -42,7 +43,7 @@ import {
42
43
  import type { FieldBatchEncodingContext, IncrementalDecoder } from "./codecs.js";
43
44
  import {
44
45
  type EncodedAnyShape,
45
- type EncodedChunkShapeV1OrV2,
46
+ type EncodedChunkShape,
46
47
  type EncodedChunkShapeV2,
47
48
  type EncodedFieldBatchV1OrV2,
48
49
  type EncodedFieldBatchV2,
@@ -50,7 +51,9 @@ import {
50
51
  type EncodedInlineArrayShape,
51
52
  type EncodedNestedArrayShape,
52
53
  type EncodedNodeShape,
54
+ type EncodedSpecializedNodeShape,
53
55
  type EncodedValueShape,
56
+ type ShapeIndex,
54
57
  SpecialField,
55
58
  supportsIncrementalEncoding,
56
59
  } from "./format/index.js";
@@ -97,9 +100,137 @@ export function decode(
97
100
  );
98
101
  }
99
102
 
103
+ /**
104
+ * Resolves `shapeIndex` to a fully-resolved {@link EncodedNodeShape}, normalizing away any
105
+ * specialized node shapes (`f`) along the way by applying their overlays via
106
+ * {@link applySpecialization} until a concrete node shape is reached.
107
+ *
108
+ * @param input - The index of the shape to resolve, which must be a concrete or specialized node shape.
109
+ * @param context - The decoding context containing the shape definitions.
110
+ * @param pendingResolution - (Internal) A set of shape indices visited so far in the current resolution chain, used to detect cycles in the specialization chain. Most callers should not provide this argument.
111
+ *
112
+ * @remarks
113
+ * Exported for testing.
114
+ */
115
+ export function normalizeToNodeShape(
116
+ input: EncodedNodeShape | EncodedSpecializedNodeShape,
117
+ context: DecoderContext<EncodedChunkShape>,
118
+ pendingResolution: Set<ShapeIndex> = new Set(),
119
+ ): EncodedNodeShape {
120
+ if (!("base" in input)) {
121
+ return input;
122
+ }
123
+
124
+ const baseIndex = input.base;
125
+ assert(!pendingResolution.has(baseIndex), 0xcfb /* cyclic specialized node shape chain */);
126
+ pendingResolution.add(baseIndex);
127
+ const encoded = context.shapes[baseIndex];
128
+ assert(encoded !== undefined, 0xcfc /* shape index out of bounds */);
129
+
130
+ const baseShape = encoded.c ?? ("f" in encoded ? encoded.f : undefined);
131
+ assert(
132
+ baseShape !== undefined,
133
+ 0xcfd /* shape at index must be a concrete (c) or specialized (f) node shape */,
134
+ );
135
+
136
+ return applySpecialization(
137
+ normalizeToNodeShape(baseShape, context, pendingResolution),
138
+ input,
139
+ context,
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Produces a specialized {@link EncodedNodeShape} by overlaying `overrides` onto `base`.
145
+ *
146
+ * See {@link EncodedSpecializedNodeShape} for the override/inherit/clear semantics.
147
+ *
148
+ * @remarks
149
+ * Exported for testing.
150
+ */
151
+ export function applySpecialization(
152
+ base: EncodedNodeShape,
153
+ overrides: EncodedSpecializedNodeShape,
154
+ context: DecoderContext<EncodedChunkShape>,
155
+ ): EncodedNodeShape {
156
+ const fields = [...(base.fields ?? [])];
157
+ const indexFromKey = new Map<FieldKey, number>();
158
+ for (const [i, [keyEncoded]] of fields.entries()) {
159
+ const key = context.identifier<FieldKey>(keyEncoded);
160
+ assert(!indexFromKey.has(key), 0xcfe /* duplicate field key in base node shape */);
161
+ indexFromKey.set(key, i);
162
+ }
163
+
164
+ // Replace fields in base with overrides, append new keys in overrides in the order they are specified.
165
+ const seenOverrideKeys = new Set<FieldKey>();
166
+ for (const [keyEncoded, shapeIndex] of overrides.fields ?? []) {
167
+ const key = context.identifier<FieldKey>(keyEncoded);
168
+ assert(
169
+ !seenOverrideKeys.has(key),
170
+ 0xcff /* duplicate field key in specialized node shape */,
171
+ );
172
+ seenOverrideKeys.add(key);
173
+ const existingIndex = indexFromKey.get(key);
174
+ if (existingIndex === undefined) {
175
+ fields.push([keyEncoded, shapeIndex]);
176
+ } else {
177
+ const index = fields[existingIndex];
178
+ assert(index !== undefined, 0xd00 /* expected existing field index */);
179
+ fields[existingIndex] = [index[0], shapeIndex];
180
+ }
181
+ }
182
+
183
+ return {
184
+ type: base.type,
185
+ value: resolveOverride(overrides.value, base.value),
186
+ fields: fields.length > 0 ? fields : undefined,
187
+ extraFields: resolveOverride(overrides.extraFields, base.extraFields),
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Resolves an override against a base value.
193
+ *
194
+ * @param override - `undefined` means the override is absent (inherit from base); `null` is the
195
+ * explicit-clear sentinel needed because JSON.stringify drops `undefined`-valued properties, making
196
+ * property-presence indistinguishable from absent on the wire.
197
+ * @param baseValue - The value to inherit when the override is absent.
198
+ */
199
+ function resolveOverride<T>(
200
+ // eslint-disable-next-line @rushstack/no-new-null
201
+ override: T | null | undefined,
202
+ baseValue: T | undefined,
203
+ ): T | undefined {
204
+ if (override === undefined) {
205
+ return baseValue;
206
+ }
207
+ if (override === null) {
208
+ return undefined;
209
+ }
210
+ return override;
211
+ }
212
+
213
+ /**
214
+ * Decoder for {@link EncodedSpecializedNodeShape}s.
215
+ * Applies the specialization's field overrides to the resolved base node shape, then delegates
216
+ * to a {@link NodeDecoder} built from the resulting shape.
217
+ */
218
+ export class SpecializedNodeDecoder implements ChunkDecoder {
219
+ private readonly inner: NodeDecoder;
220
+ public constructor(
221
+ shape: EncodedSpecializedNodeShape,
222
+ context: DecoderContext<EncodedChunkShape>,
223
+ ) {
224
+ this.inner = new NodeDecoder(normalizeToNodeShape(shape, context), context);
225
+ }
226
+ public decode(decoders: readonly ChunkDecoder[], stream: StreamCursor): TreeChunk {
227
+ return this.inner.decode(decoders, stream);
228
+ }
229
+ }
230
+
100
231
  const decoderLibrary = new DiscriminatedUnionDispatcher<
101
- EncodedChunkShapeV1OrV2,
102
- [context: DecoderContext<EncodedChunkShapeV1OrV2>],
232
+ EncodedChunkShape,
233
+ [context: DecoderContext<EncodedChunkShape>],
103
234
  ChunkDecoder
104
235
  >({
105
236
  a(shape: EncodedNestedArrayShape, context): ChunkDecoder {
@@ -120,6 +251,9 @@ const decoderLibrary = new DiscriminatedUnionDispatcher<
120
251
  ): ChunkDecoder {
121
252
  return new IncrementalChunkDecoder(context);
122
253
  },
254
+ f(shape: EncodedSpecializedNodeShape, context): ChunkDecoder {
255
+ return new SpecializedNodeDecoder(shape, context);
256
+ },
123
257
  });
124
258
 
125
259
  /**
@@ -153,8 +287,10 @@ export function readValue(
153
287
  const idCompressor = idDecodingContext.idCompressor;
154
288
  // OpSpaceCompressedIds are negative, and require a session-id to compute their value.
155
289
  // Due to a bug, we have some special casing for them (see below).
156
- // TODO: isFinalId should probably be exported from id-compressor and that could be used to do the narrowing here.
157
- if (idDecodingContext.isSummary === true && streamValue < 0) {
290
+ if (
291
+ idDecodingContext.isSummary === true &&
292
+ !isFinalId(streamValue as OpSpaceCompressedId)
293
+ ) {
158
294
  if (
159
295
  idDecodingContext.healUnresolvableIdentifiersOnDecode === true &&
160
296
  idDecodingContext.sharedObjectId !== undefined
@@ -349,7 +485,7 @@ type BasicFieldDecoder = (
349
485
  * Get a decoder for fields of a provided (via `shape` and `context`).
350
486
  */
351
487
  function fieldDecoder(
352
- context: DecoderContext<EncodedChunkShapeV1OrV2>,
488
+ context: DecoderContext<EncodedChunkShape>,
353
489
  key: FieldKey,
354
490
  shape: number,
355
491
  ): BasicFieldDecoder {
@@ -368,7 +504,7 @@ export class NodeDecoder implements ChunkDecoder {
368
504
  private readonly fieldDecoders: readonly BasicFieldDecoder[];
369
505
  public constructor(
370
506
  private readonly shape: EncodedNodeShape,
371
- private readonly context: DecoderContext<EncodedChunkShapeV1OrV2>,
507
+ private readonly context: DecoderContext<EncodedChunkShape>,
372
508
  ) {
373
509
  this.type = shape.type === undefined ? undefined : context.identifier(shape.type);
374
510
 
@@ -9,8 +9,9 @@ import { lowestMinVersionForCollab } from "@fluidframework/runtime-utils/interna
9
9
  import type { TSchema } from "@sinclair/typebox";
10
10
 
11
11
  import {
12
- ClientVersionDispatchingCodecBuilder,
12
+ VersionDispatchingCodecBuilder,
13
13
  type CodecAndSchema,
14
+ type VersionDispatchingCodec,
14
15
  FluidClientVersion,
15
16
  } from "../../../codec/index.js";
16
17
  import {
@@ -138,7 +139,11 @@ export interface FieldBatchEncodingContext {
138
139
  * @remarks
139
140
  * Fields in this batch currently don't have field schema for the root, which limits optimizations.
140
141
  */
141
- export type FieldBatchCodec = ReturnType<typeof fieldBatchCodecBuilder.build>;
142
+ export type FieldBatchCodec = VersionDispatchingCodec<
143
+ FieldBatch,
144
+ FieldBatchEncodingContext,
145
+ FieldBatchFormatVersion
146
+ >;
142
147
 
143
148
  /**
144
149
  * Creates the encode/decode functions for a specific FieldBatch format version.
@@ -231,30 +236,27 @@ function makeFieldBatchCodecForVersion(
231
236
  }
232
237
 
233
238
  /**
234
- * {@link ClientVersionDispatchingCodecBuilder} for field batch codecs.
239
+ * {@link VersionDispatchingCodecBuilder} for field batch codecs.
235
240
  */
236
- export const fieldBatchCodecBuilder = ClientVersionDispatchingCodecBuilder.build(
237
- "FieldBatch",
238
- [
239
- {
240
- minVersionForCollab: lowestMinVersionForCollab,
241
- formatVersion: FieldBatchFormatVersion.v1,
242
- codec: makeFieldBatchCodecForVersion(
243
- FieldBatchFormatVersion.v1,
244
- uncompressedEncodeV1,
245
- schemaCompressedEncodeV1,
246
- EncodedFieldBatchV1,
247
- ),
248
- },
249
- {
250
- minVersionForCollab: FluidClientVersion.v2_73,
251
- formatVersion: FieldBatchFormatVersion.v2,
252
- codec: makeFieldBatchCodecForVersion(
253
- FieldBatchFormatVersion.v2,
254
- uncompressedEncodeV2,
255
- schemaCompressedEncodeV2,
256
- EncodedFieldBatchV2,
257
- ),
258
- },
259
- ],
260
- );
241
+ export const fieldBatchCodecBuilder = VersionDispatchingCodecBuilder.build("FieldBatch", [
242
+ {
243
+ minVersionForCollab: lowestMinVersionForCollab,
244
+ formatVersion: FieldBatchFormatVersion.v1,
245
+ codec: makeFieldBatchCodecForVersion(
246
+ FieldBatchFormatVersion.v1,
247
+ uncompressedEncodeV1,
248
+ schemaCompressedEncodeV1,
249
+ EncodedFieldBatchV1,
250
+ ),
251
+ },
252
+ {
253
+ minVersionForCollab: FluidClientVersion.v2_73,
254
+ formatVersion: FieldBatchFormatVersion.v2,
255
+ codec: makeFieldBatchCodecForVersion(
256
+ FieldBatchFormatVersion.v2,
257
+ uncompressedEncodeV2,
258
+ schemaCompressedEncodeV2,
259
+ EncodedFieldBatchV2,
260
+ ),
261
+ },
262
+ ]);
@@ -32,7 +32,7 @@ import type { FieldBatch } from "./fieldBatch.js";
32
32
  import {
33
33
  type EncodedAnyShape,
34
34
  type EncodedChunkShapeV1,
35
- type EncodedChunkShapeV1OrV2,
35
+ type EncodedChunkShape,
36
36
  type EncodedChunkShapeV2,
37
37
  type EncodedFieldBatchV1OrV2,
38
38
  type EncodedNestedArrayShape,
@@ -64,8 +64,8 @@ export function compressedEncode(
64
64
  return updateShapesAndIdentifiersEncoding(context.version, batchBuffer);
65
65
  }
66
66
 
67
- export type BufferFormat = BufferFormatGeneric<EncodedChunkShapeV1OrV2>;
68
- export type Shape = ShapeGeneric<EncodedChunkShapeV1OrV2>;
67
+ export type BufferFormat = BufferFormatGeneric<EncodedChunkShape>;
68
+ export type Shape = ShapeGeneric<EncodedChunkShape>;
69
69
 
70
70
  /**
71
71
  * Like {@link FieldEncoder}, except data will be prefixed with the key.
@@ -167,7 +167,7 @@ export function asNodesEncoder(encoder: NodeEncoder): NodesEncoder {
167
167
  /**
168
168
  * Encodes a chunk with {@link EncodedAnyShape} by prefixing the data with its shape.
169
169
  */
170
- export class AnyShape extends ShapeGeneric<EncodedChunkShapeV1OrV2> {
170
+ export class AnyShape extends ShapeGeneric<EncodedChunkShape> {
171
171
  private constructor() {
172
172
  super();
173
173
  }
@@ -272,7 +272,7 @@ export const anyFieldEncoder: FieldEncoder = {
272
272
  * which is an easy way to keep all the related code together without extra objects.
273
273
  */
274
274
  export class InlineArrayEncoder
275
- extends ShapeGeneric<EncodedChunkShapeV1OrV2>
275
+ extends ShapeGeneric<EncodedChunkShape>
276
276
  implements NodesEncoder, FieldEncoder
277
277
  {
278
278
  public static readonly empty: InlineArrayEncoder = new InlineArrayEncoder(0, {
@@ -356,7 +356,7 @@ export class InlineArrayEncoder
356
356
  /**
357
357
  * Encodes the shape for a nested array as {@link EncodedNestedArrayShape} shape.
358
358
  */
359
- export class NestedArrayShape extends ShapeGeneric<EncodedChunkShapeV1OrV2> {
359
+ export class NestedArrayShape extends ShapeGeneric<EncodedChunkShape> {
360
360
  /**
361
361
  * @param innerShape - The shape of each item in this nested array.
362
362
  */
@@ -367,7 +367,7 @@ export class NestedArrayShape extends ShapeGeneric<EncodedChunkShapeV1OrV2> {
367
367
  public encodeShape(
368
368
  identifiers: DeduplicationTable<string>,
369
369
  shapes: DeduplicationTable<Shape>,
370
- ): EncodedChunkShapeV1OrV2 {
370
+ ): EncodedChunkShape {
371
371
  const shape: EncodedNestedArrayShape =
372
372
  shapes.valueToIndex.get(this.innerShape) ??
373
373
  fail(0xb4f /* index for shape not found in table */);
@@ -16,16 +16,20 @@ import { shapesV1 } from "./formatV1.js";
16
16
  export type EncodedIncrementalChunkShape = Static<typeof EncodedIncrementalChunkShape>;
17
17
  export const EncodedIncrementalChunkShape = Type.Literal(0);
18
18
 
19
+ /**
20
+ * The chunk shapes supported by the V2 format.
21
+ * @remarks
22
+ * See {@link EncodedChunkShapeV2}.
23
+ */
24
+ export const shapesV2 = {
25
+ ...shapesV1,
26
+ e: Type.Optional(EncodedIncrementalChunkShape),
27
+ } as const;
28
+
19
29
  /**
20
30
  * V2 extension of {@link EncodedChunkShapeV1}.
21
31
  * @remarks
22
32
  * See {@link DiscriminatedUnionDispatcher} for more information on this pattern.
23
33
  */
24
34
  export type EncodedChunkShapeV2 = Static<typeof EncodedChunkShapeV2>;
25
- export const EncodedChunkShapeV2 = Type.Object(
26
- {
27
- ...shapesV1,
28
- e: Type.Optional(EncodedIncrementalChunkShape),
29
- },
30
- unionOptions,
31
- );
35
+ export const EncodedChunkShapeV2 = Type.Object(shapesV2, unionOptions);
@@ -0,0 +1,83 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { type Static, Type } from "@sinclair/typebox";
7
+
8
+ import { unionOptions } from "../../../../codec/index.js";
9
+
10
+ import { ShapeIndex } from "./formatGeneric.js";
11
+ import { EncodedFieldShape, EncodedValueShape } from "./formatV1.js";
12
+ import { shapesV2 } from "./formatV2.js";
13
+
14
+ /**
15
+ * A node shape that derives from another node shape by overlaying property-level overrides.
16
+ *
17
+ * @remarks
18
+ * Compresses runs of node shapes that differ only in a few properties: a base node shape
19
+ * defines the structural skeleton, and the specialization narrows specific properties.
20
+ *
21
+ * For example, a base `FormatNode` with a variable-boolean `bold` field can be specialized
22
+ * to a shape that pins `bold` to a constant `true` — every node decoded with the
23
+ * specialization contributes zero stream tokens for `bold`.
24
+ *
25
+ * Specialization rules: `type` is always inherited from the resolved base. `fields` overrides
26
+ * apply per-key: entries whose key matches a base field replace that entry's shape index in
27
+ * place; entries with new keys are appended after all base fields. For `value` and
28
+ * `extraFields`: if the property is absent on the wire, the base's value is inherited; if
29
+ * `null`, the resulting shape has no value / no extraFields (explicit clear); any other value
30
+ * replaces the base's.
31
+ *
32
+ * The `null` sentinel exists because JSON does not preserve `undefined`-valued properties,
33
+ * so override-vs-inherit cannot be discriminated by property presence after persistence.
34
+ *
35
+ * Decoded by {@link applySpecialization}.
36
+ */
37
+ export type EncodedSpecializedNodeShape = Static<typeof EncodedSpecializedNodeShape>;
38
+ export const EncodedSpecializedNodeShape = Type.Object(
39
+ {
40
+ /**
41
+ * Index into the enclosing batch's shapes array of the shape this specializes.
42
+ * Must resolve to either an {@link EncodedNodeShape} or another
43
+ * `EncodedSpecializedNodeShape`; chains are followed transitively until a node shape
44
+ * is reached. This restriction is enforced at runtime, not by the schema.
45
+ */
46
+ base: ShapeIndex,
47
+ /**
48
+ * Field-level overrides applied to the resolved base's `fields`. Entries whose key
49
+ * matches a base field replace that field's shape index in place; entries with new
50
+ * keys are appended after all base fields, in the order given here. Base field order
51
+ * is preserved — this is the stream consumption order at decode time, so encoders
52
+ * must serialize per-field tokens in the resulting field order, not in this list's order.
53
+ */
54
+ fields: Type.Optional(Type.Array(EncodedFieldShape)),
55
+ /**
56
+ * If absent, inherits the resolved base's value shape. If `null`, the resulting shape
57
+ * has no value shape (explicit clear). Any other value replaces the base's.
58
+ */
59
+ value: Type.Optional(Type.Union([EncodedValueShape, Type.Null()])),
60
+ /**
61
+ * If absent, inherits the resolved base's extraFields shape. If `null`, the resulting
62
+ * shape has no extraFields (explicit clear). Any other value replaces the base's.
63
+ */
64
+ extraFields: Type.Optional(Type.Union([ShapeIndex, Type.Null()])),
65
+ },
66
+ { additionalProperties: false },
67
+ );
68
+
69
+ /**
70
+ * Experimental extension of {@link EncodedChunkShapeV2}.
71
+ * @remarks
72
+ * See {@link DiscriminatedUnionDispatcher} for more information on this pattern.
73
+ */
74
+ export type EncodedChunkShapeVTextExperimental = Static<
75
+ typeof EncodedChunkShapeVTextExperimental
76
+ >;
77
+ export const EncodedChunkShapeVTextExperimental = Type.Object(
78
+ {
79
+ ...shapesV2,
80
+ f: Type.Optional(EncodedSpecializedNodeShape),
81
+ },
82
+ unionOptions,
83
+ );
@@ -16,14 +16,19 @@ export {
16
16
  SpecialField,
17
17
  } from "./formatV1.js";
18
18
  export { EncodedIncrementalChunkShape, EncodedChunkShapeV2 } from "./formatV2.js";
19
+ export {
20
+ EncodedChunkShapeVTextExperimental,
21
+ EncodedSpecializedNodeShape,
22
+ } from "./formatVText.js";
19
23
  export {
20
24
  FieldBatchFormatVersion,
21
25
  EncodedFieldBatchV1,
22
26
  EncodedFieldBatchV2,
27
+ EncodedFieldBatchVTextExperimental,
23
28
  supportsIncrementalEncoding,
24
29
  type EncodedFieldBatchV1OrV2,
25
30
  type EncodedFieldBatchV1AndV2,
26
- type EncodedChunkShapeV1OrV2,
31
+ type EncodedChunkShape,
27
32
  } from "./versions.js";
28
33
  export type {
29
34
  ShapeIndex,