const contextStack: Observer[] = []; export class Observer { private effect: () => T; dependencies: Set = new Set(); constructor(effect: { (): T }) { this.effect = effect; this.execute(); } execute() { this.cleanup(); contextStack.push(this); try { this.effect(); } finally { contextStack.pop(); } } cleanup() { for (const dep of this.dependencies.values()) { dep.observers.delete(this); } this.dependencies.clear(); } } export class Observable { private value: T; observers: Set = new Set(); constructor(init: T) { this.value = init; } get() { const activeObserver = contextStack[contextStack.length - 1]; if (activeObserver) { this.observers.add(activeObserver); activeObserver.dependencies.add(this); } return this.value; } set(next: T) { this.value = next; for (const observer of Array.from(this.observers.values())) { observer.execute(); } } } export class Mapping { private observable: Observable; private observer: Observer; constructor(mapping: { (): T }) { this.observable = new Observable(undefined as T); this.observer = new Observer(() => { const result = mapping(); this.observable.set(result); return result; }); } get() { return this.observable.get(); } }