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

110 lines (91 loc) 3.64 kB
import { isObservableObject } from "mobx" import { BuiltInAction } from "../action/builtInActions" import { ActionContextActionType } from "../action/context" import { wrapInAction } from "../action/wrapInAction" import { isFrozenSnapshot } from "../frozen/Frozen" import type { AnyModel } from "../model/BaseModel" import { getModelIdPropertyName } from "../model/getModelMetadata" import { modelIdKey, modelTypeKey } from "../model/metadata" import { isModel, isModelSnapshot } from "../model/utils" import type { ModelClass } from "../modelShared/BaseModelShared" import { getModelInfoForName } from "../modelShared/modelInfo" import { assertTweakedObject } from "../tweaker/core" import { assertIsObject, failure, inDevMode, isArray, isPlainObject, lazy } from "../utils" import { ModelPool } from "../utils/ModelPool" import { reconcileSnapshot } from "./reconcileSnapshot" import type { SnapshotOutOf } from "./SnapshotOf" /** * Applies a full snapshot over an object, reconciling it with the current contents of the object. * * @typeparam T Object type. * @param node Target object (model object, object or array). * @param snapshot Snapshot to apply. */ export function applySnapshot<T extends object>(node: T, snapshot: SnapshotOutOf<T>): void { assertTweakedObject(node, "node") assertIsObject(snapshot, "snapshot") wrappedInternalApplySnapshot().call(node, snapshot) } function internalApplySnapshot<T extends object>(this: T, sn: SnapshotOutOf<T>): void { const obj = this const reconcile = () => { const modelPool = new ModelPool(obj) const ret = reconcileSnapshot(obj, sn, modelPool) if (inDevMode()) { if (ret !== obj) { throw failure("assertion failed: reconciled object has to be the same") } } } if (isArray(sn)) { if (!isArray(obj)) { throw failure("if the snapshot is an array the target must be an array too") } return reconcile() } if (isFrozenSnapshot(sn)) { throw failure("applySnapshot cannot be used over frozen objects") } if (isModelSnapshot(sn)) { const type = sn[modelTypeKey] const modelInfo = getModelInfoForName(type) if (!modelInfo) { throw failure(`model with name "${type}" not found in the registry`) } // we don't check by actual instance since the class might be a different one due to hot reloading if (!isModel(obj)) { // not a model instance, no reconciliation possible throw failure(`the target for a model snapshot must be a model instance`) } if (obj[modelTypeKey] !== type) { // different kind of model, no reconciliation possible throw failure( `snapshot model type '${type}' does not match target model type '${ (obj as any)[modelTypeKey] }'` ) } const modelIdPropertyName = getModelIdPropertyName(modelInfo.class as ModelClass<AnyModel>) const id = sn[modelIdPropertyName] if (obj[modelIdKey] !== id) { // different id, no reconciliation possible throw failure(`snapshot model id '${id}' does not match target model id '${obj[modelIdKey]}'`) } return reconcile() } if (isPlainObject(sn)) { if (!isPlainObject(obj) && !isObservableObject(obj)) { // no reconciliation possible throw failure("if the snapshot is an object the target must be an object too") } return reconcile() } throw failure(`unsupported snapshot - ${sn}`) } const wrappedInternalApplySnapshot = lazy(() => wrapInAction({ nameOrNameFn: BuiltInAction.ApplySnapshot, fn: internalApplySnapshot, actionType: ActionContextActionType.Sync, }) )