import {runInAction} from 'mobx' import {deepObserve} from 'mobx-utils' import set from 'lodash.set' const ongoingActions = new Set() export const updateDataOptimistically = async < T extends Record, U, >( model: T, preUpdate: () => void, serverUpdate: () => Promise, postUpdate?: (res: U) => void, ): Promise => { if (ongoingActions.has(model)) { return } ongoingActions.add(model) const prevState: Map = new Map() const dispose = deepObserve(model, (change, path) => { if (change.observableKind === 'object') { if (change.type === 'update') { prevState.set( [path, change.name].filter(Boolean).join('.'), change.oldValue, ) } else if (change.type === 'add') { prevState.set([path, change.name].filter(Boolean).join('.'), undefined) } } }) preUpdate() dispose() try { const res = await serverUpdate() runInAction(() => { postUpdate?.(res) }) } catch (error) { runInAction(() => { prevState.forEach((value, path) => { set(model, path, value) }) }) throw error } finally { ongoingActions.delete(model) } }