@fluidframework/tree 2.114.0 → 2.115.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 (240) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/api-report/tree.alpha.api.md +108 -39
  3. package/dist/core/index.d.ts +1 -1
  4. package/dist/core/index.d.ts.map +1 -1
  5. package/dist/core/index.js +3 -2
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/rebase/index.d.ts +1 -1
  8. package/dist/core/rebase/index.d.ts.map +1 -1
  9. package/dist/core/rebase/index.js +2 -1
  10. package/dist/core/rebase/index.js.map +1 -1
  11. package/dist/core/rebase/types.d.ts +110 -0
  12. package/dist/core/rebase/types.d.ts.map +1 -1
  13. package/dist/core/rebase/types.js +47 -1
  14. package/dist/core/rebase/types.js.map +1 -1
  15. package/dist/entrypoints/alpha.d.ts +1 -1
  16. package/dist/entrypoints/alpha.d.ts.map +1 -1
  17. package/dist/entrypoints/alpha.js +6 -4
  18. package/dist/entrypoints/alpha.js.map +1 -1
  19. package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts +9 -6
  20. package/dist/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
  21. package/dist/feature-libraries/chunked-forest/codec/codecs.js +16 -13
  22. package/dist/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +6 -5
  26. package/dist/index.js.map +1 -1
  27. package/dist/packageVersion.d.ts +1 -1
  28. package/dist/packageVersion.js +1 -1
  29. package/dist/packageVersion.js.map +1 -1
  30. package/dist/shared-tree/sharedTree.d.ts +5 -0
  31. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  32. package/dist/shared-tree/sharedTree.js +1 -1
  33. package/dist/shared-tree/sharedTree.js.map +1 -1
  34. package/dist/shared-tree/treeCheckout.d.ts +5 -0
  35. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  36. package/dist/shared-tree/treeCheckout.js +58 -12
  37. package/dist/shared-tree/treeCheckout.js.map +1 -1
  38. package/dist/shared-tree-core/branch.d.ts +1 -0
  39. package/dist/shared-tree-core/branch.d.ts.map +1 -1
  40. package/dist/shared-tree-core/branch.js.map +1 -1
  41. package/dist/shared-tree-core/sharedTreeCore.js +1 -1
  42. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  43. package/dist/simple-tree/api/index.d.ts +1 -1
  44. package/dist/simple-tree/api/index.d.ts.map +1 -1
  45. package/dist/simple-tree/api/index.js +2 -3
  46. package/dist/simple-tree/api/index.js.map +1 -1
  47. package/dist/simple-tree/api/schemaFactoryAlpha.d.ts +3 -3
  48. package/dist/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
  49. package/dist/simple-tree/api/schemaStatics.d.ts +6 -7
  50. package/dist/simple-tree/api/schemaStatics.d.ts.map +1 -1
  51. package/dist/simple-tree/api/schemaStatics.js.map +1 -1
  52. package/dist/simple-tree/api/treeNodeApi.d.ts +20 -24
  53. package/dist/simple-tree/api/treeNodeApi.d.ts.map +1 -1
  54. package/dist/simple-tree/api/treeNodeApi.js +48 -49
  55. package/dist/simple-tree/api/treeNodeApi.js.map +1 -1
  56. package/dist/simple-tree/core/treeNode.d.ts +5 -6
  57. package/dist/simple-tree/core/treeNode.d.ts.map +1 -1
  58. package/dist/simple-tree/core/treeNode.js +2 -4
  59. package/dist/simple-tree/core/treeNode.js.map +1 -1
  60. package/dist/simple-tree/core/withType.d.ts +20 -7
  61. package/dist/simple-tree/core/withType.d.ts.map +1 -1
  62. package/dist/simple-tree/core/withType.js +4 -7
  63. package/dist/simple-tree/core/withType.js.map +1 -1
  64. package/dist/simple-tree/index.d.ts +2 -2
  65. package/dist/simple-tree/index.d.ts.map +1 -1
  66. package/dist/simple-tree/index.js +3 -4
  67. package/dist/simple-tree/index.js.map +1 -1
  68. package/dist/simple-tree/leafNodeSchema.d.ts +6 -1
  69. package/dist/simple-tree/leafNodeSchema.d.ts.map +1 -1
  70. package/dist/simple-tree/leafNodeSchema.js +0 -1
  71. package/dist/simple-tree/leafNodeSchema.js.map +1 -1
  72. package/dist/simple-tree/node-kinds/array/arrayNode.d.ts +32 -3
  73. package/dist/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
  74. package/dist/simple-tree/node-kinds/array/arrayNode.js +91 -10
  75. package/dist/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  76. package/dist/simple-tree/node-kinds/map/mapNode.d.ts +77 -0
  77. package/dist/simple-tree/node-kinds/map/mapNode.d.ts.map +1 -1
  78. package/dist/simple-tree/node-kinds/map/mapNode.js +17 -1
  79. package/dist/simple-tree/node-kinds/map/mapNode.js.map +1 -1
  80. package/dist/simple-tree/node-kinds/object/objectNode.js +2 -2
  81. package/dist/simple-tree/node-kinds/object/objectNode.js.map +1 -1
  82. package/dist/simple-tree/node-kinds/record/recordNode.js +2 -2
  83. package/dist/simple-tree/node-kinds/record/recordNode.js.map +1 -1
  84. package/dist/text/codePointUtils.d.ts +2 -2
  85. package/dist/text/codePointUtils.js +2 -2
  86. package/dist/text/codePointUtils.js.map +1 -1
  87. package/dist/text/index.d.ts +3 -3
  88. package/dist/text/index.d.ts.map +1 -1
  89. package/dist/text/index.js +4 -4
  90. package/dist/text/index.js.map +1 -1
  91. package/dist/text/textDomain.d.ts +18 -18
  92. package/dist/text/textDomain.d.ts.map +1 -1
  93. package/dist/text/textDomain.js +8 -8
  94. package/dist/text/textDomain.js.map +1 -1
  95. package/dist/text/textDomainFormatted.d.ts +46 -42
  96. package/dist/text/textDomainFormatted.d.ts.map +1 -1
  97. package/dist/text/textDomainFormatted.js +32 -28
  98. package/dist/text/textDomainFormatted.js.map +1 -1
  99. package/dist/text/textDomainFormattedDefault.d.ts +18 -18
  100. package/dist/text/textDomainFormattedDefault.d.ts.map +1 -1
  101. package/dist/text/textDomainFormattedDefault.js +14 -17
  102. package/dist/text/textDomainFormattedDefault.js.map +1 -1
  103. package/dist/util/compressedIds.d.ts +22 -2
  104. package/dist/util/compressedIds.d.ts.map +1 -1
  105. package/dist/util/compressedIds.js +22 -1
  106. package/dist/util/compressedIds.js.map +1 -1
  107. package/lib/core/index.d.ts +1 -1
  108. package/lib/core/index.d.ts.map +1 -1
  109. package/lib/core/index.js +1 -1
  110. package/lib/core/index.js.map +1 -1
  111. package/lib/core/rebase/index.d.ts +1 -1
  112. package/lib/core/rebase/index.d.ts.map +1 -1
  113. package/lib/core/rebase/index.js +1 -1
  114. package/lib/core/rebase/index.js.map +1 -1
  115. package/lib/core/rebase/types.d.ts +110 -0
  116. package/lib/core/rebase/types.d.ts.map +1 -1
  117. package/lib/core/rebase/types.js +46 -0
  118. package/lib/core/rebase/types.js.map +1 -1
  119. package/lib/entrypoints/alpha.d.ts +1 -1
  120. package/lib/entrypoints/alpha.d.ts.map +1 -1
  121. package/lib/entrypoints/alpha.js +1 -1
  122. package/lib/entrypoints/alpha.js.map +1 -1
  123. package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts +9 -6
  124. package/lib/feature-libraries/chunked-forest/codec/codecs.d.ts.map +1 -1
  125. package/lib/feature-libraries/chunked-forest/codec/codecs.js +16 -13
  126. package/lib/feature-libraries/chunked-forest/codec/codecs.js.map +1 -1
  127. package/lib/index.d.ts +3 -3
  128. package/lib/index.d.ts.map +1 -1
  129. package/lib/index.js +2 -2
  130. package/lib/index.js.map +1 -1
  131. package/lib/packageVersion.d.ts +1 -1
  132. package/lib/packageVersion.js +1 -1
  133. package/lib/packageVersion.js.map +1 -1
  134. package/lib/shared-tree/sharedTree.d.ts +5 -0
  135. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  136. package/lib/shared-tree/sharedTree.js +1 -1
  137. package/lib/shared-tree/sharedTree.js.map +1 -1
  138. package/lib/shared-tree/treeCheckout.d.ts +5 -0
  139. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  140. package/lib/shared-tree/treeCheckout.js +60 -14
  141. package/lib/shared-tree/treeCheckout.js.map +1 -1
  142. package/lib/shared-tree-core/branch.d.ts +1 -0
  143. package/lib/shared-tree-core/branch.d.ts.map +1 -1
  144. package/lib/shared-tree-core/branch.js.map +1 -1
  145. package/lib/shared-tree-core/sharedTreeCore.js +1 -1
  146. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  147. package/lib/simple-tree/api/index.d.ts +1 -1
  148. package/lib/simple-tree/api/index.d.ts.map +1 -1
  149. package/lib/simple-tree/api/index.js +1 -1
  150. package/lib/simple-tree/api/index.js.map +1 -1
  151. package/lib/simple-tree/api/schemaFactoryAlpha.d.ts +3 -3
  152. package/lib/simple-tree/api/schemaFactoryAlpha.d.ts.map +1 -1
  153. package/lib/simple-tree/api/schemaStatics.d.ts +6 -7
  154. package/lib/simple-tree/api/schemaStatics.d.ts.map +1 -1
  155. package/lib/simple-tree/api/schemaStatics.js.map +1 -1
  156. package/lib/simple-tree/api/treeNodeApi.d.ts +20 -24
  157. package/lib/simple-tree/api/treeNodeApi.d.ts.map +1 -1
  158. package/lib/simple-tree/api/treeNodeApi.js +46 -46
  159. package/lib/simple-tree/api/treeNodeApi.js.map +1 -1
  160. package/lib/simple-tree/core/treeNode.d.ts +5 -6
  161. package/lib/simple-tree/core/treeNode.d.ts.map +1 -1
  162. package/lib/simple-tree/core/treeNode.js +2 -4
  163. package/lib/simple-tree/core/treeNode.js.map +1 -1
  164. package/lib/simple-tree/core/withType.d.ts +20 -7
  165. package/lib/simple-tree/core/withType.d.ts.map +1 -1
  166. package/lib/simple-tree/core/withType.js +4 -7
  167. package/lib/simple-tree/core/withType.js.map +1 -1
  168. package/lib/simple-tree/index.d.ts +2 -2
  169. package/lib/simple-tree/index.d.ts.map +1 -1
  170. package/lib/simple-tree/index.js +1 -1
  171. package/lib/simple-tree/index.js.map +1 -1
  172. package/lib/simple-tree/leafNodeSchema.d.ts +6 -1
  173. package/lib/simple-tree/leafNodeSchema.d.ts.map +1 -1
  174. package/lib/simple-tree/leafNodeSchema.js +0 -1
  175. package/lib/simple-tree/leafNodeSchema.js.map +1 -1
  176. package/lib/simple-tree/node-kinds/array/arrayNode.d.ts +32 -3
  177. package/lib/simple-tree/node-kinds/array/arrayNode.d.ts.map +1 -1
  178. package/lib/simple-tree/node-kinds/array/arrayNode.js +93 -12
  179. package/lib/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  180. package/lib/simple-tree/node-kinds/map/mapNode.d.ts +77 -0
  181. package/lib/simple-tree/node-kinds/map/mapNode.d.ts.map +1 -1
  182. package/lib/simple-tree/node-kinds/map/mapNode.js +18 -2
  183. package/lib/simple-tree/node-kinds/map/mapNode.js.map +1 -1
  184. package/lib/simple-tree/node-kinds/object/objectNode.js +3 -3
  185. package/lib/simple-tree/node-kinds/object/objectNode.js.map +1 -1
  186. package/lib/simple-tree/node-kinds/record/recordNode.js +3 -3
  187. package/lib/simple-tree/node-kinds/record/recordNode.js.map +1 -1
  188. package/lib/text/codePointUtils.d.ts +2 -2
  189. package/lib/text/codePointUtils.js +2 -2
  190. package/lib/text/codePointUtils.js.map +1 -1
  191. package/lib/text/index.d.ts +3 -3
  192. package/lib/text/index.d.ts.map +1 -1
  193. package/lib/text/index.js +3 -3
  194. package/lib/text/index.js.map +1 -1
  195. package/lib/text/textDomain.d.ts +18 -18
  196. package/lib/text/textDomain.d.ts.map +1 -1
  197. package/lib/text/textDomain.js +7 -7
  198. package/lib/text/textDomain.js.map +1 -1
  199. package/lib/text/textDomainFormatted.d.ts +46 -42
  200. package/lib/text/textDomainFormatted.d.ts.map +1 -1
  201. package/lib/text/textDomainFormatted.js +31 -27
  202. package/lib/text/textDomainFormatted.js.map +1 -1
  203. package/lib/text/textDomainFormattedDefault.d.ts +18 -18
  204. package/lib/text/textDomainFormattedDefault.d.ts.map +1 -1
  205. package/lib/text/textDomainFormattedDefault.js +14 -17
  206. package/lib/text/textDomainFormattedDefault.js.map +1 -1
  207. package/lib/util/compressedIds.d.ts +22 -2
  208. package/lib/util/compressedIds.d.ts.map +1 -1
  209. package/lib/util/compressedIds.js +22 -1
  210. package/lib/util/compressedIds.js.map +1 -1
  211. package/package.json +29 -22
  212. package/src/core/index.ts +2 -0
  213. package/src/core/rebase/index.ts +2 -0
  214. package/src/core/rebase/types.ts +113 -0
  215. package/src/entrypoints/alpha.ts +10 -6
  216. package/src/feature-libraries/chunked-forest/codec/codecs.ts +17 -14
  217. package/src/index.ts +7 -4
  218. package/src/packageVersion.ts +1 -1
  219. package/src/shared-tree/sharedTree.ts +6 -1
  220. package/src/shared-tree/treeCheckout.ts +81 -14
  221. package/src/shared-tree-core/branch.ts +1 -0
  222. package/src/shared-tree-core/sharedTreeCore.ts +1 -1
  223. package/src/simple-tree/api/index.ts +0 -1
  224. package/src/simple-tree/api/schemaStatics.ts +2 -2
  225. package/src/simple-tree/api/treeNodeApi.ts +64 -48
  226. package/src/simple-tree/core/treeNode.ts +13 -10
  227. package/src/simple-tree/core/withType.ts +20 -7
  228. package/src/simple-tree/index.ts +1 -2
  229. package/src/simple-tree/leafNodeSchema.ts +6 -1
  230. package/src/simple-tree/node-kinds/array/arrayNode.ts +109 -14
  231. package/src/simple-tree/node-kinds/map/mapNode.ts +113 -3
  232. package/src/simple-tree/node-kinds/object/objectNode.ts +3 -3
  233. package/src/simple-tree/node-kinds/record/recordNode.ts +3 -3
  234. package/src/text/README.md +1 -1
  235. package/src/text/codePointUtils.ts +2 -2
  236. package/src/text/index.ts +3 -3
  237. package/src/text/textDomain.ts +21 -21
  238. package/src/text/textDomainFormatted.ts +56 -52
  239. package/src/text/textDomainFormattedDefault.ts +14 -23
  240. package/src/util/compressedIds.ts +36 -3
@@ -1 +1 @@
1
- {"version":3,"file":"textDomainFormattedDefault.js","sourceRoot":"","sources":["../../src/text/textDomainFormattedDefault.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAM5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;GAEG;AACH,MAAM,EAAE,GAAG,IAAI,iBAAiB,CAAC,2CAA2C,CAAC,CAAC;AAE9E,MAAM,aAAa,GAAG;IACrB,IAAI,EAAE,KAAK;IACX,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,OAAO;CACJ,CAAC;AAEX;;;;;GAKG;AACH,MAAM,KAAW,0BAA0B,CA8H1C;AA9HD,WAAiB,0BAA0B;IAW1C;;;;OAIG;IACH,MAAa,eAAgB,SAAQ,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE;QACjE,IAAI,EAAE,aAAa,CAAC,OAAO;QAC3B,MAAM,EAAE,aAAa,CAAC,OAAO;QAC7B,SAAS,EAAE,aAAa,CAAC,OAAO;QAChC,IAAI,EAAE,aAAa,CAAC,MAAM;QAC1B,IAAI,EAAE,aAAa,CAAC,MAAM;KAC1B,CAAC;;IACsB,6BAAa,GAAG,IAAI,eAAe,CAAC,aAAa,CAAC,CAAC;IAP9D,0CAAe,kBAQ3B,CAAA;IAED;;;OAGG;IACU,kCAAO,GAAG,eAAe,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE;QACnE,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,SAAS;QACT,WAAW;QACX,YAAY;QACZ,WAAW;KACX,CAAC,CAAC;IAQH;;;;;;;;;;;OAWG;IACH,MAAa,cAAe,SAAQ,EAAE,CAAC,MAAM,CAAC,gBAAgB,EAAE;QAC/D,GAAG,EAAE,2BAAA,OAAO,CAAC,MAAM;QACnB,MAAM,EAAE,aAAa,CAAC,MAAM;KAC5B,CAAC;QAHF;;YAIiB,YAAO,GAAG,IAAI,CAAC;QAChC,CAAC;KAAA;IALY,yCAAc,iBAK1B,CAAA;IAED;;;;OAIG;IACU,4CAAiB,GAAG;QAChC,mBAAmB,CAAC,cAAc;QAClC,cAAc;KACL,CAAC;IAqCX;;;;;OAKG;IACH,MAAa,IAAK,SAAQ,mBAAmB,CAAC,YAAY,CACzD,EAAE,EACF,eAAe,EACf,CAAC,cAAc,CAAC,EAChB,aAAa,CACb;KAAG;IALS,+BAAI,OAKb,CAAA;AACL,CAAC,EA9HgB,0BAA0B,KAA1B,0BAA0B,QA8H1C","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { enumFromStrings, SchemaFactory, SchemaFactoryBeta } from \"../simple-tree/index.js\";\nimport type {\n\tTreeNodeFromImplicitAllowedTypes,\n\tInsertableTreeNodeFromImplicitAllowedTypes,\n} from \"../simple-tree/index.js\";\n\nimport { FormattedTextAsTree } from \"./textDomainFormatted.js\";\n\n/**\n * Schema factory for default formatted text types which are not generic.\n */\nconst sf = new SchemaFactoryBeta(\"com.fluidframework.text.formatted.default\");\n\nconst defaultFormat = {\n\tbold: false,\n\titalic: false,\n\tunderline: false,\n\tsize: 12,\n\tfont: \"Arial\",\n} as const;\n\n/**\n * A default parameterization of the generic {@link FormattedTextAsTree} with hard-coded assumptions about what kind of embedded content and what kind of formatting is supported.\n * @remarks\n * It is unlikely this meets the needs of most users, but it can serve as an unstable example of how to use the generic {@link FormattedTextAsTree}.\n * @internal\n */\nexport namespace FormattedTextAsTreeDefault {\n\t/**\n\t * Portion of a string with formatting.\n\t * @sealed\n\t * @internal\n\t */\n\texport type FormattedAtom = FormattedTextAsTree.FormattedAtom<\n\t\tCharacterFormat,\n\t\tStringAtomContent\n\t>;\n\n\t/**\n\t * Formatting options for characters.\n\t * @sealed\n\t * @internal\n\t */\n\texport class CharacterFormat extends sf.object(\"CharacterFormat\", {\n\t\tbold: SchemaFactory.boolean,\n\t\titalic: SchemaFactory.boolean,\n\t\tunderline: SchemaFactory.boolean,\n\t\tsize: SchemaFactory.number,\n\t\tfont: SchemaFactory.string,\n\t}) {\n\t\tpublic static readonly defaultFormat = new CharacterFormat(defaultFormat);\n\t}\n\n\t/**\n\t * Tag with which a line in text can be formatted from HTML.\n\t * @internal\n\t */\n\texport const LineTag = enumFromStrings(sf.scopedFactory(\"lineTag\"), [\n\t\t\"h1\",\n\t\t\"h2\",\n\t\t\"h3\",\n\t\t\"h4\",\n\t\t\"h5\",\n\t\t\"li\",\n\t\t\"ol\",\n\t\t\"checked\",\n\t\t\"unchecked\",\n\t\t\"blockquote\",\n\t\t\"codeBlock\",\n\t]);\n\t/**\n\t * {@inheritdoc FormattedTextAsTreeDefault.(LineTag:variable)}\n\t * @sealed\n\t * @internal\n\t */\n\texport type LineTag = TreeNodeFromImplicitAllowedTypes<typeof LineTag.schema>;\n\n\t/**\n\t * Unit in the string representing a new line character with line formatting.\n\t * @remarks\n\t * This aligns with how Quill represents line formatting.\n\t * Quill formats line attributes (headers, list, blockquote, etc... ) on the newline character\n\t * and only lines using this atom can have line-specific formatting.\n\t * The optional indent level mirrors Quill's indent attribute,\n\t * which is applies to the line before the line break.\n\t * Any tagged line can be indented independently.\n\t * @sealed\n\t * @internal\n\t */\n\texport class StringLineAtom extends sf.object(\"StringLineAtom\", {\n\t\ttag: LineTag.schema,\n\t\tindent: SchemaFactory.number,\n\t}) {\n\t\tpublic readonly content = \"\\n\";\n\t}\n\n\t/**\n\t * Types of \"atoms\" that make up the text.\n\t * @sealed\n\t * @internal\n\t */\n\texport const StringAtomContent = [\n\t\tFormattedTextAsTree.StringTextAtom,\n\t\tStringLineAtom,\n\t] as const;\n\t/**\n\t * {@inheritdoc FormattedTextAsTreeDefault.(StringAtomContent:variable)}\n\t * @sealed\n\t * @internal\n\t */\n\texport type StringAtomContent = TreeNodeFromImplicitAllowedTypes<typeof StringAtomContent>;\n\n\t/**\n\t * Statics for text nodes.\n\t * @sealed\n\t * @internal\n\t */\n\texport type Statics<TTree = Tree> = FormattedTextAsTree.Statics<\n\t\tTTree,\n\t\ttypeof CharacterFormat\n\t>;\n\n\t/**\n\t * Insertable shape for a formatted text atom used by {@link FormattedTextAsTree.Members.insertWithFormattingAt}.\n\t * @sealed\n\t * @internal\n\t */\n\texport type FormattedAtomInsertable = FormattedTextAsTree.FormattedAtom<\n\t\tInsertableTreeNodeFromImplicitAllowedTypes<typeof CharacterFormat>,\n\t\tInsertableTreeNodeFromImplicitAllowedTypes<TextAtomSchemas>\n\t>;\n\n\t/**\n\t * Helper for expressing the full set of formatted text atoms for a given schema.\n\t * @privateRemarks\n\t * Eventually this should probably be given a better name and/or made a system type in a system namespace.\n\t * @sealed\n\t * @internal\n\t */\n\texport type TextAtomSchemas = FormattedTextAsTree.TextAtomSchemas<[typeof StringLineAtom]>;\n\n\t/**\n\t * The schema produced using {@link FormattedTextAsTree.createSchema} with hard-coded assumptions\n\t * about what kind of embedded content and what kind of formatting is supported.\n\t * @sealed\n\t * @internal\n\t */\n\texport class Tree extends FormattedTextAsTree.createSchema(\n\t\tsf,\n\t\tCharacterFormat,\n\t\t[StringLineAtom],\n\t\tdefaultFormat,\n\t) {}\n}\n"]}
1
+ {"version":3,"file":"textDomainFormattedDefault.js","sourceRoot":"","sources":["../../src/text/textDomainFormattedDefault.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAM5F,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;GAEG;AACH,MAAM,EAAE,GAAG,IAAI,iBAAiB,CAAC,2CAA2C,CAAC,CAAC;AAE9E,MAAM,aAAa,GAAG;IACrB,IAAI,EAAE,KAAK;IACX,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,OAAO;CACJ,CAAC;AAEX;;;;;GAKG;AACH,MAAM,KAAW,oBAAoB,CAqHpC;AArHD,WAAiB,oBAAoB;IAQpC;;;;OAIG;IACH,MAAa,eAAgB,SAAQ,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE;QACjE,IAAI,EAAE,aAAa,CAAC,OAAO;QAC3B,MAAM,EAAE,aAAa,CAAC,OAAO;QAC7B,SAAS,EAAE,aAAa,CAAC,OAAO;QAChC,IAAI,EAAE,aAAa,CAAC,MAAM;QAC1B,IAAI,EAAE,aAAa,CAAC,MAAM;KAC1B,CAAC;;IACsB,6BAAa,GAAG,IAAI,eAAe,CAAC,aAAa,CAAC,CAAC;IAP9D,oCAAe,kBAQ3B,CAAA;IAED;;;OAGG;IACU,4BAAO,GAAG,eAAe,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE;QACnE,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,SAAS;QACT,WAAW;QACX,YAAY;QACZ,WAAW;KACX,CAAC,CAAC;IAQH;;;;;;;;;;;OAWG;IACH,MAAa,cAAe,SAAQ,EAAE,CAAC,MAAM,CAAC,gBAAgB,EAAE;QAC/D,GAAG,EAAE,qBAAA,OAAO,CAAC,MAAM;QACnB,MAAM,EAAE,aAAa,CAAC,MAAM;KAC5B,CAAC;QAHF;;YAIiB,YAAO,GAAG,IAAI,CAAC;QAChC,CAAC;KAAA;IALY,mCAAc,iBAK1B,CAAA;IAED;;;;OAIG;IACU,sCAAiB,GAAG,CAAC,aAAa,CAAC,cAAc,EAAE,cAAc,CAAU,CAAC;IAkCzF;;;;;OAKG;IACH,MAAa,IAAK,SAAQ,aAAa,CAAC,YAAY,CACnD,EAAE,EACF,eAAe,EACf,CAAC,cAAc,CAAC,EAChB,aAAa,CACb;KAAG;IALS,yBAAI,OAKb,CAAA;AACL,CAAC,EArHgB,oBAAoB,KAApB,oBAAoB,QAqHpC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { enumFromStrings, SchemaFactory, SchemaFactoryBeta } from \"../simple-tree/index.js\";\nimport type {\n\tTreeNodeFromImplicitAllowedTypes,\n\tInsertableTreeNodeFromImplicitAllowedTypes,\n} from \"../simple-tree/index.js\";\n\nimport { FormattedText } from \"./textDomainFormatted.js\";\n\n/**\n * Schema factory for default formatted text types which are not generic.\n */\nconst sf = new SchemaFactoryBeta(\"com.fluidframework.text.formatted.default\");\n\nconst defaultFormat = {\n\tbold: false,\n\titalic: false,\n\tunderline: false,\n\tsize: 12,\n\tfont: \"Arial\",\n} as const;\n\n/**\n * A default parameterization of the generic {@link FormattedText} with hard-coded assumptions about what kind of embedded content and what kind of formatting is supported.\n * @remarks\n * It is unlikely this meets the needs of most users, but it can serve as an unstable example of how to use the generic {@link FormattedText}.\n * @internal\n */\nexport namespace FormattedTextDefault {\n\t/**\n\t * Portion of a string with formatting.\n\t * @sealed\n\t * @internal\n\t */\n\texport type FormattedAtom = FormattedText.FormattedAtom<CharacterFormat, StringAtomContent>;\n\n\t/**\n\t * Formatting options for characters.\n\t * @sealed\n\t * @internal\n\t */\n\texport class CharacterFormat extends sf.object(\"CharacterFormat\", {\n\t\tbold: SchemaFactory.boolean,\n\t\titalic: SchemaFactory.boolean,\n\t\tunderline: SchemaFactory.boolean,\n\t\tsize: SchemaFactory.number,\n\t\tfont: SchemaFactory.string,\n\t}) {\n\t\tpublic static readonly defaultFormat = new CharacterFormat(defaultFormat);\n\t}\n\n\t/**\n\t * Tag with which a line in text can be formatted from HTML.\n\t * @internal\n\t */\n\texport const LineTag = enumFromStrings(sf.scopedFactory(\"lineTag\"), [\n\t\t\"h1\",\n\t\t\"h2\",\n\t\t\"h3\",\n\t\t\"h4\",\n\t\t\"h5\",\n\t\t\"li\",\n\t\t\"ol\",\n\t\t\"checked\",\n\t\t\"unchecked\",\n\t\t\"blockquote\",\n\t\t\"codeBlock\",\n\t]);\n\t/**\n\t * {@inheritdoc FormattedTextDefault.(LineTag:variable)}\n\t * @sealed\n\t * @internal\n\t */\n\texport type LineTag = TreeNodeFromImplicitAllowedTypes<typeof LineTag.schema>;\n\n\t/**\n\t * Unit in the string representing a new line character with line formatting.\n\t * @remarks\n\t * This aligns with how Quill represents line formatting.\n\t * Quill formats line attributes (headers, list, blockquote, etc... ) on the newline character\n\t * and only lines using this atom can have line-specific formatting.\n\t * The optional indent level mirrors Quill's indent attribute,\n\t * which is applies to the line before the line break.\n\t * Any tagged line can be indented independently.\n\t * @sealed\n\t * @internal\n\t */\n\texport class StringLineAtom extends sf.object(\"StringLineAtom\", {\n\t\ttag: LineTag.schema,\n\t\tindent: SchemaFactory.number,\n\t}) {\n\t\tpublic readonly content = \"\\n\";\n\t}\n\n\t/**\n\t * Types of \"atoms\" that make up the text.\n\t * @sealed\n\t * @internal\n\t */\n\texport const StringAtomContent = [FormattedText.StringTextAtom, StringLineAtom] as const;\n\t/**\n\t * {@inheritdoc FormattedTextDefault.(StringAtomContent:variable)}\n\t * @sealed\n\t * @internal\n\t */\n\texport type StringAtomContent = TreeNodeFromImplicitAllowedTypes<typeof StringAtomContent>;\n\n\t/**\n\t * Statics for text nodes.\n\t * @sealed\n\t * @internal\n\t */\n\texport type Statics<TTree = Tree> = FormattedText.Statics<TTree, typeof CharacterFormat>;\n\n\t/**\n\t * Insertable shape for a formatted text atom used by {@link FormattedText.Members.insertWithFormattingAt}.\n\t * @sealed\n\t * @internal\n\t */\n\texport type FormattedAtomInsertable = FormattedText.FormattedAtom<\n\t\tInsertableTreeNodeFromImplicitAllowedTypes<typeof CharacterFormat>,\n\t\tInsertableTreeNodeFromImplicitAllowedTypes<TextAtomSchemas>\n\t>;\n\n\t/**\n\t * Helper for expressing the full set of formatted text atoms for a given schema.\n\t * @privateRemarks\n\t * Eventually this should probably be given a better name and/or made a system type in a system namespace.\n\t * @sealed\n\t * @internal\n\t */\n\texport type TextAtomSchemas = FormattedText.TextAtomSchemas<[typeof StringLineAtom]>;\n\n\t/**\n\t * The schema produced using {@link FormattedText.createSchema} with hard-coded assumptions\n\t * about what kind of embedded content and what kind of formatting is supported.\n\t * @sealed\n\t * @internal\n\t */\n\texport class Tree extends FormattedText.createSchema(\n\t\tsf,\n\t\tCharacterFormat,\n\t\t[StringLineAtom],\n\t\tdefaultFormat,\n\t) {}\n}\n"]}
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { type ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
5
6
  import type { IIdCompressor, OpSpaceCompressedId, SessionId, SessionSpaceCompressedId } from "@fluidframework/id-compressor";
6
7
  /**
7
8
  * An encoded identifier that can be decoded without an originator session id.
@@ -96,6 +97,11 @@ export interface IdentifierHealingConfig {
96
97
  * same session offsets.
97
98
  */
98
99
  readonly sharedObjectId: string;
100
+ /**
101
+ * Optional logger used by {@link forceDecodeEncodedIdWithoutSession} to record telemetry
102
+ * when the heal-on-decode recovery path is taken (a non-final identifier is healed).
103
+ */
104
+ readonly logger?: ITelemetryBaseLogger;
99
105
  }
100
106
  /**
101
107
  * Decode an op-space compressed id without a session.
@@ -106,6 +112,16 @@ export interface IdentifierHealingConfig {
106
112
  * deterministic v5 UUID string — *not* a `StableId`, since that brand requires
107
113
  * v4, but still a valid identifier value; the `string` arm of the return type
108
114
  * covers this case.
115
+ *
116
+ * The recovery (heal) path only occurs because of a prior bug where non-finalized identifiers were
117
+ * written into summaries. When {@link IdentifierHealingConfig.logger} is supplied, a telemetry event
118
+ * is recorded on that path so heals can be observed in the wild. The error/throw path is intentionally
119
+ * not instrumented here: the thrown exception is expected to surface via the application's own error
120
+ * telemetry, and should never be silently swallowed.
121
+ *
122
+ * @param id - The op-space compressed ID to decode.
123
+ * @param idCompressor - The ID compressor used to normalize the ID.
124
+ * @param healing - Heal-on-decode configuration. Presence enables healing of non-final IDs.
109
125
  */
110
126
  export declare function forceDecodeEncodedIdWithoutSession(id: OpSpaceCompressedId, idCompressor: IIdCompressor, healing: IdentifierHealingConfig | undefined): SessionSpaceCompressedId | string;
111
127
  /**
@@ -159,9 +175,13 @@ export interface IdDecoderOptionsWithOriginator {
159
175
  */
160
176
  export declare class IdDecodingContext {
161
177
  /**
162
- * Used internally to prevent the use of this decoder in incremental chunks if it has a session id (which would be wrong in those chunks).
178
+ * Whether this context resolves identifiers using an originator session ID.
179
+ * @remarks
180
+ * Consulted by {@link FieldBatchDecodingContext} to prevent using an originator-based
181
+ * decoder in incremental chunks (which may come from other sessions, making such a
182
+ * decoder wrong there).
163
183
  */
164
- protected readonly hasOriginatorSessionId: boolean;
184
+ readonly hasOriginatorSessionId: boolean;
165
185
  /**
166
186
  * Compressor which can decompress session-space identifiers from {@link resolveEncodedId} as needed.
167
187
  */
@@ -1 +1 @@
1
- {"version":3,"file":"compressedIds.d.ts","sourceRoot":"","sources":["../../src/util/compressedIds.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compressedIds.d.ts","sourceRoot":"","sources":["../../src/util/compressedIds.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { LogLevel } from "@fluidframework/core-interfaces";
5
6
  import { assert } from "@fluidframework/core-utils/internal";
6
7
  import { isFinalId, isStableId } from "@fluidframework/id-compressor/internal";
7
8
  import { v5 as uuidV5 } from "uuid";
@@ -92,6 +93,16 @@ export function tryDecodeEncodedIdWithoutSession(id, idCompressor) {
92
93
  * deterministic v5 UUID string — *not* a `StableId`, since that brand requires
93
94
  * v4, but still a valid identifier value; the `string` arm of the return type
94
95
  * covers this case.
96
+ *
97
+ * The recovery (heal) path only occurs because of a prior bug where non-finalized identifiers were
98
+ * written into summaries. When {@link IdentifierHealingConfig.logger} is supplied, a telemetry event
99
+ * is recorded on that path so heals can be observed in the wild. The error/throw path is intentionally
100
+ * not instrumented here: the thrown exception is expected to surface via the application's own error
101
+ * telemetry, and should never be silently swallowed.
102
+ *
103
+ * @param id - The op-space compressed ID to decode.
104
+ * @param idCompressor - The ID compressor used to normalize the ID.
105
+ * @param healing - Heal-on-decode configuration. Presence enables healing of non-final IDs.
95
106
  */
96
107
  export function forceDecodeEncodedIdWithoutSession(id, idCompressor, healing) {
97
108
  const decoded = tryDecodeEncodedIdWithoutSession(id, idCompressor);
@@ -100,7 +111,17 @@ export function forceDecodeEncodedIdWithoutSession(id, idCompressor, healing) {
100
111
  }
101
112
  // `id` is a non-final op-space compressed id.
102
113
  if (healing !== undefined) {
103
- return uuidV5(`${healing.sharedObjectId}|${id}`, healingNamespace);
114
+ const healed = uuidV5(`${healing.sharedObjectId}|${id}`, healingNamespace);
115
+ healing.logger?.send({
116
+ category: "generic",
117
+ eventName: "HealUnresolvableIdentifierOnDecode",
118
+ },
119
+ // This telemetry should be very low-volume (and possibly never happen at all),
120
+ // as should only occur when loading documents that were corrupted by a now fixed bug and have not been re-summarized since healing was added.
121
+ // It is useful for monitoring and diagnosing for this to be reported in the same cases in which the error case would be reported,
122
+ // which is the essential level.
123
+ LogLevel.essential);
124
+ return healed;
104
125
  }
105
126
  throw new Error("Summary could not be loaded due to an incorrectly encoded identifier. See SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode for mitigation.");
106
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compressedIds.js","sourceRoot":"","sources":["../../src/util/compressedIds.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type {\n\tIIdCompressor,\n\tOpSpaceCompressedId,\n\tSessionId,\n\tSessionSpaceCompressedId,\n} from \"@fluidframework/id-compressor\";\nimport { isFinalId, isStableId } from \"@fluidframework/id-compressor/internal\";\nimport { v5 as uuidV5 } from \"uuid\";\n\n/**\n * An encoded identifier that can be decoded without an originator session id.\n * A finalized compressed id — the same numeric value in op-space and session-space.\n * @remarks\n * These are safe to use in contexts where the encoding id compressor is available to decode them,\n * even when the session id of the encoding id compressor is not known.\n */\nexport type OriginatorlessEncodedId = SessionSpaceCompressedId & OpSpaceCompressedId;\n\n/**\n * An encoded identifier that may require an originator session id to decode.\n * Any op-space compressed id, either finalized or session-local.\n * @remarks\n * These are only safe to use in contexts where the encoding id compressor is available\n * and the session id of the encoding id compressor is known.\n * Currently the only such case is in ops, excluding attach summary ops.\n */\nexport type OriginatorDependentEncodedId = OpSpaceCompressedId;\n\n/**\n * Selects how identifiers are encoded for transport/persistence.\n */\nexport enum EncodedIdType {\n\t/**\n\t * {@link OriginatorlessEncodedId}.\n\t */\n\tOriginatorless,\n\t/**\n\t * {@link OriginatorDependentEncodedId}.\n\t */\n\tOriginatorDependent,\n}\n\nexport type EncodedId<T extends EncodedIdType> = T extends EncodedIdType.Originatorless\n\t? OriginatorlessEncodedId\n\t: T extends EncodedIdType.OriginatorDependent\n\t\t? OriginatorDependentEncodedId\n\t\t: OriginatorlessEncodedId | OriginatorDependentEncodedId;\n\n/**\n * Context for encoding identifiers.\n * @remarks\n * See {@link FieldBatchDecodingContext} for the decoder.\n */\nexport interface IdEncodingContext {\n\tencodePossiblyCompressedId(id: string): string | EncodedId<EncodedIdType>;\n}\n\n/**\n * Encode a string identifier into a possibly compressed form based on the requested mode.\n *\n * @param id - The string identifier to encode.\n * @param idCompressor - The ID compressor to use for compression.\n * @param encodedIdType - The type of encoding to use. If the context doing the decoding will have the originator {@link SessionId},\n * then {@link EncodedIdType.OriginatorDependent} can be used.\n * Otherwise, {@link EncodedIdType.Originatorless} must be used.\n *\n * @privateRemarks\n * Performance:\n * When trying to optimize to avoid unnecessary identifier decompression, and store identifiers compressed in memory,\n * this will likely need to be updated to allow in SessionSpaceCompressedIds.\n */\nexport function encodePossiblyCompressedId<T extends EncodedIdType>(\n\tid: string,\n\tidCompressor: IIdCompressor,\n\tencodedIdType: T,\n): string | EncodedId<T> {\n\tif (!isStableId(id)) {\n\t\treturn id;\n\t}\n\tconst sessionSpaceCompressedId = idCompressor.tryRecompress(id);\n\tif (sessionSpaceCompressedId === undefined) {\n\t\treturn id;\n\t}\n\tconst opSpaceId = idCompressor.normalizeToOpSpace(sessionSpaceCompressedId);\n\tif (encodedIdType === EncodedIdType.Originatorless) {\n\t\treturn isFinalId(opSpaceId) ? (opSpaceId as unknown as EncodedId<T>) : id;\n\t}\n\treturn opSpaceId as unknown as EncodedId<T>;\n}\n\n/**\n * Namespace used for the deterministic UUIDv5 produced by the heal-on-decode workaround\n * in {@link forceDecodeEncodedIdWithoutSession}.\n *\n * This scheme requires consensus across all clients to function — every client that\n * encounters the same unresolvable id must produce the same v5 UUID.\n */\nconst healingNamespace = \"f8a89df3-6882-400f-b913-4c1f6f0157bd\";\n\n/**\n * Decode an {@link OriginatorlessEncodedId} without needing an originator session id.\n *\n * The id is guaranteed to be final by the type contract; it is normalized via\n * {@link IIdCompressor.tryNormalizeToSessionSpaceWithoutSession}, which handles the\n * cluster-aligned-local case rather than relying on the bare numeric value.\n */\nexport function decodeOriginatorlessEncodedId(\n\tid: OriginatorlessEncodedId,\n\tidCompressor: IIdCompressor,\n): SessionSpaceCompressedId {\n\tconst sessionSpaceId = idCompressor.tryNormalizeToSessionSpaceWithoutSession(id);\n\tassert(\n\t\tsessionSpaceId !== undefined,\n\t\t0xd0a /* OriginatorlessEncodedId must be a finalized compressed id at runtime */,\n\t);\n\treturn sessionSpaceId;\n}\n\n/**\n * Decode an {@link OriginatorDependentEncodedId} using a known originator session id.\n */\nexport function decodeEncodedIdWithOriginator(\n\tid: OriginatorDependentEncodedId,\n\toriginator: SessionId,\n\tidCompressor: IIdCompressor,\n): SessionSpaceCompressedId {\n\treturn idCompressor.normalizeToSessionSpace(id, originator);\n}\n\n/**\n * Decode an {@link OriginatorDependentEncodedId} without a session.\n *\n * Returns the session-space id if `id` is finalized (and therefore resolvable\n * without an originator session), or `undefined` if `id` is a non-final op-space\n * id that cannot be resolved without the originator session.\n */\nexport function tryDecodeEncodedIdWithoutSession(\n\tid: OriginatorDependentEncodedId,\n\tidCompressor: IIdCompressor,\n): SessionSpaceCompressedId | undefined {\n\treturn idCompressor.tryNormalizeToSessionSpaceWithoutSession(id);\n}\n\n/**\n * Configuration for the heal-on-decode workaround. The internal counterpart of\n * the user-facing {@link SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode}\n * option — see that option for the user-facing rationale and trade-offs.\n *\n * Carried by decode-side contexts ({@link IdDecoderOptionsOriginatorless},\n * `ChangeEncodingContext.healing`,\n * `EditManagerEncodingContext.healing`, etc.) when the workaround is enabled.\n * Presence enables healing; `undefined` means healing is disabled. There is no separate boolean,\n * which makes it impossible to enable healing without supplying the namespace\n * input.\n */\nexport interface IdentifierHealingConfig {\n\t/**\n\t * The SharedTree's shared-object id, used as the v5 namespace input so\n\t * healed UUIDs are unique across attaches of different documents with the\n\t * same session offsets.\n\t */\n\treadonly sharedObjectId: string;\n}\n\n/**\n * Decode an op-space compressed id without a session.\n *\n * Finalized ids are returned as session-space ids (final ids are invariant\n * across the two spaces). Non-final ids are healed via {@link IdentifierHealingConfig}\n * if provided, or cause the resolver to throw otherwise. A healed result is a\n * deterministic v5 UUID string — *not* a `StableId`, since that brand requires\n * v4, but still a valid identifier value; the `string` arm of the return type\n * covers this case.\n */\nexport function forceDecodeEncodedIdWithoutSession(\n\tid: OpSpaceCompressedId,\n\tidCompressor: IIdCompressor,\n\thealing: IdentifierHealingConfig | undefined,\n): SessionSpaceCompressedId | string {\n\tconst decoded = tryDecodeEncodedIdWithoutSession(id, idCompressor);\n\tif (decoded !== undefined) {\n\t\treturn decoded;\n\t}\n\t// `id` is a non-final op-space compressed id.\n\tif (healing !== undefined) {\n\t\treturn uuidV5(`${healing.sharedObjectId}|${id}`, healingNamespace);\n\t}\n\tthrow new Error(\n\t\t\"Summary could not be loaded due to an incorrectly encoded identifier. See SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode for mitigation.\",\n\t);\n}\n\n/**\n * Convert a decoded, but possibly compressed, identifier to the decompressed string format.\n *\n * Session-space compressed ids are decompressed to their UUID representation via the compressor.\n * String inputs pass through unchanged — they are already in the stored form,\n * whether a `StableId` UUID, a heal-synthesized v5 UUID or other arbitrary string identifier.\n */\nexport function decompressIdentifierIfNeeded(\n\tid: SessionSpaceCompressedId | string,\n\tidCompressor: Pick<IIdCompressor, \"decompress\">,\n): string {\n\tif (typeof id === \"string\") {\n\t\treturn id;\n\t} else {\n\t\tconst decompressed = idCompressor.decompress(id);\n\t\tassert(typeof decompressed === \"string\", 0xd0b /* Decompressed id must be a string */);\n\t\treturn decompressed;\n\t}\n}\n\n/**\n * Options for creating an {@link IdDecodingContext} without an originator.\n * @remarks\n * Decodes {@link OriginatorlessEncodedId} instances, and optionally has a {@link IdentifierHealingConfig} fallback for {@link OriginatorDependentEncodedId} instances.\n */\nexport interface IdDecoderOptionsOriginatorless {\n\treadonly idCompressor: IIdCompressor;\n\t/**\n\t * Optional {@link IdentifierHealingConfig} to use for healing unresolvable identifiers.\n\t * @privateRemarks\n\t * This is a required field which can be undefined to make it explicit when this is not provided.\n\t * This explicitness helps ensure we we don't accidentally drop the healing config as that would break\n\t * an edge case which does not get a lot of testing.\n\t */\n\treadonly healing: IdentifierHealingConfig | undefined;\n}\n\n/**\n * Options for creating an {@link IdDecodingContext} with an originator.\n * @remarks\n * Decodes {@link OriginatorDependentEncodedId} instances.\n */\nexport interface IdDecoderOptionsWithOriginator {\n\treadonly idCompressor: IIdCompressor;\n\t/**\n\t * The ID of the session that encoded the ids.\n\t */\n\treadonly originatorId: SessionId;\n}\n\n/**\n * Context for decoding identifiers.\n * @remarks\n * Depended on how this is constructed, it may or may not handle {@link OriginatorDependentEncodedId} instances.\n *\n * This intentionally avoids exposing anything which depends on the underlying id-compressor's session ID to avoid confusion with the session ID of the compressor which encoded the data.\n * If the session ID of the encoder which encoded the data is known, that information is baked into `resolveEncodedId`.\n *\n * @privateRemarks\n * We have code (FieldBatchDecodingContext) which sometimes has a session ID and sometimes doesn't, so we need a flexible decoding context.\n * Additionally, since this is about serialized data, having stronger types is often counterproductive, as it just relies more on type casts when parsing.\n * This is why we use this flexible pattern rather than code strongly typed over which kind of encoded ids are expected.\n */\nexport class IdDecodingContext {\n\t/**\n\t * Used internally to prevent the use of this decoder in incremental chunks if it has a session id (which would be wrong in those chunks).\n\t */\n\tprotected readonly hasOriginatorSessionId: boolean;\n\n\t/**\n\t * Compressor which can decompress session-space identifiers from {@link resolveEncodedId} as needed.\n\t */\n\tpublic readonly idCompressor: Pick<IIdCompressor, \"decompress\">;\n\n\t/**\n\t * Resolves an encoded op-space identifier to either a session-space ID\n\t * (which {@link idCompressor} can decompress if needed)\n\t * or a string (which passes through unchanged).\n\t * @remarks\n\t * In contexts where non-final identifiers can't be supported (where no originator session is available),\n\t * if a non-final identifier is encountered, this may throw or perform a data healing workaround.\n\t * See {@link FieldBatchDecodingContext.forOp} and {@link FieldBatchDecodingContext.forSummary} for details.\n\t */\n\tpublic readonly resolveEncodedId: (\n\t\tid: OpSpaceCompressedId,\n\t) => SessionSpaceCompressedId | string;\n\n\t/**\n\t * Creates a new instance of the context.\n\t * @param options - The options for creating the context.\n\t */\n\tpublic constructor(\n\t\toptions: IdDecoderOptionsOriginatorless | IdDecoderOptionsWithOriginator,\n\t) {\n\t\tthis.idCompressor = options.idCompressor;\n\t\tif (\"originatorId\" in options) {\n\t\t\tthis.hasOriginatorSessionId = true;\n\t\t\tthis.resolveEncodedId = (id): SessionSpaceCompressedId | string =>\n\t\t\t\toptions.idCompressor.normalizeToSessionSpace(id, options.originatorId);\n\t\t} else {\n\t\t\tthis.hasOriginatorSessionId = false;\n\t\t\tthis.resolveEncodedId = (id): SessionSpaceCompressedId | string =>\n\t\t\t\tforceDecodeEncodedIdWithoutSession(id, options.idCompressor, options.healing);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"compressedIds.js","sourceRoot":"","sources":["../../src/util/compressedIds.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { LogLevel, type ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport type {\n\tIIdCompressor,\n\tOpSpaceCompressedId,\n\tSessionId,\n\tSessionSpaceCompressedId,\n} from \"@fluidframework/id-compressor\";\nimport { isFinalId, isStableId } from \"@fluidframework/id-compressor/internal\";\nimport { v5 as uuidV5 } from \"uuid\";\n\n/**\n * An encoded identifier that can be decoded without an originator session id.\n * A finalized compressed id — the same numeric value in op-space and session-space.\n * @remarks\n * These are safe to use in contexts where the encoding id compressor is available to decode them,\n * even when the session id of the encoding id compressor is not known.\n */\nexport type OriginatorlessEncodedId = SessionSpaceCompressedId & OpSpaceCompressedId;\n\n/**\n * An encoded identifier that may require an originator session id to decode.\n * Any op-space compressed id, either finalized or session-local.\n * @remarks\n * These are only safe to use in contexts where the encoding id compressor is available\n * and the session id of the encoding id compressor is known.\n * Currently the only such case is in ops, excluding attach summary ops.\n */\nexport type OriginatorDependentEncodedId = OpSpaceCompressedId;\n\n/**\n * Selects how identifiers are encoded for transport/persistence.\n */\nexport enum EncodedIdType {\n\t/**\n\t * {@link OriginatorlessEncodedId}.\n\t */\n\tOriginatorless,\n\t/**\n\t * {@link OriginatorDependentEncodedId}.\n\t */\n\tOriginatorDependent,\n}\n\nexport type EncodedId<T extends EncodedIdType> = T extends EncodedIdType.Originatorless\n\t? OriginatorlessEncodedId\n\t: T extends EncodedIdType.OriginatorDependent\n\t\t? OriginatorDependentEncodedId\n\t\t: OriginatorlessEncodedId | OriginatorDependentEncodedId;\n\n/**\n * Context for encoding identifiers.\n * @remarks\n * See {@link FieldBatchDecodingContext} for the decoder.\n */\nexport interface IdEncodingContext {\n\tencodePossiblyCompressedId(id: string): string | EncodedId<EncodedIdType>;\n}\n\n/**\n * Encode a string identifier into a possibly compressed form based on the requested mode.\n *\n * @param id - The string identifier to encode.\n * @param idCompressor - The ID compressor to use for compression.\n * @param encodedIdType - The type of encoding to use. If the context doing the decoding will have the originator {@link SessionId},\n * then {@link EncodedIdType.OriginatorDependent} can be used.\n * Otherwise, {@link EncodedIdType.Originatorless} must be used.\n *\n * @privateRemarks\n * Performance:\n * When trying to optimize to avoid unnecessary identifier decompression, and store identifiers compressed in memory,\n * this will likely need to be updated to allow in SessionSpaceCompressedIds.\n */\nexport function encodePossiblyCompressedId<T extends EncodedIdType>(\n\tid: string,\n\tidCompressor: IIdCompressor,\n\tencodedIdType: T,\n): string | EncodedId<T> {\n\tif (!isStableId(id)) {\n\t\treturn id;\n\t}\n\tconst sessionSpaceCompressedId = idCompressor.tryRecompress(id);\n\tif (sessionSpaceCompressedId === undefined) {\n\t\treturn id;\n\t}\n\tconst opSpaceId = idCompressor.normalizeToOpSpace(sessionSpaceCompressedId);\n\tif (encodedIdType === EncodedIdType.Originatorless) {\n\t\treturn isFinalId(opSpaceId) ? (opSpaceId as unknown as EncodedId<T>) : id;\n\t}\n\treturn opSpaceId as unknown as EncodedId<T>;\n}\n\n/**\n * Namespace used for the deterministic UUIDv5 produced by the heal-on-decode workaround\n * in {@link forceDecodeEncodedIdWithoutSession}.\n *\n * This scheme requires consensus across all clients to function — every client that\n * encounters the same unresolvable id must produce the same v5 UUID.\n */\nconst healingNamespace = \"f8a89df3-6882-400f-b913-4c1f6f0157bd\";\n\n/**\n * Decode an {@link OriginatorlessEncodedId} without needing an originator session id.\n *\n * The id is guaranteed to be final by the type contract; it is normalized via\n * {@link IIdCompressor.tryNormalizeToSessionSpaceWithoutSession}, which handles the\n * cluster-aligned-local case rather than relying on the bare numeric value.\n */\nexport function decodeOriginatorlessEncodedId(\n\tid: OriginatorlessEncodedId,\n\tidCompressor: IIdCompressor,\n): SessionSpaceCompressedId {\n\tconst sessionSpaceId = idCompressor.tryNormalizeToSessionSpaceWithoutSession(id);\n\tassert(\n\t\tsessionSpaceId !== undefined,\n\t\t0xd0a /* OriginatorlessEncodedId must be a finalized compressed id at runtime */,\n\t);\n\treturn sessionSpaceId;\n}\n\n/**\n * Decode an {@link OriginatorDependentEncodedId} using a known originator session id.\n */\nexport function decodeEncodedIdWithOriginator(\n\tid: OriginatorDependentEncodedId,\n\toriginator: SessionId,\n\tidCompressor: IIdCompressor,\n): SessionSpaceCompressedId {\n\treturn idCompressor.normalizeToSessionSpace(id, originator);\n}\n\n/**\n * Decode an {@link OriginatorDependentEncodedId} without a session.\n *\n * Returns the session-space id if `id` is finalized (and therefore resolvable\n * without an originator session), or `undefined` if `id` is a non-final op-space\n * id that cannot be resolved without the originator session.\n */\nexport function tryDecodeEncodedIdWithoutSession(\n\tid: OriginatorDependentEncodedId,\n\tidCompressor: IIdCompressor,\n): SessionSpaceCompressedId | undefined {\n\treturn idCompressor.tryNormalizeToSessionSpaceWithoutSession(id);\n}\n\n/**\n * Configuration for the heal-on-decode workaround. The internal counterpart of\n * the user-facing {@link SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode}\n * option — see that option for the user-facing rationale and trade-offs.\n *\n * Carried by decode-side contexts ({@link IdDecoderOptionsOriginatorless},\n * `ChangeEncodingContext.healing`,\n * `EditManagerEncodingContext.healing`, etc.) when the workaround is enabled.\n * Presence enables healing; `undefined` means healing is disabled. There is no separate boolean,\n * which makes it impossible to enable healing without supplying the namespace\n * input.\n */\nexport interface IdentifierHealingConfig {\n\t/**\n\t * The SharedTree's shared-object id, used as the v5 namespace input so\n\t * healed UUIDs are unique across attaches of different documents with the\n\t * same session offsets.\n\t */\n\treadonly sharedObjectId: string;\n\n\t/**\n\t * Optional logger used by {@link forceDecodeEncodedIdWithoutSession} to record telemetry\n\t * when the heal-on-decode recovery path is taken (a non-final identifier is healed).\n\t */\n\treadonly logger?: ITelemetryBaseLogger;\n}\n\n/**\n * Decode an op-space compressed id without a session.\n *\n * Finalized ids are returned as session-space ids (final ids are invariant\n * across the two spaces). Non-final ids are healed via {@link IdentifierHealingConfig}\n * if provided, or cause the resolver to throw otherwise. A healed result is a\n * deterministic v5 UUID string — *not* a `StableId`, since that brand requires\n * v4, but still a valid identifier value; the `string` arm of the return type\n * covers this case.\n *\n * The recovery (heal) path only occurs because of a prior bug where non-finalized identifiers were\n * written into summaries. When {@link IdentifierHealingConfig.logger} is supplied, a telemetry event\n * is recorded on that path so heals can be observed in the wild. The error/throw path is intentionally\n * not instrumented here: the thrown exception is expected to surface via the application's own error\n * telemetry, and should never be silently swallowed.\n *\n * @param id - The op-space compressed ID to decode.\n * @param idCompressor - The ID compressor used to normalize the ID.\n * @param healing - Heal-on-decode configuration. Presence enables healing of non-final IDs.\n */\nexport function forceDecodeEncodedIdWithoutSession(\n\tid: OpSpaceCompressedId,\n\tidCompressor: IIdCompressor,\n\thealing: IdentifierHealingConfig | undefined,\n): SessionSpaceCompressedId | string {\n\tconst decoded = tryDecodeEncodedIdWithoutSession(id, idCompressor);\n\tif (decoded !== undefined) {\n\t\treturn decoded;\n\t}\n\t// `id` is a non-final op-space compressed id.\n\tif (healing !== undefined) {\n\t\tconst healed = uuidV5(`${healing.sharedObjectId}|${id}`, healingNamespace);\n\t\thealing.logger?.send(\n\t\t\t{\n\t\t\t\tcategory: \"generic\",\n\t\t\t\teventName: \"HealUnresolvableIdentifierOnDecode\",\n\t\t\t},\n\t\t\t// This telemetry should be very low-volume (and possibly never happen at all),\n\t\t\t// as should only occur when loading documents that were corrupted by a now fixed bug and have not been re-summarized since healing was added.\n\t\t\t// It is useful for monitoring and diagnosing for this to be reported in the same cases in which the error case would be reported,\n\t\t\t// which is the essential level.\n\t\t\tLogLevel.essential,\n\t\t);\n\t\treturn healed;\n\t}\n\tthrow new Error(\n\t\t\"Summary could not be loaded due to an incorrectly encoded identifier. See SharedTreeOptionsBeta.healUnresolvableIdentifiersOnDecode for mitigation.\",\n\t);\n}\n\n/**\n * Convert a decoded, but possibly compressed, identifier to the decompressed string format.\n *\n * Session-space compressed ids are decompressed to their UUID representation via the compressor.\n * String inputs pass through unchanged — they are already in the stored form,\n * whether a `StableId` UUID, a heal-synthesized v5 UUID or other arbitrary string identifier.\n */\nexport function decompressIdentifierIfNeeded(\n\tid: SessionSpaceCompressedId | string,\n\tidCompressor: Pick<IIdCompressor, \"decompress\">,\n): string {\n\tif (typeof id === \"string\") {\n\t\treturn id;\n\t} else {\n\t\tconst decompressed = idCompressor.decompress(id);\n\t\tassert(typeof decompressed === \"string\", 0xd0b /* Decompressed id must be a string */);\n\t\treturn decompressed;\n\t}\n}\n\n/**\n * Options for creating an {@link IdDecodingContext} without an originator.\n * @remarks\n * Decodes {@link OriginatorlessEncodedId} instances, and optionally has a {@link IdentifierHealingConfig} fallback for {@link OriginatorDependentEncodedId} instances.\n */\nexport interface IdDecoderOptionsOriginatorless {\n\treadonly idCompressor: IIdCompressor;\n\t/**\n\t * Optional {@link IdentifierHealingConfig} to use for healing unresolvable identifiers.\n\t * @privateRemarks\n\t * This is a required field which can be undefined to make it explicit when this is not provided.\n\t * This explicitness helps ensure we we don't accidentally drop the healing config as that would break\n\t * an edge case which does not get a lot of testing.\n\t */\n\treadonly healing: IdentifierHealingConfig | undefined;\n}\n\n/**\n * Options for creating an {@link IdDecodingContext} with an originator.\n * @remarks\n * Decodes {@link OriginatorDependentEncodedId} instances.\n */\nexport interface IdDecoderOptionsWithOriginator {\n\treadonly idCompressor: IIdCompressor;\n\t/**\n\t * The ID of the session that encoded the ids.\n\t */\n\treadonly originatorId: SessionId;\n}\n\n/**\n * Context for decoding identifiers.\n * @remarks\n * Depended on how this is constructed, it may or may not handle {@link OriginatorDependentEncodedId} instances.\n *\n * This intentionally avoids exposing anything which depends on the underlying id-compressor's session ID to avoid confusion with the session ID of the compressor which encoded the data.\n * If the session ID of the encoder which encoded the data is known, that information is baked into `resolveEncodedId`.\n *\n * @privateRemarks\n * We have code (FieldBatchDecodingContext) which sometimes has a session ID and sometimes doesn't, so we need a flexible decoding context.\n * Additionally, since this is about serialized data, having stronger types is often counterproductive, as it just relies more on type casts when parsing.\n * This is why we use this flexible pattern rather than code strongly typed over which kind of encoded ids are expected.\n */\nexport class IdDecodingContext {\n\t/**\n\t * Whether this context resolves identifiers using an originator session ID.\n\t * @remarks\n\t * Consulted by {@link FieldBatchDecodingContext} to prevent using an originator-based\n\t * decoder in incremental chunks (which may come from other sessions, making such a\n\t * decoder wrong there).\n\t */\n\tpublic readonly hasOriginatorSessionId: boolean;\n\n\t/**\n\t * Compressor which can decompress session-space identifiers from {@link resolveEncodedId} as needed.\n\t */\n\tpublic readonly idCompressor: Pick<IIdCompressor, \"decompress\">;\n\n\t/**\n\t * Resolves an encoded op-space identifier to either a session-space ID\n\t * (which {@link idCompressor} can decompress if needed)\n\t * or a string (which passes through unchanged).\n\t * @remarks\n\t * In contexts where non-final identifiers can't be supported (where no originator session is available),\n\t * if a non-final identifier is encountered, this may throw or perform a data healing workaround.\n\t * See {@link FieldBatchDecodingContext.forOp} and {@link FieldBatchDecodingContext.forSummary} for details.\n\t */\n\tpublic readonly resolveEncodedId: (\n\t\tid: OpSpaceCompressedId,\n\t) => SessionSpaceCompressedId | string;\n\n\t/**\n\t * Creates a new instance of the context.\n\t * @param options - The options for creating the context.\n\t */\n\tpublic constructor(\n\t\toptions: IdDecoderOptionsOriginatorless | IdDecoderOptionsWithOriginator,\n\t) {\n\t\tthis.idCompressor = options.idCompressor;\n\t\tif (\"originatorId\" in options) {\n\t\t\tthis.hasOriginatorSessionId = true;\n\t\t\tthis.resolveEncodedId = (id): SessionSpaceCompressedId | string =>\n\t\t\t\toptions.idCompressor.normalizeToSessionSpace(id, options.originatorId);\n\t\t} else {\n\t\t\tthis.hasOriginatorSessionId = false;\n\t\t\tthis.resolveEncodedId = (id): SessionSpaceCompressedId | string =>\n\t\t\t\tforceDecodeEncodedIdWithoutSession(id, options.idCompressor, options.healing);\n\t\t}\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/tree",
3
- "version": "2.114.0",
3
+ "version": "2.115.0",
4
4
  "description": "Distributed tree",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -101,17 +101,17 @@
101
101
  "temp-directory": "nyc/.nyc_output"
102
102
  },
103
103
  "dependencies": {
104
- "@fluid-internal/client-utils": "~2.114.0",
105
- "@fluidframework/core-interfaces": "~2.114.0",
106
- "@fluidframework/core-utils": "~2.114.0",
107
- "@fluidframework/datastore-definitions": "~2.114.0",
108
- "@fluidframework/driver-definitions": "~2.114.0",
109
- "@fluidframework/id-compressor": "~2.114.0",
110
- "@fluidframework/runtime-definitions": "~2.114.0",
111
- "@fluidframework/runtime-utils": "~2.114.0",
112
- "@fluidframework/shared-object-base": "~2.114.0",
113
- "@fluidframework/telemetry-utils": "~2.114.0",
114
- "@fluidframework/type-factory": "~2.114.0",
104
+ "@fluid-internal/client-utils": "~2.115.0",
105
+ "@fluidframework/core-interfaces": "~2.115.0",
106
+ "@fluidframework/core-utils": "~2.115.0",
107
+ "@fluidframework/datastore-definitions": "~2.115.0",
108
+ "@fluidframework/driver-definitions": "~2.115.0",
109
+ "@fluidframework/id-compressor": "~2.115.0",
110
+ "@fluidframework/runtime-definitions": "~2.115.0",
111
+ "@fluidframework/runtime-utils": "~2.115.0",
112
+ "@fluidframework/shared-object-base": "~2.115.0",
113
+ "@fluidframework/telemetry-utils": "~2.115.0",
114
+ "@fluidframework/type-factory": "~2.115.0",
115
115
  "@sinclair/typebox": "^0.34.13",
116
116
  "@tylerbu/sorted-btree-es6": "^2.1.1",
117
117
  "semver-ts": "^1.0.3",
@@ -121,21 +121,21 @@
121
121
  "devDependencies": {
122
122
  "@arethetypeswrong/cli": "^0.18.2",
123
123
  "@biomejs/biome": "~2.4.5",
124
- "@fluid-internal/mocha-test-setup": "~2.114.0",
125
- "@fluid-private/stochastic-test-utils": "~2.114.0",
126
- "@fluid-private/test-dds-utils": "~2.114.0",
127
- "@fluid-private/test-drivers": "~2.114.0",
124
+ "@fluid-internal/mocha-test-setup": "~2.115.0",
125
+ "@fluid-private/stochastic-test-utils": "~2.115.0",
126
+ "@fluid-private/test-dds-utils": "~2.115.0",
127
+ "@fluid-private/test-drivers": "~2.115.0",
128
128
  "@fluid-tools/benchmark": "^0.59.0",
129
129
  "@fluid-tools/build-cli": "^0.65.0",
130
130
  "@fluidframework/build-common": "^2.0.3",
131
131
  "@fluidframework/build-tools": "^0.65.0",
132
- "@fluidframework/container-definitions": "~2.114.0",
133
- "@fluidframework/container-loader": "~2.114.0",
134
- "@fluidframework/container-runtime": "~2.114.0",
132
+ "@fluidframework/container-definitions": "~2.115.0",
133
+ "@fluidframework/container-loader": "~2.115.0",
134
+ "@fluidframework/container-runtime": "~2.115.0",
135
135
  "@fluidframework/eslint-config-fluid": "^13.0.0",
136
- "@fluidframework/local-driver": "~2.114.0",
137
- "@fluidframework/test-runtime-utils": "~2.114.0",
138
- "@fluidframework/test-utils": "~2.114.0",
136
+ "@fluidframework/local-driver": "~2.115.0",
137
+ "@fluidframework/test-runtime-utils": "~2.115.0",
138
+ "@fluidframework/test-utils": "~2.115.0",
139
139
  "@fluidframework/tree-previous": "npm:@fluidframework/tree@2.112.0",
140
140
  "@microsoft/api-extractor": "7.58.1",
141
141
  "@types/diff": "^3.5.1",
@@ -159,6 +159,9 @@
159
159
  "rimraf": "^6.1.3",
160
160
  "typescript": "~5.4.5"
161
161
  },
162
+ "comments": [
163
+ "Tree test builds and eslint depend on the corresponding ID Compressor test builds because Tree tests import its internal/test-utils export, which resolves to generated test output."
164
+ ],
162
165
  "fluidBuild": {
163
166
  "tasks": {
164
167
  "build:test:cjs": [
@@ -171,6 +174,10 @@
171
174
  ],
172
175
  "ci:build:docs": [
173
176
  "build:esnext"
177
+ ],
178
+ "eslint": [
179
+ "...",
180
+ "@fluidframework/id-compressor#build:test:esm"
174
181
  ]
175
182
  }
176
183
  },
package/src/core/index.ts CHANGED
@@ -182,6 +182,8 @@ export {
182
182
  type GraphCommit,
183
183
  CommitKind,
184
184
  type CommitMetadata,
185
+ CommitOutcome,
186
+ type LocalCommitEvents,
185
187
  type LocalChangeMetadata,
186
188
  type RemoteChangeMetadata,
187
189
  type ChangeMetadata,
@@ -12,6 +12,8 @@ export {
12
12
  type GraphCommit,
13
13
  CommitKind,
14
14
  type CommitMetadata,
15
+ CommitOutcome,
16
+ type LocalCommitEvents,
15
17
  type LocalChangeMetadata,
16
18
  type RemoteChangeMetadata,
17
19
  type ChangeMetadata,
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import type { Listenable } from "@fluidframework/core-interfaces";
6
7
  import type {
7
8
  OpSpaceCompressedId,
8
9
  SessionId,
@@ -271,6 +272,72 @@ export interface LocalChangeMetadata extends CommitMetadata {
271
272
  * ```
272
273
  */
273
274
  readonly labels: TransactionLabels;
275
+
276
+ /**
277
+ * Events related to a local change that has been applied.
278
+ */
279
+ readonly events: Listenable<LocalCommitEvents>;
280
+ }
281
+
282
+ /**
283
+ * Events related to a local commit that has been applied.
284
+ * @sealed @alpha
285
+ */
286
+ export interface LocalCommitEvents {
287
+ /**
288
+ * Fired once a commit has been ordered by the sequencing service.
289
+ * @param outcome - information about what changes from the commit were applied or not
290
+ *
291
+ * @remarks
292
+ * Once a commit is sequenced, the following guarantees hold:
293
+ * 1. The changes carried by the commit have been persisted and other peers are able to see them.
294
+ * 2. There can be no more concurrent changes sequenced before this commit, which means this commit has reached its settled form.
295
+ *
296
+ * This event can be used by applications to inform the end user that their changes have been saved (`CommitOutcome.FullyApplied`) or rejected (`CommitOutcome.FullyDropped` and `CommitOutcome.NewContentOnly`).
297
+ * It can also be used to queue up a new attempt at making the rejected changes. Note however that new edits must be made outside of the event callback.
298
+ * @example Notifying the user of the outcome and allowing them to retry:
299
+ * ```typescript
300
+ * // Use `asAlpha` API to access the settled event API
301
+ * const view = asAlpha(tree.viewWith(config));
302
+ *
303
+ * // Function to clear all contents of the tree, with a precondition that no changes have occurred.
304
+ * const clearAllContents = () => {
305
+ * view.runTransaction(
306
+ * () => {
307
+ * // Remove all contents at the root
308
+ * view.root.removeRange();
309
+ * },
310
+ * { preconditions: [{ type: "noChange" }] },
311
+ * );
312
+ * };
313
+ *
314
+ * // Register the logic for notifying the user of the outcome and allow them to retry
315
+ * view.events.on("changed", (metadata) => {
316
+ * if (metadata.isLocal) {
317
+ * metadata.events.on("settled", (outcome) => {
318
+ * if (outcome === CommitOutcome.FullyApplied) {
319
+ * alert("Clear operation succeeded.");
320
+ * } else {
321
+ * const shouldTryAgain = confirm(
322
+ * "The contents have changed. Do you still want to clear everything?",
323
+ * );
324
+ * if (shouldTryAgain) {
325
+ * // It is invalid to make edits during the event callback, so we schedule the retry to occur asynchronously.
326
+ * setTimeout(clearAllContents);
327
+ * } else {
328
+ * alert("Clear operation aborted.");
329
+ * }
330
+ * }
331
+ * });
332
+ * }
333
+ * });
334
+ *
335
+ * // First attempt to clear all contents.
336
+ * // This will synchronously trigger the changed "event" and register the listener for the settled event.
337
+ * clearAllContents();
338
+ * ```
339
+ */
340
+ settled(outcome: CommitOutcome): void;
274
341
  }
275
342
 
276
343
  /**
@@ -311,6 +378,52 @@ export interface LabelTree {
311
378
  */
312
379
  export type TransactionLabels = Set<unknown> & { tree?: LabelTree };
313
380
 
381
+ /**
382
+ * Details about what changes from a commit were applied or not.
383
+ * @alpha
384
+ */
385
+ export enum CommitOutcome {
386
+ /**
387
+ * All of the changes in the commit were applied.
388
+ */
389
+ FullyApplied,
390
+ /**
391
+ * None of the changes in the commit were applied.
392
+ * @remarks
393
+ * This occurs when an implicit constraint has been violated.
394
+ * Implicit constraints are those that are automatically enforced by SharedTree on all changes.
395
+ *
396
+ * Such a violation typically arises in one of two scenarios:
397
+ * 1. A schema change conflicts with a concurrent data or schema change that was sequenced before it.
398
+ * 2. A data change conflicts with a concurrent schema change that was sequenced before it.
399
+ */
400
+ FullyDropped,
401
+ /**
402
+ * Only the creation of new content was applied.
403
+ * All other changes (including the insertion and/or modification of the new content) were dropped.
404
+ * @remarks
405
+ * This occurs when at least one explicit constraint has been violated
406
+ * (and no implicit constraints were violated.)
407
+ *
408
+ * Explicit constraints are those that are explicitly added
409
+ * through {@link RunTransactionParamsAlpha.preconditions | preconditions}
410
+ * or {@link TransactionCallbackStatusAlpha.preconditionsOnRevert | preconditionsOnRevert}.
411
+ *
412
+ * The new content may be edited (and potentially inserted) by subsequent commits,
413
+ * assuming those commits are not themselves subject to constraint violations.
414
+ * Note that, if left uninserted, new content will eventually be garbage-collected from the document.
415
+ *
416
+ * Applications typically choose to treat this outcome as equivalent to {@link CommitOutcome.FullyDropped | FullyDropped}
417
+ * and, when reattempting the change, generate a different copy of the new content if any.
418
+ *
419
+ * @privateRemarks
420
+ * New content is preserved so that subsequent commits that reference it without having to carry their own copies of it.
421
+ * This can become expensive: one extra copy per subsequent commit, included in both in cases where we know the prior commit was dropped and in cases where we don't yet know.
422
+ * We could instead drop all subsequent commits that reference the new content, but that would create a greater risk of data loss.
423
+ */
424
+ NewContentOnly,
425
+ }
426
+
314
427
  /**
315
428
  * Information about a change that has been applied by a remote client.
316
429
  * @sealed @alpha
@@ -178,6 +178,7 @@ export {
178
178
  ChangeMetadata,
179
179
  CodecName,
180
180
  CodecWriteOptions,
181
+ CommitOutcome,
181
182
  Component,
182
183
  CreateIndependentTreeAlphaOptions,
183
184
  DirtyTreeMap,
@@ -197,6 +198,7 @@ export {
197
198
  FormatValidatorBasic,
198
199
  FormatValidatorNoOp,
199
200
  FormatVersion,
201
+ FormattedText,
200
202
  HandleConverter,
201
203
  ICodecOptions,
202
204
  ITreeAlpha,
@@ -229,6 +231,7 @@ export {
229
231
  JsonTreeSchema,
230
232
  LabelTree,
231
233
  LocalChangeMetadata,
234
+ LocalCommitEvents,
232
235
  MapNodeCustomizableSchema,
233
236
  MapNodeCustomizableSchemaUnsafe,
234
237
  MapNodePojoEmulationSchema,
@@ -244,6 +247,7 @@ export {
244
247
  ObjectNodeSchemaWorkaround,
245
248
  ObjectSchemaOptionsAlpha,
246
249
  ObservationResults,
250
+ PlainText,
247
251
  ReadSchema,
248
252
  ReadableField,
249
253
  RecordNodeCustomizableSchema,
@@ -256,9 +260,9 @@ export {
256
260
  SchemaFactoryAlpha,
257
261
  SchemaStaticsAlpha,
258
262
  SchemaType,
259
- SharedTreeAlpha,
260
- SharedTreeFormatOptions,
261
- SharedTreeOptions,
263
+ SharedTreeAlpha,
264
+ SharedTreeFormatOptions,
265
+ SharedTreeOptions,
262
266
  SimpleAllowedTypeAttributes,
263
267
  SimpleArrayNodeSchema,
264
268
  SimpleFieldSchema,
@@ -272,7 +276,7 @@ export {
272
276
  SimpleTreeSchema,
273
277
  StagedSchemaUpgradePolicy,
274
278
  StagedSchemaUpgradePolicyFactory,
275
- TextAsTree,
279
+ StringSchema,
276
280
  TransactionCallbackStatusAlpha,
277
281
  TransactionConstraintAlpha,
278
282
  TransactionLabels,
@@ -309,6 +313,7 @@ export {
309
313
  createArrayInsertionAnchor,
310
314
  createIndependentTreeAlpha,
311
315
  decodeSchemaCompatibilitySnapshot,
316
+ defineTreeDataStore,
312
317
  encodeSchemaCompatibilitySnapshot,
313
318
  eraseSchemaDetails,
314
319
  eraseSchemaDetailsSubclassable,
@@ -330,7 +335,6 @@ export {
330
335
  replaceConciseTreeHandles,
331
336
  replaceHandles,
332
337
  replaceVerboseTreeHandles,
333
- trackDirtyNodes,
334
- defineTreeDataStore
338
+ trackDirtyNodes
335
339
  // #endregion
336
340
  } from "../index.js";
@@ -133,10 +133,10 @@ export interface FieldBatchEncodingContext {
133
133
  /**
134
134
  * Decode-side context for {@link FieldBatchCodec}.
135
135
  *
136
- * Carries the per-call `resolveEncodedId` function that encapsulates the
137
- * originator-session lookup and (for the forest-summarizer's legacy heal path)
138
- * the deterministic UUIDv5 synthesis. Heal and originator-session flags live
139
- * inside that function, not on this context.
136
+ * Composes an {@link IdDecodingContext} (which carries the per-call `resolveEncodedId`
137
+ * function encapsulating the originator-session lookup and, for the forest-summarizer's
138
+ * legacy heal path, the deterministic UUIDv5 synthesis) together with the optional
139
+ * incremental decoder for this batch.
140
140
  *
141
141
  * Constructed via one of the two named static factories — {@link forOp} or
142
142
  * {@link forSummary} — depending on the call site's semantics. The constructor
@@ -144,18 +144,19 @@ export interface FieldBatchEncodingContext {
144
144
  * op-style and summary-style decoding is load-bearing (different invariants
145
145
  * apply, and bugs in this area are typically the result of conflating them).
146
146
  */
147
- export class FieldBatchDecodingContext extends IdDecodingContext {
147
+ export class FieldBatchDecodingContext {
148
148
  private constructor(
149
- private readonly options: IdDecoderOptionsOriginatorless | IdDecoderOptionsWithOriginator,
149
+ /**
150
+ * Resolves the encoded identifiers contained in this batch.
151
+ */
152
+ public readonly idDecodingContext: IdDecodingContext,
150
153
  /**
151
154
  * Decoder for incremental fields. Defined when the encoded batch contains
152
155
  * incremental chunks. Only populated on summary-style contexts; op-style
153
156
  * contexts always have this undefined.
154
157
  */
155
158
  public readonly incrementalDecoder?: IncrementalDecoder,
156
- ) {
157
- super(options);
158
- }
159
+ ) {}
159
160
 
160
161
  /**
161
162
  * Construct a decode context for an op.
@@ -167,7 +168,7 @@ export class FieldBatchDecodingContext extends IdDecodingContext {
167
168
  * a UUID. Incremental decoding is not used for ops.
168
169
  */
169
170
  public static forOp(options: IdDecoderOptionsWithOriginator): FieldBatchDecodingContext {
170
- return new FieldBatchDecodingContext(options);
171
+ return new FieldBatchDecodingContext(new IdDecodingContext(options));
171
172
  }
172
173
 
173
174
  /**
@@ -189,7 +190,7 @@ export class FieldBatchDecodingContext extends IdDecodingContext {
189
190
  public static forSummary(
190
191
  options: IdDecoderOptionsOriginatorless | IdDecoderOptionsWithOriginator,
191
192
  ): FieldBatchDecodingContext {
192
- return new FieldBatchDecodingContext(options);
193
+ return new FieldBatchDecodingContext(new IdDecodingContext(options));
193
194
  }
194
195
 
195
196
  /**
@@ -206,10 +207,10 @@ export class FieldBatchDecodingContext extends IdDecodingContext {
206
207
  // This mitigates the risk of using incorrect originator session ID identifiers in incremental chunks.
207
208
  // See also private remarks on forSummary.
208
209
  assert(
209
- !this.hasOriginatorSessionId,
210
+ !this.idDecodingContext.hasOriginatorSessionId,
210
211
  0xd0c /* withIncrementalDecoder can only be called on contexts without an originator session ID */,
211
212
  );
212
- return new FieldBatchDecodingContext(this.options, incrementalDecoder);
213
+ return new FieldBatchDecodingContext(this.idDecodingContext, incrementalDecoder);
213
214
  }
214
215
  }
215
216
 
@@ -298,7 +299,9 @@ function makeFieldBatchCodecForVersion(
298
299
  context: FieldBatchDecodingContext,
299
300
  ): FieldBatch => {
300
301
  // TODO: consider checking data is in schema.
301
- return decode(data, context, context.incrementalDecoder).map((chunk) => chunk.cursor());
302
+ return decode(data, context.idDecodingContext, context.incrementalDecoder).map((chunk) =>
303
+ chunk.cursor(),
304
+ );
302
305
  },
303
306
  schema: encodedFieldBatchType,
304
307
  };