@fluidframework/tree 2.82.0 → 2.83.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 (281) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +33 -5
  3. package/api-report/tree.alpha.api.md +25 -21
  4. package/api-report/tree.beta.api.md +14 -2
  5. package/api-report/tree.legacy.beta.api.md +14 -2
  6. package/api-report/tree.legacy.public.api.md +1 -1
  7. package/api-report/tree.public.api.md +1 -1
  8. package/dist/alpha.d.ts +3 -3
  9. package/dist/beta.d.ts +1 -0
  10. package/dist/codec/codec.d.ts +3 -39
  11. package/dist/codec/codec.d.ts.map +1 -1
  12. package/dist/codec/codec.js +5 -50
  13. package/dist/codec/codec.js.map +1 -1
  14. package/dist/codec/index.d.ts +1 -1
  15. package/dist/codec/index.d.ts.map +1 -1
  16. package/dist/codec/index.js +1 -2
  17. package/dist/codec/index.js.map +1 -1
  18. package/dist/codec/versioned/codec.d.ts +20 -7
  19. package/dist/codec/versioned/codec.d.ts.map +1 -1
  20. package/dist/codec/versioned/codec.js +56 -30
  21. package/dist/codec/versioned/codec.js.map +1 -1
  22. package/dist/core/tree/detachedFieldIndexCodecs.d.ts.map +1 -1
  23. package/dist/core/tree/detachedFieldIndexCodecs.js +6 -4
  24. package/dist/core/tree/detachedFieldIndexCodecs.js.map +1 -1
  25. package/dist/extensibleUnionNode.d.ts +97 -0
  26. package/dist/extensibleUnionNode.d.ts.map +1 -0
  27. package/dist/{extensibleSchemaUnion.js → extensibleUnionNode.js} +28 -18
  28. package/dist/extensibleUnionNode.js.map +1 -0
  29. package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts +1 -1
  30. package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
  31. package/dist/feature-libraries/chunked-forest/codec/codecs.js +4 -4
  32. package/dist/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
  33. package/dist/feature-libraries/forest-summary/codec.d.ts.map +1 -1
  34. package/dist/feature-libraries/forest-summary/codec.js +7 -1
  35. package/dist/feature-libraries/forest-summary/codec.js.map +1 -1
  36. package/dist/feature-libraries/forest-summary/formatCommon.d.ts +3 -3
  37. package/dist/feature-libraries/forest-summary/formatCommon.d.ts.map +1 -1
  38. package/dist/feature-libraries/forest-summary/formatCommon.js.map +1 -1
  39. package/dist/feature-libraries/forest-summary/formatV1.d.ts +2 -3
  40. package/dist/feature-libraries/forest-summary/formatV1.d.ts.map +1 -1
  41. package/dist/feature-libraries/forest-summary/formatV1.js +1 -2
  42. package/dist/feature-libraries/forest-summary/formatV1.js.map +1 -1
  43. package/dist/feature-libraries/forest-summary/formatV2.d.ts +2 -3
  44. package/dist/feature-libraries/forest-summary/formatV2.d.ts.map +1 -1
  45. package/dist/feature-libraries/forest-summary/formatV2.js +1 -2
  46. package/dist/feature-libraries/forest-summary/formatV2.js.map +1 -1
  47. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts +2 -2
  48. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
  49. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js +4 -4
  50. package/dist/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
  51. package/dist/feature-libraries/schema-index/codec.d.ts.map +1 -1
  52. package/dist/feature-libraries/schema-index/codec.js +6 -4
  53. package/dist/feature-libraries/schema-index/codec.js.map +1 -1
  54. package/dist/index.d.ts +2 -2
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +4 -4
  57. package/dist/index.js.map +1 -1
  58. package/dist/legacy.d.ts +1 -0
  59. package/dist/packageVersion.d.ts +1 -1
  60. package/dist/packageVersion.js +1 -1
  61. package/dist/packageVersion.js.map +1 -1
  62. package/dist/shared-tree/sharedTreeChangeCodecs.js +1 -1
  63. package/dist/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
  64. package/dist/shared-tree/tree.d.ts +1 -1
  65. package/dist/shared-tree/tree.js.map +1 -1
  66. package/dist/shared-tree/treeAlpha.d.ts +1 -1
  67. package/dist/shared-tree/treeAlpha.js.map +1 -1
  68. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  69. package/dist/shared-tree/treeCheckout.js +2 -4
  70. package/dist/shared-tree/treeCheckout.js.map +1 -1
  71. package/dist/shared-tree-core/editManagerCodecsCommons.d.ts +3 -3
  72. package/dist/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
  73. package/dist/shared-tree-core/editManagerCodecsCommons.js +2 -2
  74. package/dist/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
  75. package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts +2 -2
  76. package/dist/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -1
  77. package/dist/shared-tree-core/editManagerCodecsV1toV4.js +1 -1
  78. package/dist/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -1
  79. package/dist/shared-tree-core/editManagerCodecsVSharedBranches.d.ts +2 -2
  80. package/dist/shared-tree-core/editManagerCodecsVSharedBranches.d.ts.map +1 -1
  81. package/dist/shared-tree-core/editManagerCodecsVSharedBranches.js +1 -1
  82. package/dist/shared-tree-core/editManagerCodecsVSharedBranches.js.map +1 -1
  83. package/dist/shared-tree-core/messageCodecs.d.ts.map +1 -1
  84. package/dist/shared-tree-core/messageCodecs.js +2 -2
  85. package/dist/shared-tree-core/messageCodecs.js.map +1 -1
  86. package/dist/simple-tree/api/index.d.ts +1 -1
  87. package/dist/simple-tree/api/index.d.ts.map +1 -1
  88. package/dist/simple-tree/api/index.js +2 -2
  89. package/dist/simple-tree/api/index.js.map +1 -1
  90. package/dist/simple-tree/api/snapshotCompatibilityChecker.d.ts +148 -29
  91. package/dist/simple-tree/api/snapshotCompatibilityChecker.d.ts.map +1 -1
  92. package/dist/simple-tree/api/snapshotCompatibilityChecker.js +180 -99
  93. package/dist/simple-tree/api/snapshotCompatibilityChecker.js.map +1 -1
  94. package/dist/simple-tree/api/tree.d.ts +1 -1
  95. package/dist/simple-tree/api/tree.js.map +1 -1
  96. package/dist/simple-tree/api/treeBeta.d.ts +1 -1
  97. package/dist/simple-tree/api/treeBeta.js.map +1 -1
  98. package/dist/simple-tree/core/allowedTypes.d.ts +1 -1
  99. package/dist/simple-tree/core/allowedTypes.js.map +1 -1
  100. package/dist/simple-tree/core/unhydratedFlexTree.d.ts +1 -0
  101. package/dist/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  102. package/dist/simple-tree/core/unhydratedFlexTree.js +29 -0
  103. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  104. package/dist/simple-tree/index.d.ts +1 -1
  105. package/dist/simple-tree/index.d.ts.map +1 -1
  106. package/dist/simple-tree/index.js +2 -2
  107. package/dist/simple-tree/index.js.map +1 -1
  108. package/dist/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
  109. package/dist/simple-tree/node-kinds/array/arrayNode.js +4 -13
  110. package/dist/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  111. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.d.ts.map +1 -1
  112. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.js +33 -7
  113. package/dist/simple-tree/unhydratedFlexTreeFromInsertable.js.map +1 -1
  114. package/dist/text/textDomainFormatted.d.ts +3 -3
  115. package/dist/text/textDomainFormatted.d.ts.map +1 -1
  116. package/dist/text/textDomainFormatted.js +48 -32
  117. package/dist/text/textDomainFormatted.js.map +1 -1
  118. package/dist/util/bTreeUtils.d.ts.map +1 -1
  119. package/dist/util/bTreeUtils.js +6 -6
  120. package/dist/util/bTreeUtils.js.map +1 -1
  121. package/dist/util/rangeMap.d.ts.map +1 -1
  122. package/dist/util/rangeMap.js +5 -6
  123. package/dist/util/rangeMap.js.map +1 -1
  124. package/lib/alpha.d.ts +3 -3
  125. package/lib/beta.d.ts +1 -0
  126. package/lib/codec/codec.d.ts +3 -39
  127. package/lib/codec/codec.d.ts.map +1 -1
  128. package/lib/codec/codec.js +4 -47
  129. package/lib/codec/codec.js.map +1 -1
  130. package/lib/codec/index.d.ts +1 -1
  131. package/lib/codec/index.d.ts.map +1 -1
  132. package/lib/codec/index.js +1 -1
  133. package/lib/codec/index.js.map +1 -1
  134. package/lib/codec/versioned/codec.d.ts +20 -7
  135. package/lib/codec/versioned/codec.d.ts.map +1 -1
  136. package/lib/codec/versioned/codec.js +59 -33
  137. package/lib/codec/versioned/codec.js.map +1 -1
  138. package/lib/core/tree/detachedFieldIndexCodecs.d.ts.map +1 -1
  139. package/lib/core/tree/detachedFieldIndexCodecs.js +6 -4
  140. package/lib/core/tree/detachedFieldIndexCodecs.js.map +1 -1
  141. package/lib/extensibleUnionNode.d.ts +97 -0
  142. package/lib/extensibleUnionNode.d.ts.map +1 -0
  143. package/lib/{extensibleSchemaUnion.js → extensibleUnionNode.js} +28 -18
  144. package/lib/extensibleUnionNode.js.map +1 -0
  145. package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts +1 -1
  146. package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
  147. package/lib/feature-libraries/chunked-forest/codec/codecs.js +5 -5
  148. package/lib/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
  149. package/lib/feature-libraries/forest-summary/codec.d.ts.map +1 -1
  150. package/lib/feature-libraries/forest-summary/codec.js +8 -2
  151. package/lib/feature-libraries/forest-summary/codec.js.map +1 -1
  152. package/lib/feature-libraries/forest-summary/formatCommon.d.ts +3 -3
  153. package/lib/feature-libraries/forest-summary/formatCommon.d.ts.map +1 -1
  154. package/lib/feature-libraries/forest-summary/formatCommon.js.map +1 -1
  155. package/lib/feature-libraries/forest-summary/formatV1.d.ts +2 -3
  156. package/lib/feature-libraries/forest-summary/formatV1.d.ts.map +1 -1
  157. package/lib/feature-libraries/forest-summary/formatV1.js +1 -2
  158. package/lib/feature-libraries/forest-summary/formatV1.js.map +1 -1
  159. package/lib/feature-libraries/forest-summary/formatV2.d.ts +2 -3
  160. package/lib/feature-libraries/forest-summary/formatV2.d.ts.map +1 -1
  161. package/lib/feature-libraries/forest-summary/formatV2.js +1 -2
  162. package/lib/feature-libraries/forest-summary/formatV2.js.map +1 -1
  163. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts +2 -2
  164. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.d.ts.map +1 -1
  165. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js +4 -4
  166. package/lib/feature-libraries/modular-schema/modularChangeCodecV1.js.map +1 -1
  167. package/lib/feature-libraries/schema-index/codec.d.ts.map +1 -1
  168. package/lib/feature-libraries/schema-index/codec.js +6 -4
  169. package/lib/feature-libraries/schema-index/codec.js.map +1 -1
  170. package/lib/index.d.ts +2 -2
  171. package/lib/index.d.ts.map +1 -1
  172. package/lib/index.js +2 -2
  173. package/lib/index.js.map +1 -1
  174. package/lib/legacy.d.ts +1 -0
  175. package/lib/packageVersion.d.ts +1 -1
  176. package/lib/packageVersion.js +1 -1
  177. package/lib/packageVersion.js.map +1 -1
  178. package/lib/shared-tree/sharedTreeChangeCodecs.js +1 -1
  179. package/lib/shared-tree/sharedTreeChangeCodecs.js.map +1 -1
  180. package/lib/shared-tree/tree.d.ts +1 -1
  181. package/lib/shared-tree/tree.js.map +1 -1
  182. package/lib/shared-tree/treeAlpha.d.ts +1 -1
  183. package/lib/shared-tree/treeAlpha.js.map +1 -1
  184. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  185. package/lib/shared-tree/treeCheckout.js +2 -4
  186. package/lib/shared-tree/treeCheckout.js.map +1 -1
  187. package/lib/shared-tree-core/editManagerCodecsCommons.d.ts +3 -3
  188. package/lib/shared-tree-core/editManagerCodecsCommons.d.ts.map +1 -1
  189. package/lib/shared-tree-core/editManagerCodecsCommons.js +2 -2
  190. package/lib/shared-tree-core/editManagerCodecsCommons.js.map +1 -1
  191. package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts +2 -2
  192. package/lib/shared-tree-core/editManagerCodecsV1toV4.d.ts.map +1 -1
  193. package/lib/shared-tree-core/editManagerCodecsV1toV4.js +2 -2
  194. package/lib/shared-tree-core/editManagerCodecsV1toV4.js.map +1 -1
  195. package/lib/shared-tree-core/editManagerCodecsVSharedBranches.d.ts +2 -2
  196. package/lib/shared-tree-core/editManagerCodecsVSharedBranches.d.ts.map +1 -1
  197. package/lib/shared-tree-core/editManagerCodecsVSharedBranches.js +2 -2
  198. package/lib/shared-tree-core/editManagerCodecsVSharedBranches.js.map +1 -1
  199. package/lib/shared-tree-core/messageCodecs.d.ts.map +1 -1
  200. package/lib/shared-tree-core/messageCodecs.js +2 -2
  201. package/lib/shared-tree-core/messageCodecs.js.map +1 -1
  202. package/lib/simple-tree/api/index.d.ts +1 -1
  203. package/lib/simple-tree/api/index.d.ts.map +1 -1
  204. package/lib/simple-tree/api/index.js +1 -1
  205. package/lib/simple-tree/api/index.js.map +1 -1
  206. package/lib/simple-tree/api/snapshotCompatibilityChecker.d.ts +148 -29
  207. package/lib/simple-tree/api/snapshotCompatibilityChecker.d.ts.map +1 -1
  208. package/lib/simple-tree/api/snapshotCompatibilityChecker.js +179 -98
  209. package/lib/simple-tree/api/snapshotCompatibilityChecker.js.map +1 -1
  210. package/lib/simple-tree/api/tree.d.ts +1 -1
  211. package/lib/simple-tree/api/tree.js.map +1 -1
  212. package/lib/simple-tree/api/treeBeta.d.ts +1 -1
  213. package/lib/simple-tree/api/treeBeta.js.map +1 -1
  214. package/lib/simple-tree/core/allowedTypes.d.ts +1 -1
  215. package/lib/simple-tree/core/allowedTypes.js.map +1 -1
  216. package/lib/simple-tree/core/unhydratedFlexTree.d.ts +1 -0
  217. package/lib/simple-tree/core/unhydratedFlexTree.d.ts.map +1 -1
  218. package/lib/simple-tree/core/unhydratedFlexTree.js +29 -0
  219. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  220. package/lib/simple-tree/index.d.ts +1 -1
  221. package/lib/simple-tree/index.d.ts.map +1 -1
  222. package/lib/simple-tree/index.js +1 -1
  223. package/lib/simple-tree/index.js.map +1 -1
  224. package/lib/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
  225. package/lib/simple-tree/node-kinds/array/arrayNode.js +5 -14
  226. package/lib/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  227. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.d.ts.map +1 -1
  228. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.js +34 -8
  229. package/lib/simple-tree/unhydratedFlexTreeFromInsertable.js.map +1 -1
  230. package/lib/text/textDomainFormatted.d.ts +3 -3
  231. package/lib/text/textDomainFormatted.d.ts.map +1 -1
  232. package/lib/text/textDomainFormatted.js +30 -14
  233. package/lib/text/textDomainFormatted.js.map +1 -1
  234. package/lib/util/bTreeUtils.d.ts.map +1 -1
  235. package/lib/util/bTreeUtils.js +6 -6
  236. package/lib/util/bTreeUtils.js.map +1 -1
  237. package/lib/util/rangeMap.d.ts.map +1 -1
  238. package/lib/util/rangeMap.js +5 -6
  239. package/lib/util/rangeMap.js.map +1 -1
  240. package/package.json +23 -23
  241. package/src/codec/codec.ts +10 -112
  242. package/src/codec/index.ts +0 -3
  243. package/src/codec/versioned/codec.ts +119 -83
  244. package/src/core/tree/detachedFieldIndexCodecs.ts +6 -4
  245. package/src/{extensibleSchemaUnion.ts → extensibleUnionNode.ts} +61 -19
  246. package/src/feature-libraries/chunked-forest/codec/codecs.ts +5 -11
  247. package/src/feature-libraries/forest-summary/codec.ts +8 -7
  248. package/src/feature-libraries/forest-summary/formatCommon.ts +5 -3
  249. package/src/feature-libraries/forest-summary/formatV1.ts +1 -3
  250. package/src/feature-libraries/forest-summary/formatV2.ts +1 -3
  251. package/src/feature-libraries/modular-schema/modularChangeCodecV1.ts +5 -6
  252. package/src/feature-libraries/schema-index/codec.ts +6 -4
  253. package/src/index.ts +3 -3
  254. package/src/packageVersion.ts +1 -1
  255. package/src/shared-tree/sharedTreeChangeCodecs.ts +2 -2
  256. package/src/shared-tree/tree.ts +1 -1
  257. package/src/shared-tree/treeAlpha.ts +1 -1
  258. package/src/shared-tree/treeCheckout.ts +2 -4
  259. package/src/shared-tree-core/editManagerCodecsCommons.ts +7 -7
  260. package/src/shared-tree-core/editManagerCodecsV1toV4.ts +3 -10
  261. package/src/shared-tree-core/editManagerCodecsVSharedBranches.ts +3 -10
  262. package/src/shared-tree-core/messageCodecs.ts +2 -6
  263. package/src/simple-tree/api/index.ts +2 -2
  264. package/src/simple-tree/api/snapshotCompatibilityChecker.ts +344 -142
  265. package/src/simple-tree/api/tree.ts +1 -1
  266. package/src/simple-tree/api/treeBeta.ts +1 -1
  267. package/src/simple-tree/core/allowedTypes.ts +1 -1
  268. package/src/simple-tree/core/unhydratedFlexTree.ts +43 -1
  269. package/src/simple-tree/index.ts +2 -2
  270. package/src/simple-tree/node-kinds/array/arrayNode.ts +13 -19
  271. package/src/simple-tree/unhydratedFlexTreeFromInsertable.ts +51 -10
  272. package/src/text/textDomainFormatted.ts +37 -17
  273. package/src/util/bTreeUtils.ts +10 -6
  274. package/src/util/rangeMap.ts +9 -6
  275. package/api-extractor-lint.json +0 -4
  276. package/dist/extensibleSchemaUnion.d.ts +0 -72
  277. package/dist/extensibleSchemaUnion.d.ts.map +0 -1
  278. package/dist/extensibleSchemaUnion.js.map +0 -1
  279. package/lib/extensibleSchemaUnion.d.ts +0 -72
  280. package/lib/extensibleSchemaUnion.d.ts.map +0 -1
  281. package/lib/extensibleSchemaUnion.js.map +0 -1
@@ -3,11 +3,11 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert } from "@fluidframework/core-utils/internal";
6
+ import { assert, debugAssert } from "@fluidframework/core-utils/internal";
7
7
  import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";
8
8
  import {
9
9
  getConfigForMinVersionForCollabIterable,
10
- type ConfigMapEntry,
10
+ lowestMinVersionForCollab,
11
11
  type MinimumMinorSemanticVersion,
12
12
  type SemanticVersion,
13
13
  } from "@fluidframework/runtime-utils/internal";
@@ -27,9 +27,7 @@ import {
27
27
  withSchemaValidation,
28
28
  type FormatVersion,
29
29
  type CodecWriteOptions,
30
- type IMultiFormatCodec,
31
30
  type CodecName,
32
- ensureBinaryEncoding,
33
31
  type CodecTree,
34
32
  } from "../codec.js";
35
33
 
@@ -151,7 +149,7 @@ export function makeVersionDispatchingCodec<TDecoded, TContext>(
151
149
  family: ICodecFamily<TDecoded, TContext>,
152
150
  options: ICodecOptions & { writeVersion: FormatVersion },
153
151
  ): IJsonCodec<TDecoded, JsonCompatibleReadOnly, JsonCompatibleReadOnly, TContext> {
154
- const writeCodec = family.resolve(options.writeVersion).json;
152
+ const writeCodec = family.resolve(options.writeVersion);
155
153
  const supportedVersions = new Set(family.getSupportedFormats());
156
154
  return makeVersionedCodec(supportedVersions, options, {
157
155
  encode(data, context): Versioned {
@@ -159,7 +157,7 @@ export function makeVersionDispatchingCodec<TDecoded, TContext>(
159
157
  },
160
158
  decode(data: Versioned, context) {
161
159
  const codec = family.resolve(data.version);
162
- return codec.json.decode(data, context);
160
+ return codec.decode(data, context);
163
161
  },
164
162
  });
165
163
  }
@@ -170,10 +168,9 @@ export function makeVersionDispatchingCodec<TDecoded, TContext>(
170
168
  * The codec should not perform its own schema validation.
171
169
  * The schema validation gets added when normalizing to {@link NormalizedCodecVersion}.
172
170
  */
173
- export type CodecAndSchema<TDecoded, TContext = void> = { readonly schema: TSchema } & (
174
- | IMultiFormatCodec<TDecoded, VersionedJson, JsonCompatibleReadOnly, TContext>
175
- | IJsonCodec<TDecoded, VersionedJson, JsonCompatibleReadOnly, TContext>
176
- );
171
+ export type CodecAndSchema<TDecoded, TContext = void> = {
172
+ readonly schema: TSchema;
173
+ } & IJsonCodec<TDecoded, VersionedJson, JsonCompatibleReadOnly, TContext>;
177
174
 
178
175
  /**
179
176
  * A codec alongside its format version and schema.
@@ -182,6 +179,14 @@ export interface CodecVersionBase<
182
179
  T = unknown,
183
180
  TFormatVersion extends FormatVersion = FormatVersion,
184
181
  > {
182
+ /**
183
+ * When `undefined` the codec will never be selected as a write version except via override.
184
+ * @remarks
185
+ * This format will be used for decode if data in it needs to be decoded, regardless of `minVersionForCollab`.
186
+ * `undefined` should be used for unstable codec versions (with string FormatVersions),
187
+ * as well as previously stabilized formats that are discontinued (meaning we always prefer to use some other format for encoding).
188
+ */
189
+ readonly minVersionForCollab: MinimumVersionForCollab | undefined;
185
190
  readonly formatVersion: TFormatVersion;
186
191
  readonly codec: T;
187
192
  }
@@ -228,11 +233,8 @@ export interface NormalizedCodecVersion<
228
233
  * @remarks
229
234
  * Produced by {@link ClientVersionDispatchingCodecBuilder.applyOptions}.
230
235
  */
231
- export interface EvaluatedCodecVersion<
232
- TDecoded,
233
- TContext,
234
- TFormatVersion extends FormatVersion,
235
- > extends CodecVersionBase<
236
+ interface EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion extends FormatVersion>
237
+ extends CodecVersionBase<
236
238
  IJsonCodec<TDecoded, VersionedJson, JsonCompatibleReadOnly, TContext>,
237
239
  TFormatVersion
238
240
  > {}
@@ -258,17 +260,16 @@ function normalizeCodecVersion<
258
260
  options: TBuildOptions,
259
261
  ): IJsonCodec<TDecoded, VersionedJson, JsonCompatibleReadOnly, TContext> => {
260
262
  const built = codecBuilder(options);
261
- // We currently don't expose or use binary formats, but someday we might.
262
- const multiFormat = ensureBinaryEncoding<TDecoded, TContext, VersionedJson>(built);
263
263
  return makeVersionedValidatedCodec(
264
264
  options,
265
265
  new Set([codecVersion.formatVersion]),
266
266
  built.schema,
267
- multiFormat.json,
267
+ built,
268
268
  );
269
269
  };
270
270
 
271
271
  return {
272
+ minVersionForCollab: codecVersion.minVersionForCollab,
272
273
  formatVersion: codecVersion.formatVersion,
273
274
  codec,
274
275
  };
@@ -287,13 +288,20 @@ export class ClientVersionDispatchingCodecBuilder<
287
288
  TFormatVersion extends FormatVersion,
288
289
  TBuildOptions extends CodecWriteOptions,
289
290
  > {
290
- public readonly registry: ReadonlyMap<
291
- MinimumVersionForCollab,
292
- NormalizedCodecVersion<TDecoded, TContext, TFormatVersion, TBuildOptions>
293
- >;
291
+ public readonly registry: readonly NormalizedCodecVersion<
292
+ TDecoded,
293
+ TContext,
294
+ TFormatVersion,
295
+ TBuildOptions
296
+ >[];
294
297
 
295
298
  /**
296
299
  * Use {@link ClientVersionDispatchingCodecBuilder.build} to create an instance of this class.
300
+ * @remarks
301
+ * Inputs to this are assumed to be constants in the code controlled by the developers of this package,
302
+ * and constructed at least once during tests.
303
+ * Because of this, the validation of these inputs done with debugAssert should be sufficient,
304
+ * and using debugAssert avoids bloating the bundle size for production users.
297
305
  */
298
306
  private constructor(
299
307
  /**
@@ -303,9 +311,7 @@ export class ClientVersionDispatchingCodecBuilder<
303
311
  /**
304
312
  * The registry of codecs which this builder can use to encode and decode data.
305
313
  */
306
- inputRegistry: ConfigMapEntry<
307
- CodecVersion<TDecoded, TContext, TFormatVersion, TBuildOptions>
308
- >,
314
+ inputRegistry: readonly CodecVersion<TDecoded, TContext, TFormatVersion, TBuildOptions>[],
309
315
  ) {
310
316
  type Normalized = NormalizedCodecVersion<
311
317
  TDecoded,
@@ -313,18 +319,41 @@ export class ClientVersionDispatchingCodecBuilder<
313
319
  TFormatVersion,
314
320
  TBuildOptions
315
321
  >;
316
- const normalizedRegistry = new Map<MinimumVersionForCollab, Normalized>();
317
-
318
- for (const [minVersionForCollab, codec] of Object.entries(inputRegistry) as Iterable<
319
- [
320
- MinimumVersionForCollab,
321
- CodecVersion<TDecoded, TContext, TFormatVersion, TBuildOptions>,
322
- ]
323
- >) {
322
+ const normalizedRegistry: Normalized[] = [];
323
+ const formats: Set<FormatVersion> = new Set();
324
+ const versions: Set<string | undefined> = new Set();
325
+
326
+ for (const codec of inputRegistry) {
327
+ debugAssert(
328
+ () =>
329
+ !formats.has(codec.formatVersion) ||
330
+ `duplicate codec format ${name} ${codec.formatVersion}`,
331
+ );
332
+ debugAssert(
333
+ () =>
334
+ codec.minVersionForCollab === undefined ||
335
+ typeof codec.formatVersion !== "string" ||
336
+ `unstable format ${JSON.stringify(codec.formatVersion)} (string formats) must not have a minVersionForCollab in ${name}`,
337
+ );
338
+ formats.add(codec.formatVersion);
324
339
  const normalizedCodec = normalizeCodecVersion(codec);
325
- normalizedRegistry.set(minVersionForCollab, normalizedCodec);
340
+ normalizedRegistry.push(normalizedCodec);
341
+ if (codec.minVersionForCollab !== undefined) {
342
+ debugAssert(
343
+ () =>
344
+ !versions.has(codec.minVersionForCollab) ||
345
+ `Codec ${name} has multiple entries for version ${JSON.stringify(codec.minVersionForCollab)}`,
346
+ );
347
+ versions.add(codec.minVersionForCollab);
348
+ }
326
349
  }
327
350
 
351
+ debugAssert(
352
+ () =>
353
+ versions.has(lowestMinVersionForCollab) ||
354
+ `Codec ${name} is missing entry for lowestMinVersionForCollab`,
355
+ );
356
+
328
357
  this.registry = normalizedRegistry;
329
358
  }
330
359
 
@@ -336,18 +365,12 @@ export class ClientVersionDispatchingCodecBuilder<
336
365
  */
337
366
  public applyOptions(
338
367
  options: TBuildOptions,
339
- ): [MinimumVersionForCollab, EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion>][] {
340
- return Array.from(
341
- this.registry,
342
- ([version, codec]) =>
343
- [
344
- version,
345
- {
346
- formatVersion: codec.formatVersion,
347
- codec: codec.codec(options),
348
- },
349
- ] as const,
350
- );
368
+ ): EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion>[] {
369
+ return this.registry.map((codec) => ({
370
+ minVersionForCollab: codec.minVersionForCollab,
371
+ formatVersion: codec.formatVersion,
372
+ codec: codec.codec(options),
373
+ }));
351
374
  }
352
375
 
353
376
  /**
@@ -361,7 +384,7 @@ export class ClientVersionDispatchingCodecBuilder<
361
384
  const fromFormatVersion = new Map<
362
385
  FormatVersion,
363
386
  EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion>
364
- >(applied.map(([_version, codec]) => [codec.formatVersion, codec]));
387
+ >(applied.map((codec) => [codec.formatVersion, codec]));
365
388
  return {
366
389
  encode: (data: TDecoded, context: TContext): JsonCompatibleReadOnly => {
367
390
  return writeVersion.codec.encode(data, context);
@@ -382,7 +405,7 @@ The client which encoded this data likely specified an "minVersionForCollab" val
382
405
 
383
406
  public getCodecTree(clientVersion: MinimumVersionForCollab): CodecTree {
384
407
  // TODO: add support for children codecs.
385
- const selected = getConfigForMinVersionForCollabIterable(clientVersion, this.registry);
408
+ const selected = getWriteVersionNoOverrides(this.registry, clientVersion);
386
409
  return {
387
410
  name: this.name,
388
411
  version: selected.formatVersion,
@@ -400,7 +423,7 @@ The client which encoded this data likely specified an "minVersionForCollab" val
400
423
  public static build<
401
424
  Name extends CodecName,
402
425
  Entry extends CodecVersion<unknown, unknown, FormatVersion, never>,
403
- >(name: Name, inputRegistry: ConfigMapEntry<Entry>) {
426
+ >(name: Name, inputRegistry: readonly Entry[]) {
404
427
  type TDecoded2 =
405
428
  Entry extends CodecVersion<infer D, unknown, FormatVersion, never> ? D : never;
406
429
  type TContext2 =
@@ -409,18 +432,24 @@ The client which encoded this data likely specified an "minVersionForCollab" val
409
432
  Entry extends CodecVersion<unknown, unknown, infer F, never> ? F : never;
410
433
  type TBuildOptions2 =
411
434
  Entry extends CodecVersion<unknown, unknown, FormatVersion, infer B> ? B : never;
412
- const builder = new ClientVersionDispatchingCodecBuilder(
413
- name,
414
- inputRegistry as ConfigMapEntry<unknown> as ConfigMapEntry<
415
- CodecVersion<
416
- TDecoded2,
417
- // If it does not matter what context is provided, undefined is fine, so allow it to be omitted.
418
- unknown extends TContext2 ? void : TContext2,
419
- TFormatVersion2,
420
- TBuildOptions2
421
- >
422
- >,
423
- );
435
+
436
+ type CodecFinal = CodecVersion<
437
+ TDecoded2,
438
+ // If it does not matter what context is provided, undefined is fine, so allow it to be omitted.
439
+ unknown extends TContext2 ? void : TContext2,
440
+ TFormatVersion2,
441
+ TBuildOptions2
442
+ >;
443
+
444
+ const input = inputRegistry as readonly unknown[] as readonly CodecFinal[];
445
+
446
+ const builder = new ClientVersionDispatchingCodecBuilder<
447
+ Name,
448
+ TDecoded2,
449
+ unknown extends TContext2 ? void : TContext2,
450
+ TFormatVersion2,
451
+ TBuildOptions2
452
+ >(name, input);
424
453
  return builder;
425
454
  }
426
455
  }
@@ -433,39 +462,51 @@ The client which encoded this data likely specified an "minVersionForCollab" val
433
462
  function getWriteVersion<T extends CodecVersionBase>(
434
463
  name: CodecName,
435
464
  options: CodecWriteOptions,
436
- versions: readonly [MinimumMinorSemanticVersion | MinimumVersionForCollab, T][],
465
+ versions: readonly T[],
437
466
  ): T {
438
467
  if (options.writeVersionOverrides?.has(name) === true) {
439
468
  const selectedFormatVersion = options.writeVersionOverrides.get(name);
440
- const selected = versions.find(
441
- ([_v, codec]) => codec.formatVersion === selectedFormatVersion,
442
- );
469
+ const selected = versions.find((codec) => codec.formatVersion === selectedFormatVersion);
443
470
  if (selected === undefined) {
444
471
  throw new UsageError(
445
- `Codec "${name}" does not support requested format version ${selectedFormatVersion}. Supported versions are: ${versionList(versions)}.`,
472
+ `Codec "${name}" does not support requested format version ${JSON.stringify(selectedFormatVersion)}. Supported versions are: ${versionList(versions)}.`,
446
473
  );
447
474
  } else if (options.allowPossiblyIncompatibleWriteVersionOverrides !== true) {
448
- const selectedMinVersionForCollab = selected[0];
449
- // Currently all versions must specify a minVersionForCollab, so undefined is not expected here.
450
- // TODO: It should be possible to have a version which would never be automatically selected for write (and thus does not have or need a minVersionForCollab), but can be selected via override.
451
- // Use-cases for this include experimental versions not yet stable, and discontinued or intermediate versions which are mainly being kept for read compatibility but still support writing (perhaps for round-trip testing).
452
- // For now, this check should never pass, and there is no way to create such a version yet.
475
+ const selectedMinVersionForCollab = selected.minVersionForCollab;
453
476
  if (selectedMinVersionForCollab === undefined) {
454
477
  throw new UsageError(
455
- `Codec "${name}" does not support requested format version ${selectedFormatVersion} because it does not specify a minVersionForCollab. Use "allowPossiblyIncompatibleWriteVersionOverrides" to suppress this error if appropriate.`,
478
+ `Codec "${name}" does not support requested format version ${JSON.stringify(selectedFormatVersion)} because it has minVersionForCollab undefined. Use "allowPossiblyIncompatibleWriteVersionOverrides" to suppress this error if appropriate.`,
456
479
  );
457
480
  } else if (gt(selectedMinVersionForCollab, options.minVersionForCollab)) {
458
481
  throw new UsageError(
459
- `Codec "${name}" does not support requested format version ${selectedFormatVersion} because it is only compatible back to client version ${selectedMinVersionForCollab} and the requested oldest compatible client was ${options.minVersionForCollab}. Use "allowPossiblyIncompatibleWriteVersionOverrides" to suppress this error if appropriate.`,
482
+ `Codec "${name}" does not support requested format version ${JSON.stringify(selectedFormatVersion)} because it is only compatible back to client version ${selectedMinVersionForCollab} and the requested oldest compatible client was ${options.minVersionForCollab}. Use "allowPossiblyIncompatibleWriteVersionOverrides" to suppress this error if appropriate.`,
460
483
  );
461
484
  }
462
485
  }
463
486
 
464
- return selected[1];
487
+ return selected;
465
488
  }
489
+
490
+ return getWriteVersionNoOverrides(versions, options.minVersionForCollab);
491
+ }
492
+
493
+ /**
494
+ * Selects which format should be used when writing data, without consider overrides.
495
+ */
496
+ function getWriteVersionNoOverrides<T extends CodecVersionBase>(
497
+ versions: readonly T[],
498
+ minVersionForCollab: MinimumVersionForCollab,
499
+ ): T {
500
+ const stableVersions: [MinimumMinorSemanticVersion | MinimumVersionForCollab, T][] = [];
501
+ for (const version of versions) {
502
+ if (version.minVersionForCollab !== undefined) {
503
+ stableVersions.push([version.minVersionForCollab, version]);
504
+ }
505
+ }
506
+
466
507
  const result: T = getConfigForMinVersionForCollabIterable(
467
- options.minVersionForCollab,
468
- versions,
508
+ minVersionForCollab,
509
+ stableVersions,
469
510
  );
470
511
  return result;
471
512
  }
@@ -473,11 +514,6 @@ function getWriteVersion<T extends CodecVersionBase>(
473
514
  /**
474
515
  * Formats a list of versions for use in UsageErrors.
475
516
  */
476
- function versionList(
477
- versions: readonly [
478
- MinimumMinorSemanticVersion | MinimumVersionForCollab,
479
- CodecVersionBase,
480
- ][],
481
- ): string {
482
- return `${Array.from(versions, ([_v, codec]) => codec.formatVersion).join(", ")}`;
517
+ function versionList(versions: readonly CodecVersionBase[]): string {
518
+ return JSON.stringify(Array.from(versions, (codec) => codec.formatVersion));
483
519
  }
@@ -24,16 +24,18 @@ type BuildData = CodecWriteOptions & {
24
24
 
25
25
  export const detachedFieldIndexCodecBuilder = ClientVersionDispatchingCodecBuilder.build(
26
26
  "DetachedFieldIndex",
27
- {
28
- [lowestMinVersionForCollab]: {
27
+ [
28
+ {
29
+ minVersionForCollab: lowestMinVersionForCollab,
29
30
  formatVersion: DetachedFieldIndexFormatVersion.v1,
30
31
  codec: (buildData: BuildData) =>
31
32
  makeDetachedNodeToFieldCodecV1(buildData.revisionTagCodec, buildData.idCompressor),
32
33
  },
33
- [FluidClientVersion.v2_52]: {
34
+ {
35
+ minVersionForCollab: FluidClientVersion.v2_52,
34
36
  formatVersion: DetachedFieldIndexFormatVersion.v2,
35
37
  codec: (buildData: BuildData) =>
36
38
  makeDetachedNodeToFieldCodecV2(buildData.revisionTagCodec, buildData.idCompressor),
37
39
  },
38
- },
40
+ ],
39
41
  );
@@ -3,6 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { UsageError } from "@fluidframework/telemetry-utils/internal";
7
+
6
8
  import { TreeAlpha, Tree } from "./shared-tree/index.js";
7
9
  import type {
8
10
  TreeNodeSchema,
@@ -15,15 +17,16 @@ import type {
15
17
  import {
16
18
  createCustomizedFluidFrameworkScopedFactory,
17
19
  eraseSchemaDetailsSubclassable,
20
+ getInnerNode,
18
21
  SchemaFactory,
19
22
  TreeBeta,
20
23
  } from "./simple-tree/index.js";
21
24
  import type { UnionToIntersection } from "./util/index.js";
22
25
 
23
26
  /**
24
- * Utilities for creating extensible schema unions.
27
+ * Utilities for creating extensible unions using a node.
25
28
  * @remarks
26
- * Use {@link ExtensibleSchemaUnion.extensibleSchemaUnion} to create the union schema.
29
+ * Use {@link ExtensibleUnionNode.createSchema} to create the union schema.
27
30
  *
28
31
  * Unlike a schema union created using {@link SchemaStaticsBeta.staged | staged} allowed types, this union allows for unknown future types to exist in addition to the known types.
29
32
  * This allows for faster roll-outs of new types without waiting for old clients to be updated to be aware of them.
@@ -35,42 +38,68 @@ import type { UnionToIntersection } from "./util/index.js";
35
38
  *
36
39
  * @example
37
40
  * ```typescript
38
- * const sf = new SchemaFactoryBeta("extensibleSchemaUnionExample.items");
41
+ * const sf = new SchemaFactoryBeta("extensibleUnionNodeExample.items");
39
42
  * class ItemA extends sf.object("A", { x: sf.string }) {}
40
43
  * class ItemB extends sf.object("B", { x: sf.number }) {}
41
44
  *
42
- * class AnyItem extends ExtensibleSchemaUnion.extensibleSchemaUnion(
45
+ * class AnyItem extends ExtensibleUnionNode.createSchema(
43
46
  * [ItemA, ItemB], // Future versions may add more members here
44
47
  * sf,
45
48
  * "ExtensibleUnion",
46
49
  * ) {}
47
50
  * // Instances of the union are created using `create`.
48
51
  * const anyItem = AnyItem.create(new ItemA({ x: "hello" }));
49
- * // Reading the content from the union is done via `child`,
52
+ * // Reading the content from the union is done via the `union` property,
50
53
  * // which can be `undefined` to handle the case where a future version of this schema allows a type unknown to the current version.
51
- * const childNode: ItemA | ItemB | undefined = anyItem.child;
54
+ * const childNode: ItemA | ItemB | undefined = anyItem.union;
52
55
  * // To determine which member of the union was present, its schema can be inspected:
53
56
  * const aSchema = Tree.schema(childNode ?? assert.fail("No child"));
54
57
  * assert.equal(aSchema, ItemA);
55
58
  * ```
56
- * @alpha
59
+ * @beta
57
60
  */
58
- export namespace ExtensibleSchemaUnion {
61
+ export namespace ExtensibleUnionNode {
59
62
  /**
60
- * Members for classes created by {@link ExtensibleSchemaUnion.extensibleSchemaUnion}.
61
- * @alpha
63
+ * Members for classes created by {@link ExtensibleUnionNode.createSchema}.
64
+ * @beta
62
65
  */
63
66
  export interface Members<T> {
64
67
  /**
65
- * The child wrapped by this node, which is has one of the type allowed by the union,
68
+ * The child wrapped by this node has one of the types allowed by the union,
66
69
  * or `undefined` if the type is one which was added to the union by a future version of this schema.
70
+ *
71
+ * @throws if {@link ExtensibleUnionNode.Members.isValid} is false.
72
+ */
73
+ readonly union: T | undefined;
74
+
75
+ /**
76
+ * Returns true, unless this node is in an invalid state.
77
+ * @remarks
78
+ * A well behaved application should not need this API.
79
+ * If an application is hitting errors when accessing {@link ExtensibleUnionNode.Members.union},
80
+ * this API can be used to help detect and recover from the invalid state which causes those errors (for example by replacing the invalid nodes with new ones).
81
+ *
82
+ * In this context "invalid" means that the internal implementation details of this node have had their invariants violated.
83
+ * This can happen when:
84
+ * - Using weakly typed construction APIs like {@link (TreeBeta:interface).importConcise} or {@link (TreeAlpha:interface).importVerbose} to construct an invalid state directly.
85
+ * Using such APIs, even when not creating invalid nodes, is not supported for this schema,
86
+ * since doing so requires knowing the implementation details of this node which are subject to change.
87
+ * - By editing a document using a different client using a different schema for this node.
88
+ * - Violating the TypeScript types to directly manipulate the node internals.
89
+ * - A bug in this node's implementation (possibly in a different client) corrupted the node.
90
+ * - Corruption of the document this node is contained in.
91
+ *
92
+ * @privateRemarks
93
+ * We could support {@link (TreeBeta:interface).exportVerbose} using {@link KeyEncodingOptions.allStoredKeys}
94
+ * then {@link (TreeAlpha:interface).importVerbose} with {@link KeyEncodingOptions.knownStoredKeys}.
95
+ * However, even this will error (but will not produce an invalid node) if there is a node of an unknown type in the union.
67
96
  */
68
- readonly child: T | undefined;
97
+ isValid(): boolean;
69
98
  }
70
99
 
71
100
  /**
72
- * Statics for classes created by {@link ExtensibleSchemaUnion.extensibleSchemaUnion}.
73
- * @alpha
101
+ * Statics for classes created by {@link ExtensibleUnionNode.createSchema}.
102
+ * @beta
74
103
  */
75
104
  export interface Statics<T extends readonly TreeNodeSchema[]> {
76
105
  /**
@@ -86,11 +115,11 @@ export namespace ExtensibleSchemaUnion {
86
115
  * Create an extensible schema union which currently supports the types in `types`,
87
116
  * but tolerates collaboration with future versions that may include additional types.
88
117
  * @remarks
89
- * See {@link ExtensibleSchemaUnion} for an example use.
90
- * @alpha
118
+ * See {@link ExtensibleUnionNode} for an example use.
119
+ * @beta
91
120
  */
92
121
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
93
- export function extensibleSchemaUnion<
122
+ export function createSchema<
94
123
  const T extends readonly TreeNodeSchema[],
95
124
  const TScope extends string,
96
125
  const TName extends string,
@@ -101,19 +130,32 @@ export namespace ExtensibleSchemaUnion {
101
130
  }
102
131
  const schemaFactory = createCustomizedFluidFrameworkScopedFactory(
103
132
  inputSchemaFactory,
104
- "extensibleSchemaUnion",
133
+ "extensibleUnionNode",
105
134
  );
135
+
106
136
  class Union
107
137
  extends schemaFactory.object(name, record, { allowUnknownOptionalFields: true })
108
138
  implements Members<TreeNodeFromImplicitAllowedTypes<T>>
109
139
  {
110
- public get child(): TreeNodeFromImplicitAllowedTypes<T> | undefined {
140
+ public get union(): TreeNodeFromImplicitAllowedTypes<T> | undefined {
141
+ if (!this.isValid()) {
142
+ throw new UsageError(
143
+ `This ExtensibleUnionNode (${Union.identifier}) is in an invalid state. It must have been edited by another client using a different schema or been directly imported or constructed in an invalid state.`,
144
+ );
145
+ }
111
146
  for (const [_key, child] of TreeAlpha.children(this)) {
112
147
  return child as TreeNodeFromImplicitAllowedTypes<T>;
113
148
  }
114
149
  return undefined;
115
150
  }
116
151
 
152
+ public isValid(): boolean {
153
+ // Use inner node, since it includes populated fields even when they are unknown.
154
+ const inner = getInnerNode(this);
155
+ // Fields only includes non-empty fields, so this is what we need to check the one child invariant.
156
+ return [...inner.fields].length === 1;
157
+ }
158
+
117
159
  public static create<TThis extends TreeNodeSchema>(
118
160
  this: TThis,
119
161
  child: TreeNodeFromImplicitAllowedTypes<T>,
@@ -35,13 +35,7 @@ import { TreeCompressionStrategy } from "../../treeCompressionUtils.js";
35
35
 
36
36
  import { decode } from "./chunkDecoding.js";
37
37
  import type { FieldBatch } from "./fieldBatch.js";
38
- import {
39
- type EncodedFieldBatch,
40
- validVersions,
41
- FieldBatchFormatVersion,
42
- EncodedFieldBatchV1,
43
- EncodedFieldBatchV2,
44
- } from "./format.js";
38
+ import { EncodedFieldBatch, validVersions, FieldBatchFormatVersion } from "./format.js";
45
39
  import type { IncrementalEncodingPolicy } from "./incrementalEncodingPolicy.js";
46
40
  import { schemaCompressedEncodeV1, schemaCompressedEncodeV2 } from "./schemaBasedEncode.js";
47
41
  import { uncompressedEncodeV1, uncompressedEncodeV2 } from "./uncompressedEncode.js";
@@ -157,18 +151,15 @@ export function makeFieldBatchCodec(options: CodecWriteOptions): FieldBatchCodec
157
151
  let schemaCompressedEncodeFn:
158
152
  | typeof schemaCompressedEncodeV1
159
153
  | typeof schemaCompressedEncodeV2;
160
- let encodedFieldBatchType: typeof EncodedFieldBatchV1 | typeof EncodedFieldBatchV2;
161
154
  switch (writeVersion) {
162
155
  case unbrand(FieldBatchFormatVersion.v1): {
163
156
  uncompressedEncodeFn = uncompressedEncodeV1;
164
157
  schemaCompressedEncodeFn = schemaCompressedEncodeV1;
165
- encodedFieldBatchType = EncodedFieldBatchV1;
166
158
  break;
167
159
  }
168
160
  case unbrand(FieldBatchFormatVersion.v2): {
169
161
  uncompressedEncodeFn = uncompressedEncodeV2;
170
162
  schemaCompressedEncodeFn = schemaCompressedEncodeV2;
171
- encodedFieldBatchType = EncodedFieldBatchV2;
172
163
  break;
173
164
  }
174
165
  default: {
@@ -176,7 +167,10 @@ export function makeFieldBatchCodec(options: CodecWriteOptions): FieldBatchCodec
176
167
  }
177
168
  }
178
169
 
179
- return makeVersionedValidatedCodec(options, validVersions, encodedFieldBatchType, {
170
+ // Both the encode and decode logic here support both v1 and v2, as does `validVersions` and `EncodedFieldBatch`.
171
+ // This makes this use of makeVersionedValidatedCodec atypical as it is a single call being used to make a codec that supports all versions,
172
+ // instead of one call per version, then using another utility to select between them based on version.
173
+ return makeVersionedValidatedCodec(options, validVersions, EncodedFieldBatch, {
180
174
  encode: (data: FieldBatch, context: FieldBatchEncodingContext): EncodedFieldBatch => {
181
175
  for (const cursor of data) {
182
176
  assert(
@@ -9,6 +9,7 @@ import {
9
9
  getConfigForMinVersionForCollab,
10
10
  lowestMinVersionForCollab,
11
11
  } from "@fluidframework/runtime-utils/internal";
12
+ import { Type } from "@sinclair/typebox";
12
13
 
13
14
  import {
14
15
  type CodecTree,
@@ -21,12 +22,9 @@ import type { FieldKey, ITreeCursorSynchronous } from "../../core/index.js";
21
22
  import { brand, type JsonCompatibleReadOnly } from "../../util/index.js";
22
23
  import type { FieldBatchCodec, FieldBatchEncodingContext } from "../chunked-forest/index.js";
23
24
 
24
- import {
25
- ForestFormatVersion,
26
- validVersions,
27
- type Format,
28
- FormatCommon,
29
- } from "./formatCommon.js";
25
+ import { ForestFormatVersion, validVersions, type Format } from "./formatCommon.js";
26
+ import { FormatV1 } from "./formatV1.js";
27
+ import { FormatV2 } from "./formatV2.js";
30
28
 
31
29
  /**
32
30
  * Uses field cursors
@@ -61,7 +59,10 @@ export function makeForestSummarizerCodec(
61
59
  ): ForestCodec {
62
60
  const inner = fieldBatchCodec;
63
61
  const writeVersion = clientVersionToForestFormatVersion(options.minVersionForCollab);
64
- const formatSchema = FormatCommon(writeVersion);
62
+ const formatSchema = Type.Union([FormatV1, FormatV2]);
63
+ // Both the encode and decode logic here support both v1 and v2, as does `validVersions` and `formatSchema`.
64
+ // This makes this use of makeVersionedValidatedCodec atypical as it is a single call being used to make a codec that supports all versions,
65
+ // instead of one call per version, then using another utility to select between them based on version.
65
66
  return makeVersionedValidatedCodec(options, validVersions, formatSchema, {
66
67
  encode: (data: FieldSet, context: FieldBatchEncodingContext): Format => {
67
68
  const keys: FieldKey[] = [];
@@ -20,8 +20,8 @@ export type ForestFormatVersion = Values<typeof ForestFormatVersion>;
20
20
 
21
21
  export const validVersions = new Set([...Object.values(ForestFormatVersion)]);
22
22
 
23
- export const FormatCommon = (
24
- version: ForestFormatVersion,
23
+ export const FormatCommon = <const TVersion extends ForestFormatVersion>(
24
+ version: TVersion,
25
25
  // Return type is intentionally derived.
26
26
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
27
27
  ) =>
@@ -33,4 +33,6 @@ export const FormatCommon = (
33
33
  },
34
34
  { additionalProperties: false },
35
35
  );
36
- export type Format = Static<ReturnType<typeof FormatCommon>>;
36
+ export type Format<TVersion extends ForestFormatVersion = ForestFormatVersion> = Static<
37
+ ReturnType<typeof FormatCommon<TVersion>>
38
+ >;
@@ -5,9 +5,7 @@
5
5
 
6
6
  import type { Static } from "@sinclair/typebox";
7
7
 
8
- import { brand } from "../../util/index.js";
9
-
10
8
  import { FormatCommon, ForestFormatVersion } from "./formatCommon.js";
11
9
 
12
- export const FormatV1 = FormatCommon(brand<ForestFormatVersion>(ForestFormatVersion.v1));
10
+ export const FormatV1 = FormatCommon(ForestFormatVersion.v1);
13
11
  export type FormatV1 = Static<typeof FormatV1>;
@@ -5,9 +5,7 @@
5
5
 
6
6
  import type { Static } from "@sinclair/typebox";
7
7
 
8
- import { brand } from "../../util/index.js";
9
-
10
8
  import { FormatCommon, ForestFormatVersion } from "./formatCommon.js";
11
9
 
12
- export const FormatV2 = FormatCommon(brand<ForestFormatVersion>(ForestFormatVersion.v2));
10
+ export const FormatV2 = FormatCommon(ForestFormatVersion.v2);
13
11
  export type FormatV2 = Static<typeof FormatV2>;