Patches
Overview
As seen in the previous snapshots section, any change made to a tree node will generate a new snapshot, but this is only one of the two possible ways mobx-keystone offers to detect changes. The second way is "patches".
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
}
The difference with JSON patches is that the path is an array of strings / numbers rather than a simple string. This makes it faster to parse and use since there is no parsing / splitting involved.
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 will return an interface implementation that allows you to handle patch recording via 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
It is also possible to apply patches doing 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.