UNPKG

mobx-keystone-mindreframer

Version:

A MobX powered state management solution based on data trees with first class support for Typescript, snapshots, patches and much more

147 lines (126 loc) 3.84 kB
import { action } from "mobx" import { isDataModel } from "../dataModel/utils" import { getObjectChildren } from "../parent/coreObjectChildren" import { fastGetParent, ParentPath } from "../parent/path" import { setParent } from "../parent/setParent" import { unsetInternalSnapshot } from "../snapshot/internal" import { failure, inDevMode, isMap, isObject, isPrimitive, isSet } from "../utils" import { isTweakedObject, tweakedObjects } from "./core" /** * Turns an object (array, plain object) into a tree node, * which then can accept calls to `getParent`, `getSnapshot`, etc. * If a tree node is passed it will return the passed argument directly. * * @param value Object to turn into a tree node. * @returns The object as a tree node. */ export function toTreeNode<T extends object>(value: T): T { if (!isObject(value)) { throw failure("only objects can be turned into tree nodes") } if (!isTweakedObject(value, true)) { return tweak(value, undefined) } return value } /** * @ignore * @internal */ export type Tweaker<T> = (value: T, parentPath: ParentPath<any> | undefined) => T | undefined const tweakers: { priority: number; tweaker: Tweaker<any> }[] = [] /** * @ignore * @internal */ export function registerTweaker<T>(priority: number, tweaker: Tweaker<T>): void { tweakers.push({ priority, tweaker }) tweakers.sort((a, b) => a.priority - b.priority) } function internalTweak<T>(value: T, parentPath: ParentPath<any> | undefined): T { if (isPrimitive(value)) { return value } // already tweaked if (isTweakedObject(value as any, true)) { value = setParent({ value, parentPath, indexChangeAllowed: false, isDataObject: false, cloneIfApplicable: true, }) return value } // unsupported (must go before plain object tweaker) if (isDataModel(value)) { throw failure( "data models are not directly supported. you may insert the data in the tree instead ('$' property)." ) } const tweakersLen = tweakers.length for (let i = 0; i < tweakersLen; i++) { const { tweaker } = tweakers[i] const tweakedVal = tweaker(value, parentPath) if (tweakedVal !== undefined) { return tweakedVal } } // unsupported if (isMap(value)) { throw failure("maps are not directly supported. consider using 'ObjectMap' / 'asMap' instead.") } // unsupported if (isSet(value)) { throw failure("sets are not directly supported. consider using 'ArraySet' / 'asSet' instead.") } throw failure( `tweak can only work over models, observable objects/arrays, or primitives, but got ${value} instead` ) } /** * @ignore * @internal */ export const tweak = action("tweak", internalTweak) /** * @ignore * @internal */ export function tryUntweak(value: any) { if (isPrimitive(value)) { return true } if (inDevMode()) { if (fastGetParent(value)) { throw failure("assertion failed: object cannot be untweaked while it has a parent") } } const untweaker = tweakedObjects.get(value) if (!untweaker) { return false } // we have to make a copy since it will be changed // we have to iterate ourselves since it seems like babel does not do downlevel iteration const children = [] const childrenIter = getObjectChildren(value)!.values() let childrenCur = childrenIter.next() while (!childrenCur.done) { children.push(childrenCur.value) childrenCur = childrenIter.next() } for (let i = 0; i < children.length; i++) { setParent({ value: children[i], parentPath: undefined, indexChangeAllowed: false, isDataObject: false, // no need to clone if unsetting the parent cloneIfApplicable: false, }) } untweaker() tweakedObjects.delete(value) unsetInternalSnapshot(value) return true }