Patches
Overview
As described in Snapshots, changing a tree node produces a new snapshot. Patches provide a more granular change stream: each patch describes one operation at a path.
Every change generates two kinds of patches: patches from the previous value to the new value (usually just called "patches") and patches from the new value back to the previous value ("inverse patches"). A patch object has this structure:
export interface Patch {
readonly op: "replace" | "remove" | "add"
readonly path: Path
readonly value?: any // value is not available for remove operations
}
Unlike JSON Patch, a mobx-keystone patch stores its path as an array of string or number segments rather than as a JSON Pointer string. This avoids parsing the path during normal use.
Getting patches
onPatches
onPatches(target: object, listener: OnPatchesListener): OnPatchesDisposer
onPatches lets you observe the patches generated for a tree node and all its children:
const disposer = onPatches(todo, (patches, inversePatches) => {
console.log("patches", patches)
console.log("inverse patches", inversePatches)
})
Note that the listener callback is called immediately after an observable value has changed and before the outermost action has completed. This behavior differs from, e.g., onSnapshot or a MobX reaction.
onGlobalPatches
onGlobalPatches(listener: OnGlobalPatchesListener): OnPatchesDisposer
onGlobalPatches listens to patch activity anywhere, rather than under one specific subtree:
const disposer = onGlobalPatches((target, patches, inversePatches) => {
console.log("target", target)
console.log("patches", patches)
console.log("inverse patches", inversePatches)
})
This is mostly useful for global tooling, logging, or diagnostics. In application code, prefer onPatches when you already know which subtree you want to observe.
patchRecorder
patchRecorder(target: object, opts?: PatchRecorderOptions): PatchRecorder
patchRecorder is an abstraction over onPatches that can be used like this:
const recorder = patchRecorder(todo, options)
Where the allowed options are:
/**
* Patch recorder options.
*/
export interface PatchRecorderOptions {
/**
* If the patch recorder is initially recording when created.
*/
recording?: boolean
/**
* An optional callback filter to select which patches to record/skip.
* It will be executed before the event is added to the events list.
*
* @param patches Patches about to be recorded.
* @param inversePatches Inverse patches about to be recorded.
* @returns `true` to record the patch, `false` to skip it.
*/
filter?(patches: Patch[], inversePatches: Patch[]): boolean
/**
* An optional callback run once a patch is recorded.
* It will be executed after the event is added to the events list.
*
* @param patches Patches just recorded.
* @param inversePatches Inverse patches just recorded.
*/
onPatches?: OnPatchesListener
}
It returns a recorder with the following properties:
interface PatchRecorder {
/**
* Gets/sets if the patch recorder is currently recording.
*/
recording: boolean
/**
* Observable array of patching events.
*/
readonly events: PatchRecorderEvent[]
/**
* Dispose of the patch recorder.
*/
dispose(): void
}
The PatchRecorderEvent definition is:
interface PatchRecorderEvent {
/**
* Target object.
*/
readonly target: object
/**
* Recorded patches.
*/
readonly patches: Patch[]
/**
* Recorded inverse patches.
*/
readonly inversePatches: Patch[]
}
Applying patches
applyPatches
applyPatches(obj: object, patches: Patch[] | Patch[][], reverse?: boolean): void
Apply recorded patches like this:
applyPatches(todo, patches)
as well as in reverse order (usually used for inverse patches):
applyPatches(todo, patches, true)
Conversion to JSON patches / paths
The only difference with the JSON Patch specification is that paths generated by this library are arrays instead of strings. For compatibility reasons the following conversion functions are provided:
pathToJsonPointer
pathToJsonPointer(path: Path): string
Converts a path into a JSON pointer.
jsonPointerToPath
jsonPointerToPath(jsonPointer: string): Path
Converts a JSON pointer into a path.
patchToJsonPatch
patchToJsonPatch(patch: Patch): JsonPatch
Converts a patch into a JSON patch.
jsonPatchToPatch
jsonPatchToPatch(jsonPatch: JsonPatch): Patch
Converts a JSON patch into a patch.