import { observable } from 'mobx'; import { deepSyncInternal, DeepSyncOptions, normalizeFilter } from './deep/deep_sync_adv'; const syncedObservableSymbol = Symbol(); interface SyncedObservableOptions extends DeepSyncOptions { delete_missing?: boolean, } class SyncedObservable { constructor( protected source, protected readonly dest, protected readonly options: SyncedObservableOptions ) { } get value() { return this.dest } /** * Sync the observable with the passed `from` object, or from the original object * @param from */ sync(from?: T) { sync_to_observable(this.dest, from || this.source, this.options); } /** * Refreshes the observable from the original object */ refresh() { return this.sync(); } } class SyncedObservableProxy { private [syncedObservableSymbol]: SyncedObservable; /** * Sync the observable with the passed `from` object, or from the original object * @param from */ sync(from?: T) { this[syncedObservableSymbol].sync(from) } /** * Refreshes the observable from the original object */ refresh() { this[syncedObservableSymbol].refresh() } } /** * Convert plain or immutable objects into observables. * * The returned observable is a duplicate of the given object, * but has a `sync(from?)` method on it that, when called, will * sync itself to the values from the passed `from` or from the original object * if `from` is not specified. * * @param source An object to duplicate into an Observable * @param options Additional options */ export function synced_observable(source: T, options?: SyncedObservableOptions): T & SyncedObservableProxy { const target: T & SyncedObservableProxy = observable.object({}, {}, { deep: false }) as any; const so = new SyncedObservable(source, target, options); Object.setPrototypeOf(target, SyncedObservableProxy.prototype); Object.defineProperty(target, syncedObservableSymbol, { enumerable: false, get() { return so }, }) so.sync(); return target; } export function safe_synced_observable(from: T, options?: SyncedObservableOptions) { const target: T = observable.object({}, {}, { deep: false }) as any; const so = new SyncedObservable(from, target, options); so.refresh(); return so; } export function sync_to_observable(target: any, obj: T, options: SyncedObservableOptions & { currentPath?: string[] } = {}): T { let filterObj: any = obj; if (options.currentPath) { for (let p of [...options.currentPath].reverse()) { if (p == '$') continue; filterObj = { [p]: filterObj } } } deepSyncInternal(target, obj, { refPaths: normalizeFilter(options.refs, filterObj), excludePaths: normalizeFilter(options.exclude, filterObj), currentPath: options.currentPath || ['$'], delete_missing: options.delete_missing, initializeMissingValue: (v, recurse) => { const obsv = observable(Array.isArray(v) ? [] : {}, {}, { deep: false }); recurse(obsv); return obsv; }, }) return target }