@fluidframework/tree 2.103.0 → 2.110.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 (224) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/api-report/tree.alpha.api.md +12 -3
  3. package/api-report/tree.beta.api.md +11 -3
  4. package/api-report/tree.legacy.beta.api.md +11 -3
  5. package/dist/codec/versioned/format.js +2 -24
  6. package/dist/codec/versioned/format.js.map +1 -1
  7. package/dist/core/rebase/types.js +2 -24
  8. package/dist/core/rebase/types.js.map +1 -1
  9. package/dist/core/schema-stored/formatV1.js +2 -24
  10. package/dist/core/schema-stored/formatV1.js.map +1 -1
  11. package/dist/core/schema-stored/formatV2.js +2 -24
  12. package/dist/core/schema-stored/formatV2.js.map +1 -1
  13. package/dist/core/schema-stored/index.js +3 -25
  14. package/dist/core/schema-stored/index.js.map +1 -1
  15. package/dist/core/tree/anchorSet.js +4 -8
  16. package/dist/core/tree/anchorSet.js.map +1 -1
  17. package/dist/core/tree/detachedFieldIndexFormatCommon.js +2 -24
  18. package/dist/core/tree/detachedFieldIndexFormatCommon.js.map +1 -1
  19. package/dist/core/tree/detachedFieldIndexFormatV2.js +2 -24
  20. package/dist/core/tree/detachedFieldIndexFormatV2.js.map +1 -1
  21. package/dist/core/tree/persistedTreeTextFormat.js +2 -24
  22. package/dist/core/tree/persistedTreeTextFormat.js.map +1 -1
  23. package/dist/entrypoints/internal.js +2 -15
  24. package/dist/entrypoints/internal.js.map +1 -1
  25. package/dist/feature-libraries/chunked-forest/chunkedForest.js +7 -11
  26. package/dist/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  27. package/dist/feature-libraries/chunked-forest/codec/format/formatGeneric.js +2 -24
  28. package/dist/feature-libraries/chunked-forest/codec/format/formatGeneric.js.map +1 -1
  29. package/dist/feature-libraries/chunked-forest/codec/format/formatV1.js +2 -24
  30. package/dist/feature-libraries/chunked-forest/codec/format/formatV1.js.map +1 -1
  31. package/dist/feature-libraries/chunked-forest/codec/format/formatV2.js +2 -24
  32. package/dist/feature-libraries/chunked-forest/codec/format/formatV2.js.map +1 -1
  33. package/dist/feature-libraries/chunked-forest/codec/format/formatVText.js +2 -24
  34. package/dist/feature-libraries/chunked-forest/codec/format/formatVText.js.map +1 -1
  35. package/dist/feature-libraries/flex-tree/lazyEntity.js +9 -19
  36. package/dist/feature-libraries/flex-tree/lazyEntity.js.map +1 -1
  37. package/dist/feature-libraries/flex-tree/lazyNode.js +3 -13
  38. package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  39. package/dist/feature-libraries/forest-summary/formatCommon.js +2 -24
  40. package/dist/feature-libraries/forest-summary/formatCommon.js.map +1 -1
  41. package/dist/feature-libraries/index.js +2 -24
  42. package/dist/feature-libraries/index.js.map +1 -1
  43. package/dist/feature-libraries/modular-schema/genericFieldKindFormat.js +2 -24
  44. package/dist/feature-libraries/modular-schema/genericFieldKindFormat.js.map +1 -1
  45. package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  46. package/dist/feature-libraries/modular-schema/modularChangeFamily.js +10 -30
  47. package/dist/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  48. package/dist/feature-libraries/modular-schema/modularChangeFormatV1.js +2 -24
  49. package/dist/feature-libraries/modular-schema/modularChangeFormatV1.js.map +1 -1
  50. package/dist/feature-libraries/modular-schema/modularChangeFormatV2.js +2 -24
  51. package/dist/feature-libraries/modular-schema/modularChangeFormatV2.js.map +1 -1
  52. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts +0 -5
  53. package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  54. package/dist/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  55. package/dist/feature-libraries/object-forest/objectForest.js +11 -55
  56. package/dist/feature-libraries/object-forest/objectForest.js.map +1 -1
  57. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV1.js +2 -24
  58. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV1.js.map +1 -1
  59. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV2.js +2 -24
  60. package/dist/feature-libraries/optional-field/optionalFieldChangeFormatV2.js.map +1 -1
  61. package/dist/feature-libraries/schema-edits/schemaChangeFormat.js +2 -24
  62. package/dist/feature-libraries/schema-edits/schemaChangeFormat.js.map +1 -1
  63. package/dist/feature-libraries/schema-index/formatV1.js +2 -24
  64. package/dist/feature-libraries/schema-index/formatV1.js.map +1 -1
  65. package/dist/feature-libraries/schema-index/formatV2.js +2 -24
  66. package/dist/feature-libraries/schema-index/formatV2.js.map +1 -1
  67. package/dist/feature-libraries/sequence-field/formatV1.js +2 -24
  68. package/dist/feature-libraries/sequence-field/formatV1.js.map +1 -1
  69. package/dist/feature-libraries/sequence-field/formatV2.js +2 -24
  70. package/dist/feature-libraries/sequence-field/formatV2.js.map +1 -1
  71. package/dist/feature-libraries/sequence-field/formatV3.js +2 -24
  72. package/dist/feature-libraries/sequence-field/formatV3.js.map +1 -1
  73. package/dist/index.js +2 -24
  74. package/dist/index.js.map +1 -1
  75. package/dist/packageVersion.d.ts +1 -1
  76. package/dist/packageVersion.js +1 -1
  77. package/dist/packageVersion.js.map +1 -1
  78. package/dist/shared-tree/schematizingTreeView.d.ts +1 -0
  79. package/dist/shared-tree/schematizingTreeView.d.ts.map +1 -1
  80. package/dist/shared-tree/schematizingTreeView.js +7 -41
  81. package/dist/shared-tree/schematizingTreeView.js.map +1 -1
  82. package/dist/shared-tree/sharedTree.d.ts +0 -1
  83. package/dist/shared-tree/sharedTree.d.ts.map +1 -1
  84. package/dist/shared-tree/sharedTree.js +6 -45
  85. package/dist/shared-tree/sharedTree.js.map +1 -1
  86. package/dist/shared-tree/sharedTreeChangeFormat.js +2 -24
  87. package/dist/shared-tree/sharedTreeChangeFormat.js.map +1 -1
  88. package/dist/shared-tree/treeCheckout.d.ts +12 -3
  89. package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
  90. package/dist/shared-tree/treeCheckout.js +106 -136
  91. package/dist/shared-tree/treeCheckout.js.map +1 -1
  92. package/dist/shared-tree-core/branch.js +13 -17
  93. package/dist/shared-tree-core/branch.js.map +1 -1
  94. package/dist/shared-tree-core/editManagerFormatCommons.js +2 -24
  95. package/dist/shared-tree-core/editManagerFormatCommons.js.map +1 -1
  96. package/dist/shared-tree-core/editManagerFormatV1toV4.js +2 -24
  97. package/dist/shared-tree-core/editManagerFormatV1toV4.js.map +1 -1
  98. package/dist/shared-tree-core/editManagerFormatVSharedBranches.js +2 -24
  99. package/dist/shared-tree-core/editManagerFormatVSharedBranches.js.map +1 -1
  100. package/dist/shared-tree-core/messageCodecVSharedBranches.js +2 -24
  101. package/dist/shared-tree-core/messageCodecVSharedBranches.js.map +1 -1
  102. package/dist/shared-tree-core/messageFormatV1ToV4.js +2 -24
  103. package/dist/shared-tree-core/messageFormatV1ToV4.js.map +1 -1
  104. package/dist/shared-tree-core/messageFormatVSharedBranches.js +2 -24
  105. package/dist/shared-tree-core/messageFormatVSharedBranches.js.map +1 -1
  106. package/dist/shared-tree-core/sharedTreeCore.js +6 -43
  107. package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
  108. package/dist/shared-tree-core/transaction.js +20 -30
  109. package/dist/shared-tree-core/transaction.js.map +1 -1
  110. package/dist/simple-tree/api/simpleSchemaCodec.js +2 -24
  111. package/dist/simple-tree/api/simpleSchemaCodec.js.map +1 -1
  112. package/dist/simple-tree/api/snapshotCompatibilityChecker.js +2 -24
  113. package/dist/simple-tree/api/snapshotCompatibilityChecker.js.map +1 -1
  114. package/dist/simple-tree/api/tree.d.ts +10 -0
  115. package/dist/simple-tree/api/tree.d.ts.map +1 -1
  116. package/dist/simple-tree/api/tree.js.map +1 -1
  117. package/dist/simple-tree/core/treeNodeKernel.js +83 -93
  118. package/dist/simple-tree/core/treeNodeKernel.js.map +1 -1
  119. package/dist/simple-tree/core/unhydratedFlexTree.js +6 -10
  120. package/dist/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  121. package/dist/simple-tree/leafNodeSchema.js +2 -12
  122. package/dist/simple-tree/leafNodeSchema.js.map +1 -1
  123. package/dist/simple-tree/node-kinds/array/arrayNode.js +2 -6
  124. package/dist/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  125. package/dist/simple-tree/simpleSchemaFormatV1.js +2 -24
  126. package/dist/simple-tree/simpleSchemaFormatV1.js.map +1 -1
  127. package/dist/tableSchema.d.ts +50 -4
  128. package/dist/tableSchema.d.ts.map +1 -1
  129. package/dist/tableSchema.js +202 -106
  130. package/dist/tableSchema.js.map +1 -1
  131. package/dist/util/arrayUtilities.d.ts +20 -0
  132. package/dist/util/arrayUtilities.d.ts.map +1 -1
  133. package/dist/util/arrayUtilities.js +24 -1
  134. package/dist/util/arrayUtilities.js.map +1 -1
  135. package/dist/util/index.d.ts +1 -1
  136. package/dist/util/index.d.ts.map +1 -1
  137. package/dist/util/index.js +3 -2
  138. package/dist/util/index.js.map +1 -1
  139. package/dist/util/rangeMap.d.ts +13 -0
  140. package/dist/util/rangeMap.d.ts.map +1 -1
  141. package/dist/util/rangeMap.js +69 -8
  142. package/dist/util/rangeMap.js.map +1 -1
  143. package/dist/util/typeboxBrand.js +2 -24
  144. package/dist/util/typeboxBrand.js.map +1 -1
  145. package/dist/util/utils.js +2 -24
  146. package/dist/util/utils.js.map +1 -1
  147. package/eslint.config.mts +0 -30
  148. package/lib/core/tree/anchorSet.js +1 -5
  149. package/lib/core/tree/anchorSet.js.map +1 -1
  150. package/lib/feature-libraries/chunked-forest/chunkedForest.js +1 -5
  151. package/lib/feature-libraries/chunked-forest/chunkedForest.js.map +1 -1
  152. package/lib/feature-libraries/flex-tree/lazyEntity.js +1 -11
  153. package/lib/feature-libraries/flex-tree/lazyEntity.js.map +1 -1
  154. package/lib/feature-libraries/flex-tree/lazyNode.js +1 -11
  155. package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
  156. package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
  157. package/lib/feature-libraries/modular-schema/modularChangeFamily.js +10 -30
  158. package/lib/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
  159. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts +0 -5
  160. package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
  161. package/lib/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
  162. package/lib/feature-libraries/object-forest/objectForest.js +1 -45
  163. package/lib/feature-libraries/object-forest/objectForest.js.map +1 -1
  164. package/lib/packageVersion.d.ts +1 -1
  165. package/lib/packageVersion.js +1 -1
  166. package/lib/packageVersion.js.map +1 -1
  167. package/lib/shared-tree/schematizingTreeView.d.ts +1 -0
  168. package/lib/shared-tree/schematizingTreeView.d.ts.map +1 -1
  169. package/lib/shared-tree/schematizingTreeView.js +4 -38
  170. package/lib/shared-tree/schematizingTreeView.js.map +1 -1
  171. package/lib/shared-tree/sharedTree.d.ts +0 -1
  172. package/lib/shared-tree/sharedTree.d.ts.map +1 -1
  173. package/lib/shared-tree/sharedTree.js +1 -40
  174. package/lib/shared-tree/sharedTree.js.map +1 -1
  175. package/lib/shared-tree/treeCheckout.d.ts +12 -3
  176. package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
  177. package/lib/shared-tree/treeCheckout.js +62 -92
  178. package/lib/shared-tree/treeCheckout.js.map +1 -1
  179. package/lib/shared-tree-core/branch.js +1 -5
  180. package/lib/shared-tree-core/branch.js.map +1 -1
  181. package/lib/shared-tree-core/sharedTreeCore.js +1 -38
  182. package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
  183. package/lib/shared-tree-core/transaction.js +1 -11
  184. package/lib/shared-tree-core/transaction.js.map +1 -1
  185. package/lib/simple-tree/api/tree.d.ts +10 -0
  186. package/lib/simple-tree/api/tree.d.ts.map +1 -1
  187. package/lib/simple-tree/api/tree.js.map +1 -1
  188. package/lib/simple-tree/core/treeNodeKernel.js +1 -11
  189. package/lib/simple-tree/core/treeNodeKernel.js.map +1 -1
  190. package/lib/simple-tree/core/unhydratedFlexTree.js +1 -5
  191. package/lib/simple-tree/core/unhydratedFlexTree.js.map +1 -1
  192. package/lib/simple-tree/leafNodeSchema.js +1 -11
  193. package/lib/simple-tree/leafNodeSchema.js.map +1 -1
  194. package/lib/simple-tree/node-kinds/array/arrayNode.js +1 -5
  195. package/lib/simple-tree/node-kinds/array/arrayNode.js.map +1 -1
  196. package/lib/tableSchema.d.ts +50 -4
  197. package/lib/tableSchema.d.ts.map +1 -1
  198. package/lib/tableSchema.js +171 -75
  199. package/lib/tableSchema.js.map +1 -1
  200. package/lib/util/arrayUtilities.d.ts +20 -0
  201. package/lib/util/arrayUtilities.d.ts.map +1 -1
  202. package/lib/util/arrayUtilities.js +22 -0
  203. package/lib/util/arrayUtilities.js.map +1 -1
  204. package/lib/util/index.d.ts +1 -1
  205. package/lib/util/index.d.ts.map +1 -1
  206. package/lib/util/index.js +1 -1
  207. package/lib/util/index.js.map +1 -1
  208. package/lib/util/rangeMap.d.ts +13 -0
  209. package/lib/util/rangeMap.d.ts.map +1 -1
  210. package/lib/util/rangeMap.js +69 -8
  211. package/lib/util/rangeMap.js.map +1 -1
  212. package/package.json +24 -23
  213. package/src/feature-libraries/modular-schema/modularChangeFamily.ts +14 -47
  214. package/src/feature-libraries/modular-schema/modularChangeTypes.ts +0 -5
  215. package/src/packageVersion.ts +1 -1
  216. package/src/shared-tree/schematizingTreeView.ts +4 -0
  217. package/src/shared-tree/sharedTree.ts +2 -6
  218. package/src/shared-tree/treeCheckout.ts +59 -51
  219. package/src/simple-tree/api/tree.ts +11 -0
  220. package/src/tableSchema.ts +306 -80
  221. package/src/util/arrayUtilities.ts +35 -0
  222. package/src/util/index.ts +2 -0
  223. package/src/util/rangeMap.ts +108 -9
  224. package/tsconfig.json +5 -0
@@ -13,7 +13,7 @@ import type {
13
13
  IFluidSerializer,
14
14
  SharedKernel,
15
15
  } from "@fluidframework/shared-object-base/internal";
16
- import { UsageError, type TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
16
+ import { type TelemetryLoggerExt, UsageError } from "@fluidframework/telemetry-utils/internal";
17
17
 
18
18
  import {
19
19
  type CodecTree,
@@ -317,7 +317,6 @@ export class SharedTreeKernel
317
317
  chunkCompressionStrategy: options.treeEncodeType,
318
318
  logger,
319
319
  breaker: this.breaker,
320
- disposeForksAfterTransaction: options.disposeForksAfterTransaction,
321
320
  });
322
321
 
323
322
  this.registerSharedBranchForEditing("main", this.checkout);
@@ -632,9 +631,7 @@ export interface SharedTreeOptions
632
631
 
633
632
  export interface SharedTreeOptionsInternal
634
633
  extends SharedTreeOptions,
635
- Partial<SharedTreeCoreOptionsInternal> {
636
- disposeForksAfterTransaction?: boolean;
637
- }
634
+ Partial<SharedTreeCoreOptionsInternal> {}
638
635
 
639
636
  /**
640
637
  * Configuration options for SharedTree's internal tree storage.
@@ -757,7 +754,6 @@ export const defaultSharedTreeOptions: Required<SharedTreeOptionsInternal> = {
757
754
  minVersionForCollab: FluidClientVersion.v2_0,
758
755
  forest: ForestTypeReference,
759
756
  treeEncodeType: TreeCompressionStrategy.Compressed,
760
- disposeForksAfterTransaction: true,
761
757
  shouldEncodeIncrementally: defaultIncrementalEncodingPolicy,
762
758
  enableSharedBranches: false,
763
759
  healUnresolvableIdentifiersOnDecode: false,
@@ -8,7 +8,7 @@ import type { IFluidHandle, Listenable } from "@fluidframework/core-interfaces/i
8
8
  import { assert, unreachableCase, fail } from "@fluidframework/core-utils/internal";
9
9
  import type { IIdCompressor, SessionId } from "@fluidframework/id-compressor";
10
10
  import { isStableId } from "@fluidframework/id-compressor/internal";
11
- import { UsageError, type TelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
11
+ import { type TelemetryLoggerExt, UsageError } from "@fluidframework/telemetry-utils/internal";
12
12
 
13
13
  import {
14
14
  FluidClientVersion,
@@ -57,6 +57,7 @@ import {
57
57
  makeAnonChange,
58
58
  type TaggedChange,
59
59
  deltaFieldMapHasVisibleChanges,
60
+ findCommonAncestor,
60
61
  } from "../core/index.js";
61
62
  import {
62
63
  type FieldBatchCodec,
@@ -74,7 +75,6 @@ import {
74
75
  SquashingTransactionStack,
75
76
  SharedTreeBranch,
76
77
  TransactionResult as InternalTransactionResult,
77
- onForkTransitive,
78
78
  type SharedTreeBranchChange,
79
79
  type Transactor,
80
80
  } from "../shared-tree-core/index.js";
@@ -87,7 +87,6 @@ import {
87
87
  type ViewableTree,
88
88
  type TreeBranch,
89
89
  type TreeBranchAlpha,
90
- type TreeChangeEvents,
91
90
  type VerboseTree,
92
91
  type VoidTransactionCallbackStatus,
93
92
  type TransactionCallbackStatus,
@@ -308,7 +307,6 @@ export function createTreeCheckout(
308
307
  chunkCompressionStrategy?: TreeCompressionStrategy;
309
308
  logger?: TelemetryLoggerExt;
310
309
  breaker?: Breakable;
311
- disposeForksAfterTransaction?: boolean;
312
310
  codecOptions?: Partial<CodecWriteOptions>;
313
311
  },
314
312
  ): TreeCheckout {
@@ -355,7 +353,6 @@ export function createTreeCheckout(
355
353
  args?.removedRoots,
356
354
  args?.logger,
357
355
  breaker,
358
- args?.disposeForksAfterTransaction,
359
356
  );
360
357
  }
361
358
 
@@ -527,7 +524,6 @@ export class TreeCheckout implements ITreeCheckout {
527
524
  /** Optional logger for telemetry. */
528
525
  private readonly logger?: TelemetryLoggerExt,
529
526
  public readonly breaker: Breakable = new Breakable("TreeCheckout"),
530
- public readonly disposeForksAfterTransaction = true,
531
527
  ) {
532
528
  this.#transaction = this.createTransactionStack(branch);
533
529
  this.editLock = new EditLock(this.#transaction.activeBranchEditor);
@@ -659,9 +655,6 @@ export class TreeCheckout implements ITreeCheckout {
659
655
  branch: SharedTreeBranch<SharedTreeEditBuilder, SharedTreeChange>,
660
656
  ): SquashingTransactionStack<SharedTreeEditBuilder, SharedTreeChange> {
661
657
  return new SquashingTransactionStack(branch, this.mintRevisionTag, () => {
662
- const disposeForks = this.disposeForksAfterTransaction
663
- ? trackForksForDisposal(this)
664
- : undefined;
665
658
  // When each transaction is started, make a restorable checkpoint of the current state of removed roots
666
659
  const restoreRemovedRoots = this._removedRoots.createCheckpoint();
667
660
  return (result, viewUpdate: SharedTreeChange | undefined) => {
@@ -688,7 +681,6 @@ export class TreeCheckout implements ITreeCheckout {
688
681
  unreachableCase(result);
689
682
  }
690
683
  }
691
- disposeForks?.();
692
684
  };
693
685
  });
694
686
  }
@@ -788,19 +780,48 @@ export class TreeCheckout implements ITreeCheckout {
788
780
  labels: buildLabelsSet(this.labelTreeNode),
789
781
  };
790
782
 
791
- this.#events.emit("changed", metadata, getRevertible);
783
+ this.emitChangedLocked(() => {
784
+ this.#events.emit("changed", metadata, getRevertible);
785
+ });
792
786
  withinEventContext = false;
793
787
  }
794
788
  } else if (this.isRemoteChangeEvent(event)) {
795
789
  // TODO: figure out how to plumb through commit kind info for remote changes
796
- this.#events.emit("changed", {
797
- isLocal: false,
798
- kind: CommitKind.Default,
799
- labels: new Set<unknown>(),
790
+ this.emitChangedLocked(() => {
791
+ this.#events.emit("changed", {
792
+ isLocal: false,
793
+ kind: CommitKind.Default,
794
+ labels: new Set<unknown>(),
795
+ });
800
796
  });
801
797
  }
802
798
  };
803
799
 
800
+ /**
801
+ * Hold the `editLock` for the duration of `emit`, so that re-entrant edits, transactions,
802
+ * branch operations, etc. attempted from inside a `changed` listener throw the canonical
803
+ * "forbidden during a change event" {@link UsageError} via {@link EditLock.checkUnlocked}.
804
+ *
805
+ * @remarks
806
+ * Shared by both the local and remote `changed` emission paths in {@link TreeCheckout.onAfterBranchChange}.
807
+ * The `try`/`finally` ensures the lock is released even if a listener throws.
808
+ */
809
+ private emitChangedLocked(emit: () => void): void {
810
+ this.editLock.lock();
811
+ try {
812
+ emit();
813
+ } finally {
814
+ // TODO: any event that throws potentially leaves the code which triggered that event,
815
+ // and thus this checkout (and likely more) in a broken state.
816
+ // Unlocking this editLock prevents future use of this broken state from giving a confusing error in this case,
817
+ // however, a better approach would probably be to put something (this checkout and/or the editLock)
818
+ // into a broken state (using a properly scoped `Breakable`),
819
+ // likely by moving emitChangedLocked into EditLock, and having EditLock get a Breakable,
820
+ // and having the new emitChangedLocked use `Breakable.use`.
821
+ this.editLock.unlock();
822
+ }
823
+ }
824
+
804
825
  private readonly onAfterChange = (event: SharedTreeBranchChange<SharedTreeChange>): void => {
805
826
  this.editLock.lock();
806
827
  this.#events.emit("beforeBatch", event);
@@ -908,6 +929,15 @@ export class TreeCheckout implements ITreeCheckout {
908
929
 
909
930
  private mountTransaction(params: RunTransactionParams | undefined, isAsync: boolean): void {
910
931
  this.checkNotDisposed();
932
+ // Starting a transaction is an edit, so it is forbidden from within a change-event
933
+ // callback (where the edit lock is held), the same as direct edits. For the async
934
+ // entry point this throw is captured as a rejected promise by the `async` wrapper.
935
+ //
936
+ // Note: because runTransaction/runTransactionAsync are `@breakingMethod`, this throw also
937
+ // puts the checkout into a broken state (unlike a direct edit, which throws recoverably).
938
+ // That is the same pre-existing broken-state limitation tracked by the TODO in
939
+ // `emitChangedLocked`, not something specific to transactions.
940
+ this.editLock.checkUnlocked("Running a transaction");
911
941
  if (isAsync && this.transaction.size > 0) {
912
942
  throw new UsageError(
913
943
  "An asynchronous transaction cannot be started while another transaction is already in progress.",
@@ -1170,7 +1200,6 @@ export class TreeCheckout implements ITreeCheckout {
1170
1200
  throw new UsageError("A view cannot be forked while it has a pending transaction.");
1171
1201
  }
1172
1202
 
1173
- this.editLock.checkUnlocked("Branching");
1174
1203
  const branch = this.#transaction.activeBranch.fork();
1175
1204
  const storedSchema = this.storedSchema.clone();
1176
1205
  const forkBreaker = new Breakable("TreeCheckout");
@@ -1187,7 +1216,6 @@ export class TreeCheckout implements ITreeCheckout {
1187
1216
  this._removedRoots.clone(),
1188
1217
  this.logger,
1189
1218
  forkBreaker,
1190
- this.disposeForksAfterTransaction,
1191
1219
  );
1192
1220
  this.#events.emit("fork", checkout);
1193
1221
  return checkout;
@@ -1250,6 +1278,19 @@ export class TreeCheckout implements ITreeCheckout {
1250
1278
  getCheckout(branch).rebase(this);
1251
1279
  }
1252
1280
 
1281
+ public isMissingEditsFrom(branch: TreeBranch): boolean {
1282
+ const branchCheckout = getCheckout(branch);
1283
+ const targetPath: GraphCommit<unknown>[] = [];
1284
+ const ancestor = findCommonAncestor(this.mainBranch.getHead(), [
1285
+ branchCheckout.mainBranch.getHead(),
1286
+ targetPath,
1287
+ ]);
1288
+ if (ancestor === undefined) {
1289
+ throw new UsageError("Branches do not share a common ancestor.");
1290
+ }
1291
+ return targetPath.length > 0;
1292
+ }
1293
+
1253
1294
  public merge(branch: TreeBranch): void;
1254
1295
  public merge(branch: TreeBranch, disposeMerged: boolean): void;
1255
1296
  public merge(branch: TreeBranch, disposeMerged = true): void {
@@ -1681,12 +1722,7 @@ class EditLock {
1681
1722
  */
1682
1723
  public checkUnlocked<T extends string>(action: T extends Capitalize<T> ? T : never): void {
1683
1724
  if (this.locked) {
1684
- // These type assertions ensure that the event name strings used here match the actual event names
1685
- const nodeChanged: keyof TreeChangeEvents = "nodeChanged";
1686
- const treeChanged: keyof TreeChangeEvents = "treeChanged";
1687
- throw new UsageError(
1688
- `${action} is forbidden during a ${nodeChanged} or ${treeChanged} event`,
1689
- );
1725
+ throw new UsageError(`${action} is forbidden during a change event callback`);
1690
1726
  }
1691
1727
  }
1692
1728
 
@@ -1700,34 +1736,6 @@ class EditLock {
1700
1736
  }
1701
1737
  }
1702
1738
 
1703
- /**
1704
- * Keeps track of all new forks created until the returned function is invoked, which will dispose all of those for.
1705
- * The returned function may only be called once.
1706
- *
1707
- * @param checkout - The tree checkout for which you want to monitor forks for disposal.
1708
- * @returns a function which can be called to dispose all of the tracked forks.
1709
- */
1710
- function trackForksForDisposal(checkout: TreeCheckout): () => void {
1711
- const forks = new Set<TreeCheckout>();
1712
- const onDisposeUnSubscribes: (() => void)[] = [];
1713
- const onForkUnSubscribe = onForkTransitive(checkout, (fork) => {
1714
- forks.add(fork);
1715
- onDisposeUnSubscribes.push(fork.events.on("dispose", () => forks.delete(fork)));
1716
- });
1717
- let disposed = false;
1718
- return () => {
1719
- assert(!disposed, 0xaa9 /* Forks may only be disposed once */);
1720
- for (const fork of forks) {
1721
- fork.dispose();
1722
- }
1723
- for (const unsubscribe of onDisposeUnSubscribes) {
1724
- unsubscribe();
1725
- }
1726
- onForkUnSubscribe();
1727
- disposed = true;
1728
- };
1729
- }
1730
-
1731
1739
  function verboseFromCursor(
1732
1740
  reader: ITreeCursor,
1733
1741
  schema: ReadonlyMap<TreeNodeSchemaIdentifier, TreeNodeStoredSchema>,
@@ -342,6 +342,17 @@ export interface TreeBranchAlpha extends TreeBranch, TreeContextAlpha {
342
342
  * Update the tests and docs to match when that is done.
343
343
  */
344
344
  applyChange(change: JsonCompatibleReadOnly): void;
345
+
346
+ /**
347
+ * Determines if there are changes on the given branch that are not present on this branch.
348
+ * @param branch - The branch to compare to.
349
+ *
350
+ * The new edits, if any, can be applied to this branch by {@link TreeBranch.rebaseOnto | rebasing this branch onto the given branch}
351
+ * or by {@link TreeBranch.merge | merging the given branch into this branch}.
352
+ *
353
+ * @throws UsageError if the branches are unrelated.
354
+ */
355
+ isMissingEditsFrom(branch: TreeBranch): boolean;
345
356
  }
346
357
 
347
358
  /**