Skip to main content

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.