import { set as setBase } from './ObservableObject'; import { batch, notify } from './batching'; import { getNode, getNodeValue } from './globals'; import { isObservable, lockObservable } from './helpers'; import { isPromise } from './is'; import { observable } from './observable'; import { ObservableComputed, ObservableComputedTwoWay, ObservableReadable } from './observableInterfaces'; import { observe } from './observe'; export function computed(compute: () => T | Promise): T; export function computed(compute: () => T | Promise): ObservableComputed; export function computed( compute: (() => T | Promise) | ObservableReadable, set: (value: T2) => void, ): ObservableComputedTwoWay; export function computed( compute: (() => T | Promise) | ObservableReadable, set?: (value: T2) => void, ): ObservableComputed | ObservableComputedTwoWay { // Create an observable for this computed variable const obs = observable(); lockObservable(obs, true); const node = getNode(obs); node.isComputed = true; const setInner = function (val: any) { const prevNode = node.linkedToNode; // If it was previously linked to a node remove self // from its linkedFromNodes if (prevNode) { prevNode.linkedFromNodes!.delete(node); node.linkedToNode = undefined; } if (isObservable(val)) { // If the computed is a proxy to another observable // link it to the target observable const linkedNode = getNode(val); node.linkedToNode = linkedNode; if (!linkedNode.linkedFromNodes) { linkedNode.linkedFromNodes = new Set(); } linkedNode.linkedFromNodes.add(node); // If the target observable is different then notify for the change if (prevNode) { const value = getNodeValue(linkedNode); const prevValue = getNodeValue(prevNode); notify(node, value, prevValue, 0); } } else if (val !== obs.peek()) { // Update the computed value lockObservable(obs, false); setBase(node, val); lockObservable(obs, true); } }; // Lazily activate the observable when get is called node.root.activate = () => { observe( compute, ({ value }) => { if (isPromise(value)) { value.then((v) => setInner(v)); } else { setInner(value); } }, { immediate: true, retainObservable: true }, ); }; if (set) { node.root.set = (value: any) => { batch(() => set(value)); }; } return obs as ObservableComputed; }