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
121 lines (102 loc) • 2.93 kB
text/typescript
import { action, createAtom, IAtom, untracked } from "mobx"
import { fastGetParentPath, ParentPath } from "../parent/path"
import { debugFreeze } from "../utils"
import type { SnapshotOutOf } from "./SnapshotOf"
interface SnapshotData<T extends object> {
standard: SnapshotOutOf<T>
readonly atom: IAtom
}
const snapshots = new WeakMap<Object, SnapshotData<any>>()
/**
* @ignore
* @internal
*/
export function getInternalSnapshot<T extends object>(
value: T
): Readonly<SnapshotData<T>> | undefined {
return snapshots.get(value) as any
}
function getInternalSnapshotParent(
sn: SnapshotData<any>,
parentPath: ParentPath<any> | undefined
): { parentSnapshot: SnapshotData<any>; parentPath: ParentPath<any> } | undefined {
return untracked(() => {
if (!parentPath) {
return undefined
}
const parentSn = getInternalSnapshot(parentPath.parent)
if (!parentSn) {
return undefined
}
return sn
? {
parentSnapshot: parentSn,
parentPath: parentPath,
}
: undefined
})
}
/**
* @ignore
* @internal
*/
export const unsetInternalSnapshot = action("unsetInternalSnapshot", (value: any) => {
const oldSn = getInternalSnapshot(value) as SnapshotData<any>
if (oldSn) {
snapshots.delete(value)
oldSn.atom.reportChanged()
}
})
/**
* @ignore
* @internal
*/
export const setInternalSnapshot = action(
"setInternalSnapshot",
<T extends object>(value: any, standard: T): void => {
const oldSn = getInternalSnapshot(value) as SnapshotData<any>
// do not actually update if not needed
if (oldSn && oldSn.standard === standard) {
return
}
debugFreeze(standard)
let sn: SnapshotData<any>
if (oldSn) {
sn = oldSn
sn.standard = standard
} else {
sn = {
standard,
atom: createAtom("snapshot"),
}
snapshots.set(value, sn)
}
sn.atom.reportChanged()
// also update parent(s) snapshot(s) if needed
const parent = getInternalSnapshotParent(oldSn, fastGetParentPath(value))
if (parent) {
const { parentSnapshot, parentPath } = parent
// might be false in the cases where the parent has not yet been created
if (parentSnapshot) {
const path = parentPath.path
// patches for parent changes should not be emitted
let parentStandardSn = parentSnapshot.standard
if (parentStandardSn[path] !== sn.standard) {
if (Array.isArray(parentStandardSn)) {
parentStandardSn = parentStandardSn.slice()
} else {
parentStandardSn = Object.assign({}, parentStandardSn)
}
parentStandardSn[path] = sn.standard
setInternalSnapshot(parentPath.parent, parentStandardSn)
}
}
}
}
)
/**
* @ignore
*/
export function reportInternalSnapshotObserved(sn: Readonly<SnapshotData<any>>) {
sn.atom.reportObserved()
}