@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
package/src/index.ts CHANGED
@@ -12,6 +12,8 @@ export {
12
12
  CommitKind,
13
13
  RevertibleStatus,
14
14
  type CommitMetadata,
15
+ CommitOutcome,
16
+ type LocalCommitEvents,
15
17
  type LocalChangeMetadata,
16
18
  type RemoteChangeMetadata,
17
19
  type ChangeMetadata,
@@ -300,6 +302,7 @@ export {
300
302
  type HandleConverter,
301
303
  allowUnused,
302
304
  type LeafSchema,
305
+ type StringSchema,
303
306
  type ArrayNodeCustomizableSchema,
304
307
  type ArrayNodeCustomizableSchemaAlpha,
305
308
  type ArrayNodePojoEmulationSchema,
@@ -404,7 +407,7 @@ import * as InternalTypes from "./internalTypes.js";
404
407
  export { InternalTypes };
405
408
 
406
409
  // Internal/System types:
407
- // These would be put in `internalTypes` except doing so tents to cause errors like:
410
+ // These would be put in `internalTypes` except doing so tends to cause errors like:
408
411
  // The inferred type of 'NodeMap' cannot be named without a reference to '../../node_modules/@fluidframework/tree/lib/internalTypes.js'. This is likely not portable. A type annotation is necessary.
409
412
  export type { MapNodeInsertableData } from "./simple-tree/index.js";
410
413
 
@@ -414,9 +417,9 @@ export { TableSchema, type System_TableSchema } from "./tableSchema.js";
414
417
  export { asAlpha, asBeta } from "./api.js";
415
418
 
416
419
  export {
417
- TextAsTree,
418
- FormattedTextAsTree,
419
- FormattedTextAsTreeDefault,
420
+ PlainText,
421
+ FormattedText,
422
+ FormattedTextDefault,
420
423
  codePointCount,
421
424
  utf16LengthForCodePoints,
422
425
  } from "./text/index.js";
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tree";
9
- export const pkgVersion = "2.114.0";
9
+ export const pkgVersion = "2.115.0";
@@ -252,7 +252,7 @@ export class SharedTreeKernel
252
252
  idCompressor,
253
253
  healing:
254
254
  options.healUnresolvableIdentifiersOnDecode === true
255
- ? { sharedObjectId: sharedObject.id }
255
+ ? { sharedObjectId: sharedObject.id, logger }
256
256
  : undefined,
257
257
  });
258
258
  const forestSummarizer = new ForestSummarizer(
@@ -602,6 +602,11 @@ export interface SharedTreeOptionsBeta extends ForestOptions, Partial<CodecWrite
602
602
  * Healed identifiers are written back out at the next summary in their stable UUID form,
603
603
  * so the inconsistency does not need to be re-healed on every load.
604
604
  *
605
+ * When this flag is enabled, each time an unresolvable identifier is healed a
606
+ * `HealUnresolvableIdentifierOnDecode` telemetry event is emitted (at
607
+ * {@link @fluidframework/core-interfaces#LogLevelConst.info | LogLevel.info}) through the shared
608
+ * object's logger, allowing applications to detect which documents actually required healing.
609
+ *
605
610
  * Off by default because enabling it for documents that are not actually corrupt
606
611
  * would mask genuine bugs that otherwise surface as decode failures.
607
612
  *
@@ -4,7 +4,11 @@
4
4
  */
5
5
 
6
6
  import { createEmitter } from "@fluid-internal/client-utils";
7
- import type { IFluidHandle, Listenable } from "@fluidframework/core-interfaces/internal";
7
+ import type {
8
+ IEmitter,
9
+ IFluidHandle,
10
+ Listenable,
11
+ } from "@fluidframework/core-interfaces/internal";
8
12
  import { assert, unreachableCase, fail } from "@fluidframework/core-utils/internal";
9
13
  import type { IIdCompressor } from "@fluidframework/id-compressor";
10
14
  import { type TelemetryLoggerExt, UsageError } from "@fluidframework/telemetry-utils/internal";
@@ -21,6 +25,7 @@ import {
21
25
  type AnchorSetRootEvents,
22
26
  type ChangeFamily,
23
27
  CommitKind,
28
+ CommitOutcome,
24
29
  type DeltaVisitor,
25
30
  type DetachedFieldIndex,
26
31
  type IEditableForest,
@@ -57,6 +62,7 @@ import {
57
62
  deltaFieldMapHasVisibleChanges,
58
63
  findCommonAncestor,
59
64
  rebaseBranch,
65
+ type LocalCommitEvents,
60
66
  } from "../core/index.js";
61
67
  import {
62
68
  type FieldBatchCodec,
@@ -115,7 +121,12 @@ import {
115
121
  import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
116
122
  import { SharedTreeChangeEnricher } from "./sharedTreeChangeEnricher.js";
117
123
  import type { SharedTreeChangeProcessingContext } from "./sharedTreeChangeFamily.js";
118
- import { SharedTreeChangeFamily, hasSchemaChange } from "./sharedTreeChangeFamily.js";
124
+ import {
125
+ ConstraintStatus,
126
+ SharedTreeChangeFamily,
127
+ getConstraintStatus,
128
+ hasSchemaChange,
129
+ } from "./sharedTreeChangeFamily.js";
119
130
  import type { SharedTreeChange } from "./sharedTreeChangeTypes.js";
120
131
  import type { ISharedTreeEditor, SharedTreeEditBuilder } from "./sharedTreeEditBuilder.js";
121
132
  import { extractTransactionChangeProcessor } from "./transactionPostProcessor.js";
@@ -500,6 +511,14 @@ export class TreeCheckout implements ITreeCheckout {
500
511
 
501
512
  private readonly views = new Set<TreeView<ImplicitFieldSchema>>();
502
513
 
514
+ /**
515
+ * Event emitters for local commits.
516
+ */
517
+ private readonly localCommitEventEmitters = new Map<
518
+ RevisionTag,
519
+ IEmitter<LocalCommitEvents>
520
+ >();
521
+
503
522
  /**
504
523
  * Revertibles maintained for automatic disposal
505
524
  */
@@ -668,12 +687,14 @@ export class TreeCheckout implements ITreeCheckout {
668
687
 
669
688
  private registerForBranchEvents(): void {
670
689
  this.#transaction.branch.events.on("afterChange", this.onAfterBranchChange);
690
+ this.#transaction.branch.events.on("commitSequenced", this.onCommitSequenced);
671
691
  this.#transaction.activeBranchEvents.on("afterChange", this.onAfterChange);
672
692
  this.#transaction.activeBranchEvents.on("ancestryTrimmed", this.onAncestryTrimmed);
673
693
  }
674
694
 
675
695
  private unregisterFromBranchEvents(): void {
676
696
  this.#transaction.branch.events.off("afterChange", this.onAfterBranchChange);
697
+ this.#transaction.branch.events.off("commitSequenced", this.onCommitSequenced);
677
698
  this.#transaction.activeBranchEvents.off("afterChange", this.onAfterChange);
678
699
  this.#transaction.activeBranchEvents.off("ancestryTrimmed", this.onAncestryTrimmed);
679
700
  }
@@ -739,6 +760,39 @@ export class TreeCheckout implements ITreeCheckout {
739
760
  }
740
761
  }
741
762
 
763
+ private readonly onCommitSequenced = (commit: GraphCommit<SharedTreeChange>): void => {
764
+ const emitter = this.localCommitEventEmitters.get(commit.revision);
765
+ if (emitter === undefined) {
766
+ // If the commit predates the creation of this checkout, there will be no emitter associated with the commit.
767
+ // In that case, we don't need to emit any events.
768
+ return;
769
+ }
770
+ const constraintStatus = getConstraintStatus(commit.change);
771
+ let outcome: CommitOutcome;
772
+ switch (constraintStatus) {
773
+ case ConstraintStatus.Satisfied: {
774
+ outcome = CommitOutcome.FullyApplied;
775
+ break;
776
+ }
777
+ case ConstraintStatus.ImplicitViolation: {
778
+ outcome = CommitOutcome.FullyDropped;
779
+ break;
780
+ }
781
+ case ConstraintStatus.ExplicitViolation: {
782
+ outcome = CommitOutcome.NewContentOnly;
783
+ break;
784
+ }
785
+ default: {
786
+ unreachableCase(constraintStatus);
787
+ }
788
+ }
789
+ this.emitChangedLocked(() => {
790
+ emitter.emit("settled", outcome);
791
+ }, "a commit settled event callback");
792
+ // No more events will be emitted for this commit, so we can remove the emitter from the map.
793
+ this.localCommitEventEmitters.delete(commit.revision);
794
+ };
795
+
742
796
  private readonly onAfterBranchChange = (
743
797
  event: SharedTreeBranchChange<SharedTreeChange>,
744
798
  ): void => {
@@ -787,6 +841,8 @@ export class TreeCheckout implements ITreeCheckout {
787
841
 
788
842
  let withinEventContext = true;
789
843
 
844
+ const events = createEmitter<LocalCommitEvents>();
845
+ this.localCommitEventEmitters.set(commit.revision, events);
790
846
  const metadata: ChangeMetadata = {
791
847
  kind,
792
848
  isLocal: true,
@@ -805,6 +861,7 @@ export class TreeCheckout implements ITreeCheckout {
805
861
  getRevertible: (onDisposed) => getRevertible?.(onDisposed),
806
862
  label: this.labelTreeNode?.label,
807
863
  labels: buildLabelsSet(this.labelTreeNode),
864
+ events,
808
865
  };
809
866
 
810
867
  this.emitChangedLocked(() => {
@@ -838,8 +895,11 @@ export class TreeCheckout implements ITreeCheckout {
838
895
  * Shared by both the local and remote `changed` emission paths in {@link TreeCheckout.onAfterBranchChange}.
839
896
  * The `try`/`finally` ensures the lock is released even if a listener throws.
840
897
  */
841
- private emitChangedLocked(emit: () => void): void {
842
- this.editLock.lock();
898
+ private emitChangedLocked(
899
+ emit: () => void,
900
+ reason: string = "a change event callback",
901
+ ): void {
902
+ this.editLock.lock(reason);
843
903
  try {
844
904
  emit();
845
905
  } finally {
@@ -855,7 +915,7 @@ export class TreeCheckout implements ITreeCheckout {
855
915
  }
856
916
 
857
917
  private readonly onAfterChange = (event: SharedTreeBranchChange<SharedTreeChange>): void => {
858
- this.editLock.lock();
918
+ this.editLock.lock("a change event callback");
859
919
  this.#events.emit("beforeBatch", event);
860
920
  if (event.change !== undefined) {
861
921
  const revision =
@@ -1703,7 +1763,13 @@ class EditLock {
1703
1763
  * @remarks Edits will throw an error if the lock is currently locked.
1704
1764
  */
1705
1765
  public readonly editor: ISharedTreeEditor;
1706
- private locked = false;
1766
+ /**
1767
+ * The current status of the lock.
1768
+ * The `reason` string will be included in the error message if an attempt is made to edit the tree while the lock is held.
1769
+ */
1770
+ private status:
1771
+ | { readonly isLocked: false }
1772
+ | { readonly isLocked: true; readonly reason: string } = { isLocked: false };
1707
1773
 
1708
1774
  /**
1709
1775
  * @param editor - an editor which will be used to create a new editor that is monitored to determine if any changes are happening to the tree.
@@ -1767,14 +1833,15 @@ class EditLock {
1767
1833
 
1768
1834
  /**
1769
1835
  * Prevent further changes from being made to {@link EditLock.editor} until {@link EditLock.unlock} is called.
1836
+ * @param reason - A string describing why the lock is being acquired. This will be included in the error message if an attempt is made to edit while the lock is held.
1770
1837
  * @remarks May only be called when the lock is not already locked.
1771
1838
  */
1772
- public lock(): void {
1773
- if (this.locked) {
1839
+ public lock(reason: string): void {
1840
+ if (this.status.isLocked) {
1774
1841
  debugger;
1775
1842
  }
1776
- assert(!this.locked, 0xaa7 /* Checkout has already been locked */);
1777
- this.locked = true;
1843
+ assert(!this.status.isLocked, 0xaa7 /* Checkout has already been locked */);
1844
+ this.status = { isLocked: true, reason };
1778
1845
  }
1779
1846
 
1780
1847
  /**
@@ -1783,8 +1850,8 @@ class EditLock {
1783
1850
  * This must start with a capital letter, as it shows up as the first part of the error message and we want it to look nice.
1784
1851
  */
1785
1852
  public checkUnlocked<T extends string>(action: T extends Capitalize<T> ? T : never): void {
1786
- if (this.locked) {
1787
- throw new UsageError(`${action} is forbidden during a change event callback`);
1853
+ if (this.status.isLocked) {
1854
+ throw new UsageError(`${action} is forbidden during ${this.status.reason}`);
1788
1855
  }
1789
1856
  }
1790
1857
 
@@ -1793,8 +1860,8 @@ class EditLock {
1793
1860
  * @remarks May only be called when the lock is currently locked.
1794
1861
  */
1795
1862
  public unlock(): void {
1796
- assert(this.locked, 0xaa8 /* Checkout has not been locked */);
1797
- this.locked = false;
1863
+ assert(this.status.isLocked, 0xaa8 /* Checkout has not been locked */);
1864
+ this.status = { isLocked: false };
1798
1865
  }
1799
1866
  }
1800
1867
 
@@ -86,6 +86,7 @@ export interface SharedTreeBranchEvents<
86
86
 
87
87
  /**
88
88
  * Fired when a commit is ordered by the sequencing service.
89
+ * @param commit - The commit that was sequenced
89
90
  *
90
91
  * @remarks
91
92
  * Once a commit is sequenced, the following guarantees hold:
@@ -214,7 +214,7 @@ export class SharedTreeCore<
214
214
  options.minVersionForCollab,
215
215
  this.schemaAndPolicy,
216
216
  options.healUnresolvableIdentifiersOnDecode === true
217
- ? { sharedObjectId: sharedObject.id }
217
+ ? { sharedObjectId: sharedObject.id, logger }
218
218
  : undefined,
219
219
  ),
220
220
  ...summarizables,
@@ -67,7 +67,6 @@ export {
67
67
  createCustomizedFluidFrameworkScopedFactory,
68
68
  } from "./schemaCreationUtilities.js";
69
69
  export {
70
- deltaMarksToArrayOps,
71
70
  getIdentifierFromNode,
72
71
  getPropertyKeyFromStoredKey,
73
72
  getStoredKey,
@@ -16,7 +16,7 @@ import {
16
16
  type FieldSchema,
17
17
  type FieldSchemaAlpha,
18
18
  } from "../fieldSchema.js";
19
- import type { LeafSchema } from "../leafNodeSchema.js";
19
+ import type { LeafSchema, StringSchema } from "../leafNodeSchema.js";
20
20
  import {
21
21
  stringSchema,
22
22
  numberSchema,
@@ -252,7 +252,7 @@ export const schemaStatics = {
252
252
  props?: Omit<FieldProps<TCustomMetadata>, "defaultProvider">,
253
253
  ): FieldSchemaAlpha<
254
254
  FieldKind.Identifier,
255
- typeof stringSchema,
255
+ StringSchema,
256
256
  TCustomMetadata,
257
257
  FieldPropsAlpha<TCustomMetadata>
258
258
  > => {
@@ -25,6 +25,7 @@ import {
25
25
  type ImplicitAllowedTypes,
26
26
  type TreeNodeFromImplicitAllowedTypes,
27
27
  normalizeAllowedTypes,
28
+ type TreeNodeKernel,
28
29
  } from "../core/index.js";
29
30
  import { type ImplicitFieldSchema, FieldSchema } from "../fieldSchema.js";
30
31
  import { tryGetTreeNodeForField } from "../getTreeNodeForField.js";
@@ -115,6 +116,10 @@ export interface ArrayNodeRemoveOp {
115
116
  * `"remove"` and the destination array's delta contains an `"insert"` — they cannot be
116
117
  * correlated without additional bookkeeping on the caller's side.
117
118
  *
119
+ * The operations cover the complete array: trailing elements that were not changed are included
120
+ * in a final `"retain"` op. Applying every operation therefore consumes the entire pre-edit array
121
+ * and produces the entire post-edit array.
122
+ *
118
123
  * @sealed @alpha
119
124
  */
120
125
  export type ArrayNodeDeltaOp = ArrayNodeRetainOp | ArrayNodeInsertOp | ArrayNodeRemoveOp;
@@ -296,7 +301,13 @@ export const treeNodeApi: TreeNodeApi = {
296
301
  // unavailable rather than receiving stale marks from only the first batch.
297
302
  // TODO: Once the eventing stack is rewritten to walk the composed delta at
298
303
  // flush time, `marks` will always be defined. Remove the `undefined` fallback.
299
- const delta = marks === undefined ? undefined : deltaMarksToArrayOps(marks);
304
+ const delta =
305
+ marks === undefined
306
+ ? undefined
307
+ : deltaMarksToArrayOpsWithRetain(marks, getArrayLength(kernel), (count) => ({
308
+ type: "retain",
309
+ count,
310
+ }));
300
311
  listener({ delta });
301
312
  });
302
313
  } else {
@@ -314,7 +325,17 @@ export const treeNodeApi: TreeNodeApi = {
314
325
  return kernel.events.on("childrenChangedAfterBatch", ({ fieldMarks }) => {
315
326
  const marks = fieldMarks.get(EmptyKey);
316
327
  const delta =
317
- marks === undefined ? undefined : deltaMarksToArrayOpsForTreeChanged(marks);
328
+ marks === undefined
329
+ ? undefined
330
+ : deltaMarksToArrayOpsWithRetain(
331
+ marks,
332
+ getArrayLength(kernel),
333
+ (count, subtreeChanged) => ({
334
+ type: "retain",
335
+ count,
336
+ subtreeChanged,
337
+ }),
338
+ );
318
339
  (listener as (data: { readonly delta: typeof delta }) => void)({ delta });
319
340
  });
320
341
  }
@@ -351,73 +372,68 @@ export const treeNodeApi: TreeNodeApi = {
351
372
  };
352
373
 
353
374
  /**
354
- * Converts an array of internal {@link DeltaMark}s for a sequence field into sequential
355
- * array delta ops suitable for inclusion in {@link NodeChangedDataDelta.delta}.
375
+ * Converts internal {@link DeltaMark}s for an array's sequence field into sequential array delta
376
+ * operations.
377
+ *
378
+ * @remarks
379
+ * Each mark describes a contiguous run:
380
+ * - A mark with only `count` becomes a retain.
381
+ * - A mark with only `attach` becomes an insert.
382
+ * - A mark with only `detach` becomes a remove.
383
+ * - A mark with both `attach` and `detach` becomes a remove followed by an insert.
356
384
  *
357
- * Each mark in the delta describes a contiguous run of positions in the original array:
358
- * - A mark with only `count` (no attach/detach) `"retain"` with no subtree information
359
- * - A mark with only `attach` → `"insert"` (new elements added)
360
- * - A mark with only `detach` → `"remove"` (elements removed)
361
- * - A mark with both `attach` and `detach` → `"remove"` + `"insert"`
385
+ * Internal mark arrays may omit an unchanged suffix. The returned operations always cover the
386
+ * complete array, so this function uses the array's post-edit length to append a trailing retain
387
+ * when needed.
362
388
  *
363
389
  * @param marks - The low-level delta marks for the array's sequence field.
390
+ * @param postEditLength - The array's length after the edit.
391
+ * @param buildRetainOp - Builds a retain operation. Its second argument indicates whether the
392
+ * retained element's subtree changed; it is `false` for a synthesized trailing retain.
364
393
  *
365
394
  * @privateRemarks
366
395
  * The case where both `attach` and `detach` are set is unreachable today: the sequence-field
367
- * encoder never emits such marks for array (EmptyKey) fields. It is handled defensively.
396
+ * encoder never emits such marks for array (`EmptyKey`) fields. It is handled defensively.
368
397
  */
369
- export function deltaMarksToArrayOps(marks: readonly DeltaMark[]): ArrayNodeDeltaOp[] {
370
- const ops: ArrayNodeDeltaOp[] = [];
398
+ export function deltaMarksToArrayOpsWithRetain<TRetain extends ArrayNodeRetainOp>(
399
+ marks: readonly DeltaMark[],
400
+ postEditLength: number,
401
+ buildRetainOp: (count: number, subtreeChanged: boolean) => TRetain,
402
+ ): (TRetain | ArrayNodeInsertOp | ArrayNodeRemoveOp)[] {
403
+ const ops: (TRetain | ArrayNodeInsertOp | ArrayNodeRemoveOp)[] = [];
404
+ let processedLength = 0;
371
405
  for (const mark of marks) {
372
406
  if (mark.detach !== undefined) {
373
407
  ops.push({ type: "remove", count: mark.count });
374
408
  }
375
409
  if (mark.attach !== undefined) {
376
410
  ops.push({ type: "insert", count: mark.count });
411
+ processedLength += mark.count;
377
412
  } else if (mark.detach === undefined) {
378
- // Retain: elements were not added or removed.
379
- ops.push({ type: "retain", count: mark.count });
413
+ // When `fields` is set, `count` is guaranteed to be 1 (DeltaMark invariant).
414
+ ops.push(buildRetainOp(mark.count, mark.fields !== undefined));
415
+ processedLength += mark.count;
380
416
  }
381
417
  }
418
+ // DeltaMark arrays are allowed to omit the trailing retain, while our user facing array equivalents are not, so add it if necessary.
419
+ assert(
420
+ processedLength <= postEditLength,
421
+ 0xd19 /* Array delta exceeds post-edit array length */,
422
+ );
423
+ if (processedLength < postEditLength) {
424
+ ops.push(buildRetainOp(postEditLength - processedLength, false));
425
+ }
382
426
  return ops;
383
427
  }
384
428
 
385
429
  /**
386
- * Converts an array of internal {@link DeltaMark}s for a sequence field into sequential
387
- * {@link ArrayNodeTreeChangedDeltaOp}s suitable for inclusion in
388
- * {@link NodeChangedDataTreeDelta.delta} (delivered to {@link TreeChangeEventsAlpha.treeChanged}).
389
- *
390
- * Same conversion rules as {@link deltaMarksToArrayOps}, but retain ops additionally carry a
391
- * {@link ArrayNodeTreeChangedRetainOp.subtreeChanged} flag derived from whether the mark has
392
- * a `fields` property (indicating a descendant changed).
393
- *
394
- * @param marks - The low-level delta marks for the array's sequence field.
395
- *
396
- * @privateRemarks
397
- * The case where both `attach` and `detach` are set is unreachable today: the sequence-field
398
- * encoder never emits such marks for array (EmptyKey) fields. It is handled defensively.
430
+ * Returns the length of the `EmptyKey` field under the provided node kernel.
431
+ * @remarks
432
+ * Use this with the kernel of an array node to get the current length of the array.
433
+ * This is the same as `.length` on the array node, but is usable without having to get (and possible create) the node and narrow to the proper type to access that.
399
434
  */
400
- export function deltaMarksToArrayOpsForTreeChanged(
401
- marks: readonly DeltaMark[],
402
- ): ArrayNodeTreeChangedDeltaOp[] {
403
- const ops: ArrayNodeTreeChangedDeltaOp[] = [];
404
- for (const mark of marks) {
405
- if (mark.detach !== undefined) {
406
- ops.push({ type: "remove", count: mark.count });
407
- }
408
- if (mark.attach !== undefined) {
409
- ops.push({ type: "insert", count: mark.count });
410
- } else if (mark.detach === undefined) {
411
- // Retain: elements were not added or removed (but may have deep changes).
412
- // When `fields` is set, `count` is guaranteed to be 1 (DeltaMark invariant).
413
- ops.push({
414
- type: "retain",
415
- count: mark.count,
416
- subtreeChanged: mark.fields !== undefined,
417
- });
418
- }
419
- }
420
- return ops;
435
+ function getArrayLength(arrayNodeKernel: TreeNodeKernel): number {
436
+ return arrayNodeKernel.getInnerNode().getBoxed(EmptyKey).length;
421
437
  }
422
438
 
423
439
  /**
@@ -9,8 +9,12 @@ import { UsageError } from "@fluidframework/telemetry-utils/internal";
9
9
  import { markEager } from "./flexList.js";
10
10
  import { tryGetTreeNodeSchema } from "./treeNodeKernel.js";
11
11
  import { NodeKind, type TreeNodeSchemaClass } from "./treeNodeSchema.js";
12
- // eslint-disable-next-line import-x/no-deprecated
13
- import { type WithType, typeNameSymbol, type typeSchemaSymbol } from "./withType.js";
12
+ import type {
13
+ WithType,
14
+ // eslint-disable-next-line import-x/no-deprecated -- Required to implement the deprecated typeNameSymbol API.
15
+ typeNameSymbol,
16
+ typeSchemaSymbol,
17
+ } from "./withType.js";
14
18
 
15
19
  /**
16
20
  * A non-{@link NodeKind.Leaf|leaf} SharedTree node. Includes objects, arrays, and maps.
@@ -72,22 +76,21 @@ export abstract class TreeNode implements WithType {
72
76
  * someone could manually (or via Intellisense auto-implement completion, or in response to a type error)
73
77
  * make an object literal with the [type] field and pass it off as a node: this private brand prevents that.
74
78
  */
75
- // eslint-disable-next-line no-unused-private-class-members
79
+ // eslint-disable-next-line no-unused-private-class-members -- Exists for type checking sideeffect. See private remarks above.
76
80
  readonly #brand!: unknown;
77
81
 
78
82
  /**
79
- * Adds a type symbol for stronger typing.
80
- * @privateRemarks
81
- * Subclasses provide more specific strings for this to get strong typing of otherwise type compatible nodes.
83
+ * Adds a schema identifier brand for stronger and more efficient type checking.
84
+ * @privateRemarks Subclasses provide more specific values to distinguish otherwise type-compatible nodes.
82
85
  * @deprecated Use {@link typeSchemaSymbol} instead.
83
86
  */
84
- // eslint-disable-next-line import-x/no-deprecated
87
+ // eslint-disable-next-line import-x/no-deprecated -- Required to implement the deprecated typeNameSymbol API.
85
88
  public abstract get [typeNameSymbol](): string;
86
89
 
87
90
  /**
88
- * Adds a type symbol for stronger typing.
91
+ * {@inheritDoc typeSchemaSymbol}
89
92
  * @privateRemarks
90
- * Subclasses provide more specific strings for this to get strong typing of otherwise type compatible nodes.
93
+ * Subclasses provide more specific values for this to get strong typing of otherwise type compatible nodes.
91
94
  */
92
95
  public abstract get [typeSchemaSymbol](): TreeNodeSchemaClass;
93
96
 
@@ -152,7 +155,7 @@ export const privateToken = {};
152
155
  * @param base - prototype to search for
153
156
  * @returns true iff `base` is in the prototype chain starting at `derived`.
154
157
  */
155
- // eslint-disable-next-line @rushstack/no-new-null
158
+ // eslint-disable-next-line @rushstack/no-new-null -- getPrototypeOf uses null.
156
159
  export function inPrototypeChain(derived: object | null, base: object): boolean {
157
160
  let checking = derived;
158
161
  while (checking !== null) {
@@ -11,17 +11,14 @@ import type { TreeNode } from "./treeNode.js";
11
11
  import type { NodeKind, TreeNodeSchemaClass } from "./treeNodeSchema.js";
12
12
 
13
13
  /**
14
- * The type of a {@link TreeNode}.
14
+ * An internal brand to improve compiler performance.
15
15
  * For more information about the type, use `Tree.schema(theNode)` instead.
16
16
  * @remarks
17
- * This symbol mainly exists on nodes to allow TypeScript to provide more accurate type checking.
18
17
  * `Tree.is` and `Tree.schema` provide a superset of this information in more friendly ways.
19
- *
20
- * This symbol should not manually be added to objects as doing so allows the object to be invalidly used where nodes are expected.
21
- * Instead construct a real node of the desired type using its constructor.
22
18
  * @privateRemarks
23
- * This prevents non-nodes from being accidentally used as nodes, as well as allows the type checker to distinguish different node types.
24
- * @deprecated External code should use `Tree.schema(theNode)` for schema related runtime data access. For type narrowing, use `WithType` instead of the symbols directly.
19
+ * The identifier of a {@link TreeNode}'s schema.
20
+ * This allows the type checker to distinguish different node types more efficiently than via {@link typeSchemaSymbol}.
21
+ * @deprecated External code should use `Tree.schema(theNode)` for schema related runtime data access. For type narrowing, use {@link WithType} instead of the symbols directly.
25
22
  * @system @public
26
23
  */
27
24
  export const typeNameSymbol: unique symbol = Symbol("TreeNode Type");
@@ -97,6 +94,13 @@ export const contentSchemaSymbol: unique symbol = Symbol("SharedTree Schema");
97
94
  * }
98
95
  * }
99
96
  * ```
97
+ * @privateRemarks
98
+ * The properties of this are implemented as getters rather than instance properties as that is the easiest way to make them non-enumerable,
99
+ * and also saves memory and time as they only have to be setup on the prototype.
100
+ * These being non-enumerable inherited properties minimizes impact on users,
101
+ * hiding them from things like `Reflect.ownKeys`, Object spread, `Object.getOwnPropertyDescriptors`, and `for...in` loops.
102
+ * How we declare them here actually doesn't impact that,
103
+ * but is a good way to communicate to users and internal devs that these should be implemented as inherited getters to accomplish the above.
100
104
  * @sealed @public
101
105
  */
102
106
  export interface WithType<
@@ -106,6 +110,15 @@ export interface WithType<
106
110
  > {
107
111
  /**
108
112
  * Type symbol, marking a type in a way to increase type safety via strong type checking.
113
+ *
114
+ * Use `Tree.schema(theNode)` for schema related runtime data access. For type narrowing, use `WithType` instead of the symbols directly.
115
+ * @remarks
116
+ * This should be redundant with {@link typeSchemaSymbol}, but is kept for improved compiler performance.
117
+ * If we just rely on {@link typeSchemaSymbol}, the compiler has to work much harder to distinguish schema in unions,
118
+ * which can cause large schema unions to hit "error TS2859: Excessive complexity comparing types".
119
+ *
120
+ * This property is not intended for any use other than helping the compiler distinguish schema types,
121
+ * and should not be referenced in any code other than internal to the tree package.
109
122
  * @deprecated Use {@link typeSchemaSymbol} instead.
110
123
  */
111
124
  get [typeNameSymbol](): TName;
@@ -100,7 +100,6 @@ export {
100
100
  type ArrayNodeRetainOp,
101
101
  type ArrayNodeTreeChangedDeltaOp,
102
102
  type ArrayNodeTreeChangedRetainOp,
103
- deltaMarksToArrayOps,
104
103
  type NodeChangedData,
105
104
  type NodeChangedDataAlpha,
106
105
  type NodeChangedDataDelta,
@@ -328,6 +327,6 @@ export {
328
327
  handleSchema,
329
328
  nullSchema,
330
329
  } from "./leafNodeSchema.js";
331
- export type { LeafSchema } from "./leafNodeSchema.js";
330
+ export type { LeafSchema, StringSchema } from "./leafNodeSchema.js";
332
331
  export { getUnhydratedContext } from "./createContext.js";
333
332
  export { type TreeSchema, createTreeSchema } from "./treeSchema.js";
@@ -125,7 +125,12 @@ export interface LeafSchema<Name extends string, T extends TreeLeafValue>
125
125
  > {}
126
126
 
127
127
  // Leaf schema shared between all SchemaFactory instances.
128
- export const stringSchema = makeLeaf("string", ValueSchema.String);
128
+ /**
129
+ * A {@link LeafSchema} for a `string`.
130
+ * @system @sealed @alpha
131
+ */
132
+ export type StringSchema = LeafSchema<"string", string> & SimpleLeafNodeSchema;
133
+ export const stringSchema: StringSchema = makeLeaf("string", ValueSchema.String);
129
134
  export const numberSchema = makeLeaf("number", ValueSchema.Number);
130
135
  export const booleanSchema = makeLeaf("boolean", ValueSchema.Boolean);
131
136
  export const nullSchema = makeLeaf("null", ValueSchema.Null);