import { WriteEvent, WriteLog } from "./types.ts"; class WriteEmitter { private writeLogs = new Set(); private ignoreCounter = 0; private listeners = new Set<(e: WriteLog) => void>(); action any>(fn: T): T { const runInAction = this.runInAction.bind(this); return function (this: object, ...args: any[]) { return runInAction(() => fn.apply(this, args))[0]; } as unknown as T; } runInAction(f: () => T): [T, WriteLog] { const writeLog = new Map(); this.writeLogs.add(writeLog); try { return [f(), writeLog]; } finally { this.writeLogs.delete(writeLog); if (this.writeLogs.size === 0 && writeLog.size > 0) { this.listeners.forEach((listener) => listener(writeLog)); } } } ignore(f: () => T): T { this.ignoreCounter++; try { return f(); } finally { this.ignoreCounter--; } } on(listener: (e: WriteLog) => void) { this.listeners.add(listener); } off(listener: (e: WriteLog) => void) { this.listeners.delete(listener); } emit(me: WriteEvent) { if (this.ignoreCounter > 0) { return; } if (this.writeLogs.size === 0) { this.listeners.forEach((listener) => listener(new Map([[me.target, new Map([[me.prop, me.value]])]])) ); } else { for (const writeLog of this.writeLogs.values()) { if (!writeLog.has(me.target)) { writeLog.set(me.target, new Map()); } const writeMap = writeLog.get(me.target)!; writeMap.set(me.prop, me.value + (writeMap.get(me.prop) ?? 0)); } } } } const writeEmitter = new WriteEmitter(); export const action = writeEmitter.action.bind(writeEmitter); export const runInAction = writeEmitter.runInAction.bind(writeEmitter); export const ignore = writeEmitter.ignore.bind(writeEmitter); export default writeEmitter;