diff --git a/packages/dds/tree/src/core/forest/forest.ts b/packages/dds/tree/src/core/forest/forest.ts index a57f8325a0b0..403dceac2f00 100644 --- a/packages/dds/tree/src/core/forest/forest.ts +++ b/packages/dds/tree/src/core/forest/forest.ts @@ -58,7 +58,12 @@ export interface ForestEvents { * * When invalidating, all outstanding cursors must be freed or cleared. */ -export interface IForestSubscription extends Listenable { +export interface IForestSubscription { + /** + * Events for this forest. + */ + readonly events: Listenable; + /** * Set of anchors this forest is tracking. * diff --git a/packages/dds/tree/src/core/index.ts b/packages/dds/tree/src/core/index.ts index 2dcc3e9d38ef..948b96836e26 100644 --- a/packages/dds/tree/src/core/index.ts +++ b/packages/dds/tree/src/core/index.ts @@ -121,7 +121,7 @@ export { type TreeFieldStoredSchema, ValueSchema, TreeNodeStoredSchema, - type TreeStoredSchemaSubscription as TreeStoredSchemaSubscription, + type TreeStoredSchemaSubscription, type MutableTreeStoredSchema, type FieldKindIdentifier, type FieldKindData, diff --git a/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts b/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts index 2c1d3d81bfee..1081a4ec15c0 100644 --- a/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts +++ b/packages/dds/tree/src/core/schema-stored/storedSchemaRepository.ts @@ -37,9 +37,12 @@ export interface SchemaEvents { /** * A collection of stored schema that fires events in response to changes. */ -export interface TreeStoredSchemaSubscription - extends Listenable, - TreeStoredSchema {} +export interface TreeStoredSchemaSubscription extends TreeStoredSchema { + /** + * Events for this schema subscription. + */ + readonly events: Listenable; +} /** * Mutable collection of stored schema. @@ -59,7 +62,8 @@ export interface MutableTreeStoredSchema extends TreeStoredSchemaSubscription { export class TreeStoredSchemaRepository implements MutableTreeStoredSchema { protected nodeSchemaData: BTree; protected rootFieldSchemaData: TreeFieldStoredSchema; - protected readonly events = createEmitter(); + protected readonly _events = createEmitter(); + public readonly events: Listenable = this._events; /** * Copies in the provided schema. If `data` is an TreeStoredSchemaRepository, it will be cheap-cloned. @@ -92,13 +96,6 @@ export class TreeStoredSchemaRepository implements MutableTreeStoredSchema { } } - public on( - eventName: K, - listener: SchemaEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public get nodeSchema(): ReadonlyMap { // Btree implements iterator, but not in a type-safe way return this.nodeSchemaData as unknown as ReadonlyMap< @@ -112,12 +109,12 @@ export class TreeStoredSchemaRepository implements MutableTreeStoredSchema { } public apply(newSchema: TreeStoredSchema): void { - this.events.emit("beforeSchemaChange", newSchema); + this._events.emit("beforeSchemaChange", newSchema); const clone = new TreeStoredSchemaRepository(newSchema); // In the future, we could use btree's delta functionality to do a more efficient update this.rootFieldSchemaData = clone.rootFieldSchemaData; this.nodeSchemaData = clone.nodeSchemaData; - this.events.emit("afterSchemaChange", newSchema); + this._events.emit("afterSchemaChange", newSchema); } public clone(): TreeStoredSchemaRepository { diff --git a/packages/dds/tree/src/core/tree/anchorSet.ts b/packages/dds/tree/src/core/tree/anchorSet.ts index f255c2544676..73c3b88e9e75 100644 --- a/packages/dds/tree/src/core/tree/anchorSet.ts +++ b/packages/dds/tree/src/core/tree/anchorSet.ts @@ -206,7 +206,12 @@ export interface AnchorSetRootEvents { /** * Node in a tree of anchors. */ -export interface AnchorNode extends UpPath, Listenable { +export interface AnchorNode extends UpPath { + /** + * Events for this anchor node. + */ + readonly events: Listenable; + /** * Allows access to data stored on the Anchor in "slots". * Use {@link anchorSlot} to create slots. @@ -277,8 +282,10 @@ export function anchorSlot(): AnchorSlot { * * @sealed */ -export class AnchorSet implements Listenable, AnchorLocator { - private readonly events = createEmitter(); +export class AnchorSet implements AnchorLocator { + readonly #events = createEmitter(); + public readonly events: Listenable = this.#events; + /** * Incrementing counter to give each anchor in this set a unique index for its identifier. * "0" is reserved for the `NeverAnchor`. @@ -312,7 +319,7 @@ export class AnchorSet implements Listenable, AnchorLocator private activeVisitor?: DeltaVisitor; public constructor() { - this.on("treeChanging", () => { + this.events.on("treeChanging", () => { this.generationNumber += 1; }); } @@ -344,13 +351,6 @@ export class AnchorSet implements Listenable, AnchorLocator } } - public on( - eventName: K, - listener: AnchorSetRootEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - /** * Check if there are currently no anchors tracked. * Mainly for testing anchor cleanup. @@ -792,7 +792,7 @@ export class AnchorSet implements Listenable, AnchorLocator notifyChildrenChanging(): void { this.maybeWithNode( (p) => p.events.emit("childrenChanging", p), - () => this.anchorSet.events.emit("childrenChanging", this.anchorSet), + () => this.anchorSet.#events.emit("childrenChanging", this.anchorSet), ); }, notifyChildrenChanged(): void { @@ -1098,7 +1098,7 @@ export class AnchorSet implements Listenable, AnchorLocator this.parentField = undefined; }, }; - this.events.emit("treeChanging", this); + this.#events.emit("treeChanging", this); this.activeVisitor = visitor; return visitor; } @@ -1209,13 +1209,6 @@ class PathNode extends ReferenceCountedBase implements UpPath, AnchorN super(1); } - public on( - eventName: K, - listener: AnchorEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public child(key: FieldKey, index: number): UpPath { // Fast path: if child exists, return it. return ( diff --git a/packages/dds/tree/src/events/emitter.ts b/packages/dds/tree/src/events/emitter.ts index 7f0f7e5ac1c3..73bbbdddbfcc 100644 --- a/packages/dds/tree/src/events/emitter.ts +++ b/packages/dds/tree/src/events/emitter.ts @@ -63,7 +63,7 @@ export interface HasListeners> { /** * Provides an API for subscribing to and listening to events. * - * @remarks Classes wishing to emit events may either extend this class or compose over it. + * @remarks Classes wishing to emit events may either extend this class, compose over it, or expose it as a property of type {@link Listenable}. * * @example Extending this class * @@ -97,6 +97,20 @@ export interface HasListeners> { * } * } * ``` + * + * @example Exposing this class as a property + * + * ```typescript + * class MyExposingClass { + * private readonly _events = createEmitter(); + * public readonly events: Listenable = this._events; + * + * private load() { + * this._events.emit("loaded"); + * const results: number[] = this._events.emitAndCollect("computed"); + * } + * } + * ``` */ export class EventEmitter> implements Listenable, HasListeners diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts index 8a201c8a092e..521eea910817 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkTree.ts @@ -134,7 +134,7 @@ export class Chunker implements IChunker { if (cached !== undefined) { return cached; } - this.unregisterSchemaCallback = this.schema.on("afterSchemaChange", () => + this.unregisterSchemaCallback = this.schema.events.on("afterSchemaChange", () => this.schemaChanged(), ); return this.tryShapeFromSchema(this.schema, this.policy, schema, this.typeShapes); diff --git a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts index 3218c0ab22ba..abd2236975e9 100644 --- a/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts +++ b/packages/dds/tree/src/feature-libraries/chunked-forest/chunkedForest.ts @@ -28,7 +28,7 @@ import { mapCursorField, rootFieldKey, } from "../../core/index.js"; -import { createEmitter } from "../../events/index.js"; +import { createEmitter, type Listenable } from "../../events/index.js"; import { assertValidRange, brand, fail, getOrAddEmptyToMap } from "../../util/index.js"; import { BasicChunk, BasicChunkCursor, type SiblingsOrKey } from "./basicChunk.js"; @@ -53,7 +53,8 @@ interface StackNode { export class ChunkedForest implements IEditableForest { private activeVisitor?: DeltaVisitor; - private readonly events = createEmitter(); + readonly #events = createEmitter(); + public readonly events: Listenable = this.#events; /** * @param roots - dummy node above the root under which detached fields are stored. All content of the forest is reachable from this. @@ -73,13 +74,6 @@ export class ChunkedForest implements IEditableForest { return this.roots.fields.size === 0; } - public on( - eventName: K, - listener: ForestEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public clone(schema: TreeStoredSchemaSubscription, anchors: AnchorSet): ChunkedForest { this.roots.referenceAdded(); return new ChunkedForest(this.roots, schema, this.chunker.clone(schema), anchors); @@ -119,11 +113,11 @@ export class ChunkedForest implements IEditableForest { this.forest.activeVisitor = undefined; }, destroy(detachedField: FieldKey, count: number): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); this.forest.roots.fields.delete(detachedField); }, create(content: ProtoNodes, destination: FieldKey): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); const chunks: TreeChunk[] = content.map((c) => chunkTree(c, { policy: this.forest.chunker, @@ -131,7 +125,7 @@ export class ChunkedForest implements IEditableForest { }), ); this.forest.roots.fields.set(destination, chunks); - this.forest.events.emit("afterRootFieldCreated", destination); + this.forest.#events.emit("afterRootFieldCreated", destination); }, attach(source: FieldKey, count: number, destination: PlaceIndex): void { this.attachEdit(source, count, destination); @@ -146,7 +140,7 @@ export class ChunkedForest implements IEditableForest { * @param destination - The index in the current field at which to attach the content. */ attachEdit(source: FieldKey, count: number, destination: PlaceIndex): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); const sourceField = this.forest.roots.fields.get(source) ?? []; this.forest.roots.fields.delete(source); if (sourceField.length === 0) { @@ -166,7 +160,7 @@ export class ChunkedForest implements IEditableForest { * If not specified, the detached range is destroyed. */ detachEdit(source: Range, destination: FieldKey | undefined): void { - this.forest.events.emit("beforeChange"); + this.forest.#events.emit("beforeChange"); const parent = this.getParent(); const sourceField = parent.mutableChunk.fields.get(parent.key) ?? []; diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/context.ts b/packages/dds/tree/src/feature-libraries/flex-tree/context.ts index 8ec7112c79aa..7a2f57f87f9f 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/context.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/context.ts @@ -49,7 +49,8 @@ export interface FlexTreeContext { * A common context of a "forest" of FlexTrees. * It handles group operations like transforming cursors into anchors for edits. */ -export interface FlexTreeHydratedContext extends FlexTreeContext, Listenable { +export interface FlexTreeHydratedContext extends FlexTreeContext { + readonly events: Listenable; /** * Gets the root field of the tree. */ @@ -92,7 +93,7 @@ export class Context implements FlexTreeHydratedContext, IDisposable { public readonly nodeKeyManager: NodeKeyManager, ) { this.eventUnregister = [ - this.checkout.forest.on("beforeChange", () => { + this.checkout.forest.events.on("beforeChange", () => { this.prepareForEdit(); }), ]; @@ -160,11 +161,8 @@ export class Context implements FlexTreeHydratedContext, IDisposable { return field; } - public on( - eventName: K, - listener: ForestEvents[K], - ): () => void { - return this.checkout.forest.on(eventName, listener); + public get events(): Listenable { + return this.checkout.forest.events; } } diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts index d3c734662835..548496de74a8 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/lazyField.ts @@ -151,7 +151,7 @@ export abstract class LazyField extends LazyEntity implements FlexT const anchorNode = context.checkout.forest.anchors.locate(fieldAnchor.parent) ?? fail("parent anchor node should always exist since field is under a node"); - this.offAfterDestroy = anchorNode.on("afterDestroy", () => { + this.offAfterDestroy = anchorNode.events.on("afterDestroy", () => { this[disposeSymbol](); }); } diff --git a/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts b/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts index 021c2fe305f9..ee6ff1962214 100644 --- a/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts +++ b/packages/dds/tree/src/feature-libraries/flex-tree/lazyNode.ts @@ -89,7 +89,7 @@ export class LazyTreeNode extends LazyEntity implements FlexTreeNode { this.storedSchema = context.schema.nodeSchema.get(this.schema) ?? fail("missing schema"); assert(cursor.mode === CursorLocationType.Nodes, 0x783 /* must be in nodes mode */); anchorNode.slots.set(flexTreeSlot, this); - this.#removeDeleteCallback = anchorNode.on("afterDestroy", cleanupTree); + this.#removeDeleteCallback = anchorNode.events.on("afterDestroy", cleanupTree); } public borrowCursor(): ITreeCursorSynchronous { diff --git a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts index 61f5d1f19db6..ffacb467ef3c 100644 --- a/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts +++ b/packages/dds/tree/src/feature-libraries/object-forest/objectForest.ts @@ -33,7 +33,7 @@ import { aboveRootPlaceholder, deepCopyMapTree, } from "../../core/index.js"; -import { createEmitter } from "../../events/index.js"; +import { createEmitter, type Listenable } from "../../events/index.js"; import { assertNonNegativeSafeInteger, assertValidIndex, @@ -73,7 +73,8 @@ export class ObjectForest implements IEditableForest { // All cursors that are in the "Current" state. Must be empty when editing. public readonly currentCursors: Set = new Set(); - private readonly events = createEmitter(); + readonly #events = createEmitter(); + public readonly events: Listenable = this.#events; readonly #roots: MutableMapTree; public get roots(): MapTree { @@ -98,13 +99,6 @@ export class ObjectForest implements IEditableForest { return this.roots.fields.size === 0; } - public on( - eventName: K, - listener: ForestEvents[K], - ): () => void { - return this.events.on(eventName, listener); - } - public clone(_: TreeStoredSchemaSubscription, anchors: AnchorSet): ObjectForest { return new ObjectForest(anchors, this.additionalAsserts, this.roots); } @@ -133,7 +127,7 @@ export class ObjectForest implements IEditableForest { * This is required for each change since there may be app facing change event handlers which create cursors. */ const preEdit = (): void => { - this.events.emit("beforeChange"); + this.#events.emit("beforeChange"); assert( this.currentCursors.has(cursor), 0x995 /* missing visitor cursor while editing */, @@ -168,7 +162,7 @@ export class ObjectForest implements IEditableForest { public create(content: ProtoNodes, destination: FieldKey): void { preEdit(); this.forest.add(content, destination); - this.forest.events.emit("afterRootFieldCreated", destination); + this.forest.#events.emit("afterRootFieldCreated", destination); } public attach(source: FieldKey, count: number, destination: PlaceIndex): void { preEdit(); diff --git a/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts b/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts index b0f92028a0ea..13960fd2a229 100644 --- a/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts +++ b/packages/dds/tree/src/feature-libraries/schema-index/schemaSummarizer.ts @@ -52,7 +52,7 @@ export class SchemaSummarizer implements Summarizable { collabWindow: CollabWindow, ) { this.codec = makeSchemaCodec(options); - this.schema.on("afterSchemaChange", () => { + this.schema.events.on("afterSchemaChange", () => { // Invalidate the cache, as we need to regenerate the blob if the schema changes // We are assuming that schema changes from remote ops are valid, as we are in a summarization context. this.schemaIndexLastChangedSeq = collabWindow.getCurrentSeq(); diff --git a/packages/dds/tree/src/shared-tree-core/branch.ts b/packages/dds/tree/src/shared-tree-core/branch.ts index f7d53664543f..aaac692a3a8f 100644 --- a/packages/dds/tree/src/shared-tree-core/branch.ts +++ b/packages/dds/tree/src/shared-tree-core/branch.ts @@ -21,7 +21,7 @@ import { tagRollbackInverse, type RebaseStatsWithDuration, } from "../core/index.js"; -import { EventEmitter, type Listenable } from "../events/index.js"; +import { createEmitter, type Listenable } from "../events/index.js"; import { TransactionStack } from "./transactionStack.js"; import { fail } from "../util/index.js"; @@ -175,10 +175,9 @@ export interface BranchTrimmingEvents { /** * A branch of changes that can be applied to a SharedTree. */ -export class SharedTreeBranch< - TEditor extends ChangeFamilyEditor, - TChange, -> extends EventEmitter> { +export class SharedTreeBranch { + readonly #events = createEmitter>(); + public readonly events: Listenable> = this.#events; public readonly editor: TEditor; private readonly transactions = new TransactionStack(); /** @@ -222,12 +221,11 @@ export class SharedTreeBranch< keyof RebaseStatsWithDuration >, ) { - super(); this.editor = this.changeFamily.buildEditor(mintRevisionTag, (change) => this.apply(change), ); this.unsubscribeBranchTrimmer = branchTrimmer?.on("ancestryTrimmed", (commit) => { - this.emit("ancestryTrimmed", commit); + this.#events.emit("ancestryTrimmed", commit); }); } @@ -267,9 +265,9 @@ export class SharedTreeBranch< newCommits: [newHead], } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = newHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return [taggedChange.change, newHead]; } @@ -290,7 +288,7 @@ export class SharedTreeBranch< const onDisposeUnSubscribes: (() => void)[] = []; const onForkUnSubscribe = onForkTransitive(this, (fork) => { forks.add(fork); - onDisposeUnSubscribes.push(fork.on("dispose", () => forks.delete(fork))); + onDisposeUnSubscribes.push(fork.events.on("dispose", () => forks.delete(fork))); }); this.transactions.push(this.head.revision, () => { forks.forEach((fork) => fork.dispose()); @@ -298,7 +296,7 @@ export class SharedTreeBranch< onForkUnSubscribe(); }); this.editor.enterTransaction(); - this.emit("transactionStarted", this.transactions.size === 1); + this.#events.emit("transactionStarted", this.transactions.size === 1); } /** @@ -315,7 +313,7 @@ export class SharedTreeBranch< const [startCommit, commits] = this.popTransaction(); this.editor.exitTransaction(); - this.emit("transactionCommitted", this.transactions.size === 0); + this.#events.emit("transactionCommitted", this.transactions.size === 0); if (commits.length === 0) { return undefined; } @@ -336,9 +334,9 @@ export class SharedTreeBranch< newCommits: [newHead], } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = newHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return [commits, newHead]; } @@ -356,9 +354,9 @@ export class SharedTreeBranch< const [startCommit, commits] = this.popTransaction(); this.editor.exitTransaction(); - this.emit("transactionAborted", this.transactions.size === 0); + this.#events.emit("transactionAborted", this.transactions.size === 0); if (commits.length === 0) { - this.emit("transactionRolledBack", this.transactions.size === 0); + this.#events.emit("transactionRolledBack", this.transactions.size === 0); return [undefined, []]; } @@ -384,10 +382,10 @@ export class SharedTreeBranch< removedCommits: commits, } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = startCommit; - this.emit("afterChange", changeEvent); - this.emit("transactionRolledBack", this.transactions.size === 0); + this.#events.emit("afterChange", changeEvent); + this.#events.emit("transactionRolledBack", this.transactions.size === 0); return [change, commits]; } @@ -435,7 +433,7 @@ export class SharedTreeBranch< this.mintRevisionTag, this.branchTrimmer, ); - this.emit("fork", fork); + this.#events.emit("fork", fork); return fork; } @@ -483,9 +481,9 @@ export class SharedTreeBranch< newCommits, } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = newSourceHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return rebaseResult; } @@ -529,9 +527,9 @@ export class SharedTreeBranch< newCommits: sourceCommits, } as const; - this.emit("beforeChange", changeEvent); + this.#events.emit("beforeChange", changeEvent); this.head = rebaseResult.newSourceHead; - this.emit("afterChange", changeEvent); + this.#events.emit("afterChange", changeEvent); return [change, sourceCommits]; } @@ -585,7 +583,7 @@ export class SharedTreeBranch< this.unsubscribeBranchTrimmer?.(); this.disposed = true; - this.emit("dispose"); + this.#events.emit("dispose"); } private assertNotDisposed(): void { @@ -594,20 +592,22 @@ export class SharedTreeBranch< } /** - * Registers an event listener that fires when the given forkable object forks. + * Registers an event listener that fires when the given branch forks. * The listener will also fire when any of those forks fork, and when those forks of forks fork, and so on. - * @param forkable - an object that emits an event when it is forked + * @param branch - the branch that will be listened to for forks * @param onFork - the fork event listener * @returns a function which when called will deregister all registrations (including transitive) created by this function. * The deregister function has undefined behavior if called more than once. */ -export function onForkTransitive void }>>( - forkable: T, +// Branches are invariant over TChange +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function onForkTransitive>( + branch: T, onFork: (fork: T) => void, ): () => void { const offs: (() => void)[] = []; offs.push( - forkable.on("fork", (fork) => { + branch.events.on("fork", (fork: T) => { offs.push(onForkTransitive(fork, onFork)); onFork(fork); }), diff --git a/packages/dds/tree/src/shared-tree-core/editManager.ts b/packages/dds/tree/src/shared-tree-core/editManager.ts index 8e7e775e5a48..e4cd45958064 100644 --- a/packages/dds/tree/src/shared-tree-core/editManager.ts +++ b/packages/dds/tree/src/shared-tree-core/editManager.ts @@ -191,7 +191,7 @@ export class EditManager< this.telemetryEventBatcher, ); - this.localBranch.on("afterChange", (event) => { + this.localBranch.events.on("afterChange", (event) => { if (event.type === "append") { for (const commit of event.newCommits) { this.localCommits.push(commit); @@ -223,19 +223,19 @@ export class EditManager< private registerBranch(branch: SharedTreeBranch): void { this.trackBranch(branch); // Whenever the branch is rebased, update our record of its base trunk commit - const offBeforeRebase = branch.on("beforeChange", (args) => { + const offBeforeRebase = branch.events.on("beforeChange", (args) => { if (args.type === "replace" && getChangeReplaceType(args) === "rebase") { this.untrackBranch(branch); } }); - const offAfterRebase = branch.on("afterChange", (args) => { + const offAfterRebase = branch.events.on("afterChange", (args) => { if (args.type === "replace" && getChangeReplaceType(args) === "rebase") { this.trackBranch(branch); this.trimTrunk(); } }); // When the branch is disposed, update our branch set and trim the trunk - const offDispose = branch.on("dispose", () => { + const offDispose = branch.events.on("dispose", () => { this.untrackBranch(branch); this.trimTrunk(); offBeforeRebase(); diff --git a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts index 29c787798db9..8ce343b4c837 100644 --- a/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts +++ b/packages/dds/tree/src/shared-tree-core/sharedTreeCore.ts @@ -183,16 +183,16 @@ export class SharedTreeCore this.mintRevisionTag, rebaseLogger, ); - this.editManager.localBranch.on("transactionStarted", () => { + this.editManager.localBranch.events.on("transactionStarted", () => { this.commitEnricher.startNewTransaction(); }); - this.editManager.localBranch.on("transactionAborted", () => { + this.editManager.localBranch.events.on("transactionAborted", () => { this.commitEnricher.abortCurrentTransaction(); }); - this.editManager.localBranch.on("transactionCommitted", () => { + this.editManager.localBranch.events.on("transactionCommitted", () => { this.commitEnricher.commitCurrentTransaction(); }); - this.editManager.localBranch.on("beforeChange", (change) => { + this.editManager.localBranch.events.on("beforeChange", (change) => { // Ensure that any previously prepared commits that have not been sent are purged. this.commitEnricher.purgePreparedCommits(); if (this.detachedRevision !== undefined) { @@ -219,7 +219,7 @@ export class SharedTreeCore this.commitEnricher.prepareCommit(change.newCommits[0] ?? oob(), true); } }); - this.editManager.localBranch.on("afterChange", (change) => { + this.editManager.localBranch.events.on("afterChange", (change) => { if (this.getLocalBranch().isTransacting()) { // We do not submit ops for changes that are part of a transaction. return; diff --git a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts index 47440042b680..bcf1d14dc851 100644 --- a/packages/dds/tree/src/shared-tree/schematizingTreeView.ts +++ b/packages/dds/tree/src/shared-tree/schematizingTreeView.ts @@ -297,7 +297,7 @@ export class SchematizingSimpleTreeView< new HydratedContext(this.rootFieldSchema.allowedTypeSet, view.context), ); - const unregister = this.checkout.storedSchema.on("afterSchemaChange", () => { + const unregister = this.checkout.storedSchema.events.on("afterSchemaChange", () => { unregister(); this.unregisterCallbacks.delete(unregister); view[disposeSymbol](); @@ -307,7 +307,7 @@ export class SchematizingSimpleTreeView< this.view = undefined; this.checkout.forest.anchors.slots.delete(SimpleContextSlot); - const unregister = this.checkout.storedSchema.on("afterSchemaChange", () => { + const unregister = this.checkout.storedSchema.events.on("afterSchemaChange", () => { unregister(); this.unregisterCallbacks.delete(unregister); this.update(); diff --git a/packages/dds/tree/src/shared-tree/treeCheckout.ts b/packages/dds/tree/src/shared-tree/treeCheckout.ts index e60f4449182b..54feae4ee834 100644 --- a/packages/dds/tree/src/shared-tree/treeCheckout.ts +++ b/packages/dds/tree/src/shared-tree/treeCheckout.ts @@ -444,15 +444,15 @@ export class TreeCheckout implements ITreeCheckoutFork { private readonly breaker: Breakable = new Breakable("TreeCheckout"), ) { // when a transaction is started, take a snapshot of the current state of removed roots - _branch.on("transactionStarted", () => { + _branch.events.on("transactionStarted", () => { this.removedRootsSnapshots.push(this.removedRoots.clone()); }); // when a transaction is committed, the latest snapshot of removed roots can be discarded - _branch.on("transactionCommitted", () => { + _branch.events.on("transactionCommitted", () => { this.removedRootsSnapshots.pop(); }); // after a transaction is rolled back, revert removed roots back to the latest snapshot - _branch.on("transactionRolledBack", () => { + _branch.events.on("transactionRolledBack", () => { const snapshot = this.removedRootsSnapshots.pop(); assert(snapshot !== undefined, 0x9ae /* a snapshot for removed roots does not exist */); this.removedRoots = snapshot; @@ -462,7 +462,7 @@ export class TreeCheckout implements ITreeCheckoutFork { // For example, a bug in the editor might produce a malformed change object and thus applying the change to the forest will throw an error. // In such a case we will crash here, preventing the change from being added to the commit graph, and preventing `afterChange` from firing. // One important consequence of this is that we will not submit the op containing the invalid change, since op submissions happens in response to `afterChange`. - _branch.on("beforeChange", (event) => { + _branch.events.on("beforeChange", (event) => { if (event.change !== undefined) { const revision = event.type === "replace" @@ -509,7 +509,7 @@ export class TreeCheckout implements ITreeCheckoutFork { } } }); - _branch.on("afterChange", (event) => { + _branch.events.on("afterChange", (event) => { // The following logic allows revertibles to be generated for the change. // Currently only appends (including merges) and transaction commits are supported. if (!_branch.isTransacting()) { @@ -590,7 +590,7 @@ export class TreeCheckout implements ITreeCheckoutFork { // When the branch is trimmed, we can garbage collect any repair data whose latest relevant revision is one of the // trimmed revisions. - _branch.on("ancestryTrimmed", (revisions) => { + _branch.events.on("ancestryTrimmed", (revisions) => { this.withCombinedVisitor((visitor) => { revisions.forEach((revision) => { // get all the roots last created or used by the revision @@ -653,7 +653,7 @@ export class TreeCheckout implements ITreeCheckoutFork { } public get rootEvents(): Listenable { - return this.forest.anchors; + return this.forest.anchors.events; } public get editor(): ISharedTreeEditor { diff --git a/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts b/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts index 3154ed67d8e0..2211d21212d5 100644 --- a/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts +++ b/packages/dds/tree/src/simple-tree/api/treeNodeApi.ts @@ -168,7 +168,7 @@ export const treeNodeApi: TreeNodeApi = { case "nodeChanged": { const nodeSchema = kernel.schema; if (isObjectNodeSchema(nodeSchema)) { - return kernel.on("childrenChangedAfterBatch", ({ changedFields }) => { + return kernel.events.on("childrenChangedAfterBatch", ({ changedFields }) => { const changedProperties = new Set( Array.from( changedFields, @@ -180,17 +180,17 @@ export const treeNodeApi: TreeNodeApi = { listener({ changedProperties }); }); } else if (nodeSchema.kind === NodeKind.Array) { - return kernel.on("childrenChangedAfterBatch", () => { + return kernel.events.on("childrenChangedAfterBatch", () => { listener({ changedProperties: undefined }); }); } else { - return kernel.on("childrenChangedAfterBatch", ({ changedFields }) => { + return kernel.events.on("childrenChangedAfterBatch", ({ changedFields }) => { listener({ changedProperties: changedFields }); }); } } case "treeChanged": { - return kernel.on("subtreeChangedAfterBatch", () => listener({})); + return kernel.events.on("subtreeChangedAfterBatch", () => listener({})); } default: throw new UsageError(`No event named ${JSON.stringify(eventName)}.`); diff --git a/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts b/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts index 1a8331d2b1e9..a7e3d705b93f 100644 --- a/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts +++ b/packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts @@ -95,7 +95,7 @@ function isHydrated(state: HydrationState): state is HydratedState { * The kernel has the same lifetime as the node and spans both its unhydrated and hydrated states. * When hydration occurs, the kernel is notified via the {@link TreeNodeKernel.hydrate | hydrate} method. */ -export class TreeNodeKernel implements Listenable { +export class TreeNodeKernel { private disposed = false; /** @@ -205,9 +205,9 @@ export class TreeNodeKernel implements Listenable { this.#hydrationState = { anchorNode, offAnchorNode: new Set([ - anchorNode.on("afterDestroy", () => this.dispose()), + anchorNode.events.on("afterDestroy", () => this.dispose()), // TODO: this should be triggered on change even for unhydrated nodes. - anchorNode.on("childrenChanging", () => { + anchorNode.events.on("childrenChanging", () => { this.generationNumber += 1; }), ]), @@ -221,7 +221,7 @@ export class TreeNodeKernel implements Listenable { this.#hydrationState.offAnchorNode.add( // Argument is forwarded between matching events, so the type should be correct. // eslint-disable-next-line @typescript-eslint/no-explicit-any - anchorNode.on(eventName, (arg: any) => events.emit(eventName, arg)), + anchorNode.events.on(eventName, (arg: any) => events.emit(eventName, arg)), ); } } @@ -248,13 +248,11 @@ export class TreeNodeKernel implements Listenable { return treeStatusFromAnchorCache(this.#hydrationState.anchorNode); } - public on(eventName: K, listener: KernelEvents[K]): Off { + public get events(): Listenable { // Retrieve the correct events object based on whether this node is pre or post hydration. - const events: Listenable = isHydrated(this.#hydrationState) - ? this.#hydrationState.anchorNode + return isHydrated(this.#hydrationState) + ? this.#hydrationState.anchorNode.events : this.#unhydratedEvents.value; - - return events.on(eventName, listener); } public dispose(): void { @@ -339,7 +337,7 @@ export class TreeNodeKernel implements Listenable { off(); }; anchorForgetters.set(this.node, forget); - const off = anchorNode.on("afterDestroy", forget); + const off = anchorNode.events.on("afterDestroy", forget); return anchorNode; } diff --git a/packages/dds/tree/src/simple-tree/proxies.ts b/packages/dds/tree/src/simple-tree/proxies.ts index ef3bdd751c4e..67fc9aa546d1 100644 --- a/packages/dds/tree/src/simple-tree/proxies.ts +++ b/packages/dds/tree/src/simple-tree/proxies.ts @@ -172,7 +172,7 @@ function bindProxies(proxies: RootedProxyPaths[], forest: IForestSubscription): if (proxies.length > 0) { // Creating a new array emits one event per element in the array, so listen to the event once for each element let i = 0; - const off = forest.on("afterRootFieldCreated", (fieldKey) => { + const off = forest.events.on("afterRootFieldCreated", (fieldKey) => { // Non null asserting here because of the length check above // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (proxies[i]!.rootPath as Mutable).parentField = fieldKey; diff --git a/packages/dds/tree/src/test/events/events.spec.ts b/packages/dds/tree/src/test/events/events.spec.ts index 13bef28befad..2addf458fcc9 100644 --- a/packages/dds/tree/src/test/events/events.spec.ts +++ b/packages/dds/tree/src/test/events/events.spec.ts @@ -217,3 +217,14 @@ class MyCompositionClass implements Listenable { return this.events.on(eventName, listener); } } + +class MyExposingClass { + private readonly _events = createEmitter(); + + public readonly events: Listenable = this._events; + + private load() { + this._events.emit("loaded"); + const results: number[] = this._events.emitAndCollect("computed"); + } +} diff --git a/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts b/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts index 46b7f5a53d5a..206f24bd091b 100644 --- a/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts +++ b/packages/dds/tree/src/test/feature-libraries/modular-schema/schemaEvolutionExamples.spec.ts @@ -49,7 +49,7 @@ class TestSchemaRepository extends TreeStoredSchemaRepository { public tryUpdateRootFieldSchema(schema: TreeFieldStoredSchema): boolean { if (allowsFieldSuperset(this.policy, this, this.rootFieldSchema, schema)) { this.rootFieldSchemaData = schema; - this.events.emit("afterSchemaChange", this); + this._events.emit("afterSchemaChange", this); return true; } return false; @@ -64,7 +64,7 @@ class TestSchemaRepository extends TreeStoredSchemaRepository { const original = this.nodeSchema.get(name); if (allowsTreeSuperset(this.policy, this, original, storedSchema)) { this.nodeSchemaData.set(name, storedSchema); - this.events.emit("afterSchemaChange", this); + this._events.emit("afterSchemaChange", this); return true; } return false; diff --git a/packages/dds/tree/src/test/forestTestSuite.ts b/packages/dds/tree/src/test/forestTestSuite.ts index ff262bee5926..9981961886a1 100644 --- a/packages/dds/tree/src/test/forestTestSuite.ts +++ b/packages/dds/tree/src/test/forestTestSuite.ts @@ -606,7 +606,7 @@ export function testForest(config: ForestTestConfiguration): void { ); const log: string[] = []; - forest.on("beforeChange", () => { + forest.events.on("beforeChange", () => { const cursor = forest.allocateCursor(); moveToDetachedField(forest, cursor); log.push("beforeChange"); @@ -629,7 +629,7 @@ export function testForest(config: ForestTestConfiguration): void { ); const log: string[] = []; - forest.on("beforeChange", () => { + forest.events.on("beforeChange", () => { log.push("beforeChange"); }); diff --git a/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts b/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts index 68107d969a58..12a8aa3cc960 100644 --- a/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/branch.spec.ts @@ -337,7 +337,7 @@ describe("Branches", () => { it("emit a fork event after forking", () => { let fork: DefaultBranch | undefined; const branch = create(); - branch.on("fork", (f) => (fork = f)); + branch.events.on("fork", (f) => (fork = f)); // The fork event should return the new branch, just as the fork method does assert.equal(branch.fork(), fork); assert.equal(branch.fork(), fork); @@ -346,7 +346,7 @@ describe("Branches", () => { it("emit a dispose event after disposing", () => { const branch = create(); let disposed = false; - branch.on("dispose", () => (disposed = true)); + branch.events.on("dispose", () => (disposed = true)); branch.dispose(); assert.equal(disposed, true); }); @@ -358,7 +358,7 @@ describe("Branches", () => { it(`emit a transactionStarted event after a new transaction scope is opened ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionStarted", (isOuterTransaction) => { + branch.events.on("transactionStarted", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -385,7 +385,7 @@ describe("Branches", () => { it(`emit a transactionAborted event after a transaction scope is aborted ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionAborted", (isOuterTransaction) => { + branch.events.on("transactionAborted", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -414,7 +414,7 @@ describe("Branches", () => { it(`emit a transactionCommitted event after a new transaction scope is committed ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionCommitted", (isOuterTransaction) => { + branch.events.on("transactionCommitted", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -443,7 +443,7 @@ describe("Branches", () => { it(`emit a transactionRolledBack event after a transaction scope is rolled back ${withCommitsTitle}`, () => { const branch = create(); const log: boolean[] = []; - branch.on("transactionRolledBack", (isOuterTransaction) => { + branch.events.on("transactionRolledBack", (isOuterTransaction) => { log.push(isOuterTransaction); }); branch.startTransaction(); @@ -686,11 +686,9 @@ describe("Branches", () => { it("registers listener on forks created inside of the listener", () => { const branch = create(); let forkCount = 0; - onForkTransitive(branch, () => { - forkCount += 1; - assert(branch.hasListeners("fork")); - if (forkCount <= 1) { - branch.fork(); + onForkTransitive(branch, (f) => { + if (forkCount++ === 0) { + f.fork(); } }); branch.fork(); @@ -709,12 +707,12 @@ describe("Branches", () => { const branch = new SharedTreeBranch(initCommit, defaultChangeFamily, mintRevisionTag); let head = branch.getHead(); - branch.on("beforeChange", (c) => { + branch.events.on("beforeChange", (c) => { // Check that the branch head never changes in the "before" event; it should only change after the "after" event. assert.equal(branch.getHead(), head); onChange?.(c); }); - branch.on("afterChange", (c) => { + branch.events.on("afterChange", (c) => { head = branch.getHead(); onChange?.(c); }); diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts index abf6eed82d73..b0a33e88586c 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManager.bench.ts @@ -272,7 +272,7 @@ describe("EditManager - Bench", () => { const family = testChangeFamilyFactory(new NoOpChangeRebaser()); const manager = editManagerFactory(family); // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + manager.localBranch.events.on("afterChange", ({ change }) => {}); const sequencedEdits: Commit[] = []; for (let iChange = 0; iChange < count; iChange++) { const revision = mintRevisionTag(); diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts index 711f3065f83e..3c68e6136774 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerCorrectness.test.ts @@ -749,7 +749,7 @@ function trackTrimmed( branch: SharedTreeBranch, ): ReadonlySet { const trimmedCommits = new Set(); - branch.on("ancestryTrimmed", (trimmedRevisions) => { + branch.events.on("ancestryTrimmed", (trimmedRevisions) => { trimmedRevisions.forEach((revision) => trimmedCommits.add(revision)); }); return trimmedCommits; diff --git a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts index 9d9419e5bcde..df6e01dcfef6 100644 --- a/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts +++ b/packages/dds/tree/src/test/shared-tree-core/edit-manager/editManagerTestUtils.ts @@ -120,8 +120,7 @@ export function rebaseLocalEditsOverTrunkEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); for (let iChange = 0; iChange < localEditCount; iChange++) { const revision = mintRevisionTag(); manager.localBranch.apply({ change: mintChange(undefined), revision }); @@ -205,8 +204,7 @@ export function rebasePeerEditsOverTrunkEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); for (let iChange = 0; iChange < trunkEditCount; iChange++) { const revision = mintRevisionTag(); manager.addSequencedChange( @@ -303,8 +301,7 @@ export function rebaseAdvancingPeerEditsOverTrunkEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); for (let iChange = 0; iChange < editCount; iChange++) { const revision = mintRevisionTag(); manager.addSequencedChange( @@ -399,8 +396,7 @@ export function rebaseConcurrentPeerEdits( mintChange: (revision: RevisionTag | undefined) => TChange, defer: boolean = false, ): void | (() => void) { - // Subscribe to the local branch to emulate the behavior of SharedTree - manager.localBranch.on("afterChange", ({ change }) => {}); + subscribeToLocalBranch(manager); const peerEdits: Commit[] = []; for (let iChange = 0; iChange < editsPerPeerCount; iChange++) { for (let iPeer = 0; iPeer < peerCount; iPeer++) { @@ -434,7 +430,7 @@ export function addSequencedChange( ...args: Parameters<(typeof editManager)["addSequencedChange"]> ): DeltaRoot { let delta: DeltaRoot = emptyDelta; - const offChange = editManager.localBranch.on("afterChange", ({ change }) => { + const offChange = editManager.localBranch.events.on("afterChange", ({ change }) => { if (change !== undefined) { delta = asDelta(change.change.intentions); } @@ -443,3 +439,13 @@ export function addSequencedChange( offChange(); return delta; } + +/** Subscribe to the local branch to emulate the behavior of SharedTree */ +function subscribeToLocalBranch( + manager: EditManager>, +): void { + manager.localBranch.events.on("afterChange", (branchChange) => { + // Reading the change property causes lazy computation to occur, and is important to accurately emulate SharedTree behavior + const _change = branchChange.change; + }); +} diff --git a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts index 229a443b0463..37e2b64c188d 100644 --- a/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts +++ b/packages/dds/tree/src/test/shared-tree-core/sharedTreeCore.spec.ts @@ -634,7 +634,7 @@ describe("SharedTreeCore", () => { assert.equal(tree.preparedCommitsCount, 0); // Temporarily make commit application fail - const disableFailure = tree.getLocalBranch().on("beforeChange", () => { + const disableFailure = tree.getLocalBranch().events.on("beforeChange", () => { throw new Error("Invalid commit"); }); assert.throws(() => changeTree(tree)); diff --git a/packages/dds/tree/src/test/shared-tree/editing.spec.ts b/packages/dds/tree/src/test/shared-tree/editing.spec.ts index 2816019bda9d..df96020d3b4d 100644 --- a/packages/dds/tree/src/test/shared-tree/editing.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/editing.spec.ts @@ -1770,7 +1770,7 @@ describe("Editing", () => { beforeDetach(source: RangeUpPath, destination: DetachedPlaceUpPath): void {}, afterDetach(source: PlaceUpPath, destination: DetachedRangeUpPath): void {}, }; - const unsubscribePathVisitor = node.on( + const unsubscribePathVisitor = node.events.on( "subtreeChanging", (n: AnchorNode) => pathVisitor, ); @@ -2614,7 +2614,7 @@ describe("Editing", () => { beforeDetach(source: RangeUpPath, destination: DetachedPlaceUpPath): void {}, afterDetach(source: PlaceUpPath, destination: DetachedRangeUpPath): void {}, }; - const unsubscribePathVisitor = node.on( + const unsubscribePathVisitor = node.events.on( "subtreeChanging", (n: AnchorNode) => pathVisitor, ); diff --git a/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts b/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts index 329ef2d53b68..0c6b013a236c 100644 --- a/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/schematizeTree.spec.ts @@ -102,7 +102,7 @@ describe("schematizeTree", () => { let previousSchema: TreeStoredSchema = new TreeStoredSchemaRepository(storedSchema); expectSchema(storedSchema, previousSchema); - storedSchema.on("afterSchemaChange", () => { + storedSchema.events.on("afterSchemaChange", () => { previousSchema = new TreeStoredSchemaRepository(storedSchema); }); @@ -122,7 +122,7 @@ describe("schematizeTree", () => { const storedSchema = new TreeStoredSchemaRepository(); const log: string[] = []; - storedSchema.on("afterSchemaChange", () => { + storedSchema.events.on("afterSchemaChange", () => { log.push("schema"); }); initializeContent(makeSchemaRepository(storedSchema), content.schema, () => diff --git a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts index 2b94bdcfc6da..70cdd3fa48c8 100644 --- a/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts +++ b/packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts @@ -75,8 +75,8 @@ describe("sharedTreeView", () => { const root = view.root; const anchorNode = getOrCreateInnerNode(root).anchorNode; const log: string[] = []; - const unsubscribe = anchorNode.on("childrenChanging", () => log.push("change")); - const unsubscribeSubtree = anchorNode.on("subtreeChanging", () => { + const unsubscribe = anchorNode.events.on("childrenChanging", () => log.push("change")); + const unsubscribeSubtree = anchorNode.events.on("subtreeChanging", () => { log.push("subtree"); }); const unsubscribeAfter = view.checkout.events.on("afterBatch", () => log.push("after")); @@ -115,10 +115,10 @@ describe("sharedTreeView", () => { const root = view.root; const anchorNode = getOrCreateInnerNode(root).anchorNode; const log: string[] = []; - const unsubscribe = anchorNode.on("childrenChanging", (upPath) => + const unsubscribe = anchorNode.events.on("childrenChanging", (upPath) => log.push(`change-${String(upPath.parentField)}-${upPath.parentIndex}`), ); - const unsubscribeSubtree = anchorNode.on("subtreeChanging", (upPath) => { + const unsubscribeSubtree = anchorNode.events.on("subtreeChanging", (upPath) => { log.push(`subtree-${String(upPath.parentField)}-${upPath.parentIndex}`); }); const unsubscribeAfter = view.checkout.events.on("afterBatch", () => log.push("after")); diff --git a/packages/dds/tree/src/test/tree/anchorSet.spec.ts b/packages/dds/tree/src/test/tree/anchorSet.spec.ts index acc2ef2c87d6..e94c2504a935 100644 --- a/packages/dds/tree/src/test/tree/anchorSet.spec.ts +++ b/packages/dds/tree/src/test/tree/anchorSet.spec.ts @@ -416,8 +416,8 @@ describe("AnchorSet", () => { // AnchorSet does not guarantee event ordering within a batch so use UnorderedTestLogger. const log = new UnorderedTestLogger(); const anchors = new AnchorSet(); - anchors.on("childrenChanging", log.logger("root childrenChange")); - anchors.on("treeChanging", log.logger("root treeChange")); + anchors.events.on("childrenChanging", log.logger("root childrenChange")); + anchors.events.on("treeChanging", log.logger("root treeChange")); const detachMark: DeltaMark = { count: 1, @@ -436,10 +436,10 @@ describe("AnchorSet", () => { const anchor0 = anchors.track(makePath([rootFieldKey, 0])); const node0 = anchors.locate(anchor0) ?? assert.fail(); - node0.on("childrenChanging", log.logger("childrenChanging")); - node0.on("childrenChanged", log.logger("childrenChanged")); - node0.on("subtreeChanging", log.logger("subtreeChange")); - node0.on("afterDestroy", log.logger("afterDestroy")); + node0.events.on("childrenChanging", log.logger("childrenChanging")); + node0.events.on("childrenChanged", log.logger("childrenChanged")); + node0.events.on("subtreeChanging", log.logger("subtreeChange")); + node0.events.on("afterDestroy", log.logger("afterDestroy")); log.expect([]); @@ -506,7 +506,7 @@ describe("AnchorSet", () => { const expectedChangedFields = new Set([fieldOne, fieldTwo, fieldThree]); let listenerFired = false; - node0.on("childrenChangedAfterBatch", ({ changedFields }) => { + node0.events.on("childrenChangedAfterBatch", ({ changedFields }) => { // This is the main validation of this test assert.deepEqual(changedFields, expectedChangedFields); listenerFired = true; @@ -653,7 +653,10 @@ describe("AnchorSet", () => { )(); }, }; - const unsubscribePathVisitor = node0.on("subtreeChanging", (n: AnchorNode) => pathVisitor); + const unsubscribePathVisitor = node0.events.on( + "subtreeChanging", + (n: AnchorNode) => pathVisitor, + ); announceTestDelta(insertAtFoo4, anchors, undefined, undefined, build); log.expect([ ["visitSubtreeChange.beforeAttach-src:Temp-0[0, 1]-dst:foo[4]", 1],