disposeNode method
Tear down node, dispatching on its own kind.
Idempotent: disposing an already-disposed node is a no-op, so teardown paths compose. Effects additionally await their pending cleanup, which is why this returns a Future; for a slot or a cell the returned future is already complete.
Without this a node is permanent. The graph holds a strong reverse edge to every dependent, so dropping the last user-held reference reclaims nothing and a long-lived source retains every node that ever read it.
Implementation
Future<void> disposeNode(AsyncGraphNode node) async {
switch (node) {
case _AsyncEffectHandle():
await node.disposeAsync();
case AsyncEffectHandle():
await node.disposeAsync();
case AsyncSlotHandle():
if (node._nodeDisposed) return;
node._nodeDisposed = true;
_detachAndDirty(node, node._dependencies);
node._failInFlight(const _Superseded());
case AsyncCellHandle():
if (node._nodeDisposed) return;
node._nodeDisposed = true;
// A cell is a pure source: no upstream set to detach.
_detachAndDirty(node, <Object>{});
}
}