UNPKG

mobx-keystone

Version:

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

95 lines (85 loc) 2.72 kB
import { action } from "mobx" import { fastGetRoot } from "../parent/path" import { getModelRefId, internalCustomRef, RefIdResolver, RefOnResolvedValueChange, RefResolver, resolveId, } from "./core" import type { Ref, RefConstructor } from "./Ref" /** * Custom reference options. */ export interface RootRefOptions<T extends object> { /** * Must return the ID associated to the given target object, or `undefined` if it has no ID. * If not provided it will try to get the reference id from the model `getRefId()` method. * * @param target Target object. */ getId?: RefIdResolver /** * What should happen when the resolved value changes. * * @param ref Reference object. * @param newValue New resolved value. * @param oldValue Old resolved value. */ onResolvedValueChange?: RefOnResolvedValueChange<T> } /** * Creates a root ref to an object, which in its snapshot form has an id. * A root ref will only be able to resolve references as long as both the Ref * and the referenced object share a common root. * * @template T Target object type. * @param modelTypeId Unique model type id. * @param [options] Root reference options. * @returns A function that allows you to construct that type of root reference. */ export const rootRef: <T extends object>( modelTypeId: string, options?: RootRefOptions<T> ) => RefConstructor<T> = action( "rootRef", <T extends object>(modelTypeId: string, options?: RootRefOptions<T>): RefConstructor<T> => { const getId = options?.getId ?? getModelRefId const onResolvedValueChange = options?.onResolvedValueChange const resolverGen = (ref: Ref<T>): RefResolver<T> => { let cachedTarget: T | undefined return () => { const refRoot = fastGetRoot(ref, true) if (isRefRootCachedTargetOk(ref, refRoot, cachedTarget, getId)) { return cachedTarget } // when not found, everytime a child is added/removed or its id changes we will perform another search // this search is only done once for every distinct getId function const newTarget = resolveId<T>(refRoot, ref.id, getId) if (newTarget) { cachedTarget = newTarget } return newTarget } } return internalCustomRef(modelTypeId, resolverGen, getId, onResolvedValueChange) } ) function isRefRootCachedTargetOk<T extends object>( ref: Ref<T>, refRoot: object, cachedTarget: T | undefined, getId: RefIdResolver ): cachedTarget is T { if (!cachedTarget) { return false } if (ref.id !== getId(cachedTarget)) { return false } if (refRoot !== fastGetRoot(cachedTarget, true)) { return false } return true }