diff --git a/src/json-crdt/model/Model.ts b/src/json-crdt/model/Model.ts index 034f013f91..1ff6c4ab15 100644 --- a/src/json-crdt/model/Model.ts +++ b/src/json-crdt/model/Model.ts @@ -21,6 +21,7 @@ import {AvlMap} from '../../util/trees/avl/AvlMap'; import type {JsonNode, JsonNodeView} from '../nodes/types'; import type {Printable} from '../../util/print/types'; import type {NodeBuilder} from '../../json-crdt-patch'; +import type {NodeApi} from './api/nodes'; export const UNDEFINED = new ConNode(ORIGIN, undefined); @@ -291,6 +292,8 @@ export class Model implements Printable { if (isSystemNode) return; const node = this.index.get(value); if (!node) return; + const api = node.api; + if (api) (api as NodeApi).events.onDelete(); node.children((child) => this.deleteNodeTree(child.id)); this.index.del(value); } diff --git a/src/json-crdt/model/api/__tests__/NodeEvents.spec.ts b/src/json-crdt/model/api/__tests__/NodeEvents.spec.ts new file mode 100644 index 0000000000..2ce7fc3d19 --- /dev/null +++ b/src/json-crdt/model/api/__tests__/NodeEvents.spec.ts @@ -0,0 +1,25 @@ +import {Model} from '../..'; + +test('does not fire events after node is deleted', () => { + const model = Model.withLogicalClock(); + model.api.root({ + foo: { + bar: { + baz: 'asdf', + }, + }, + }); + const bar = model.api.obj(['foo', 'bar']); + let cnt = 0; + bar.events.changes.listen(() => { + cnt++; + }); + expect(cnt).toBe(0); + bar.set({ + gg: 'wp', + }); + expect(cnt).toBe(1); + model.api.obj(['foo']).del(['bar']); + model.api.obj(['foo']).set({gl: 'hf'}); + expect(cnt).toBe(1); +}); diff --git a/src/json-crdt/model/api/events/NodeEvents.ts b/src/json-crdt/model/api/events/NodeEvents.ts index 190161c4e1..c9b26e02e4 100644 --- a/src/json-crdt/model/api/events/NodeEvents.ts +++ b/src/json-crdt/model/api/events/NodeEvents.ts @@ -21,9 +21,12 @@ export interface NodeEventMap { } class ChangesFanOut extends FanOut> { + /** @ignore */ private _v: JsonNodeView | undefined = undefined; + /** @ignore */ private _u: FanOutUnsubscribe | undefined = undefined; + /** @ignore */ constructor(private readonly api: NodeApi) { super(); } @@ -45,10 +48,20 @@ class ChangesFanOut extends FanOut @@ -101,6 +114,16 @@ export class NodeEvents super.off(type, listener, options); } + /** + * Called when this node is deleted. + * + * @internal + * @ignore + */ + public onDelete() { + this.changes.dispose(); + } + // ---------------------------------------------------------------- SyncStore public readonly subscribe = (callback: () => void): SyncStoreUnsubscribe => {