
Product
Socket Now Protects the Chrome Extension Ecosystem
Socket is launching experimental protection for Chrome extensions, scanning for malware and risky permissions to prevent silent supply chain attacks.
@sanity/diff-patch
Advanced tools
Generates a set of Sanity patches needed to change an item (usually a document) from one shape to another
Generate Sanity patch mutations by comparing two documents or values. This library creates conflict-resistant patches designed for collaborative editing environments where multiple users may be editing the same document simultaneously.
Used internally by the Sanity App SDK for its collaborative editing system.
npm install @sanity/diff-patch
diffPatch(source, target, options?)
Generate patch mutations to transform a source document into a target document.
Parameters:
source: DocumentStub
- The original documenttarget: DocumentStub
- The desired document stateoptions?: PatchOptions
- Configuration optionsReturns: SanityPatchMutation[]
- Array of patch mutations
Options:
interface PatchOptions {
id?: string // Document ID (extracted from _id if not provided)
basePath?: Path // Base path for patches (default: [])
ifRevisionID?: string | true // Revision lock for optimistic updates
}
Example:
import {diffPatch} from '@sanity/diff-patch'
const source = {
_id: 'movie-123',
_type: 'movie',
_rev: 'abc',
title: 'The Matrix',
year: 1999,
}
const target = {
_id: 'movie-123',
_type: 'movie',
title: 'The Matrix Reloaded',
year: 2003,
director: 'The Wachowskis',
}
const mutations = diffPatch(source, target, {ifRevisionID: true})
// [
// {
// patch: {
// id: 'movie-123',
// ifRevisionID: 'abc',
// set: {
// title: 'The Matrix Reloaded',
// year: 2003,
// director: 'The Wachowskis'
// }
// }
// }
// ]
diffValue(source, target, basePath?)
Generate patch operations for values without document wrapper.
Parameters:
source: unknown
- The original valuetarget: unknown
- The desired value statebasePath?: Path
- Base path to prefix operations (default: [])Returns: SanityPatchOperations[]
- Array of patch operations
Example:
import {diffValue} from '@sanity/diff-patch'
const source = {
name: 'John',
tags: ['developer'],
}
const target = {
name: 'John Doe',
tags: ['developer', 'typescript'],
active: true,
}
const operations = diffValue(source, target)
// [
// {
// set: {
// name: 'John Doe',
// 'tags[1]': 'typescript',
// active: true
// }
// }
// ]
// With base path
const operations = diffValue(source, target, ['user', 'profile'])
// [
// {
// set: {
// 'user.profile.name': 'John Doe',
// 'user.profile.tags[1]': 'typescript',
// 'user.profile.active': true
// }
// }
// ]
The library generates patches that preserve user intent and minimize conflicts in collaborative scenarios:
// Starting document
const originalDoc = {
_id: 'blog-post-123',
_type: 'blogPost',
title: 'Getting Started with Sanity',
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system for modern applications.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers controll over the entire stack.',
},
{
_key: 'conclusion',
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
],
}
// User A reorders paragraphs AND fixes a typo
const userAChanges = {
...originalDoc,
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system for modern applications.',
},
{
_key: 'conclusion', // Moved conclusion before benefits
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers control over the entire stack.', // Fixed typo: "controll" → "control"
},
],
}
// User B simultaneously improves the intro text
const userBChanges = {
...originalDoc,
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system that gives developers control over the entire stack.', // Added more specific language about developer control
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers control over the entire stack.',
},
{
_key: 'conclusion',
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
],
}
// Generate patches that capture each user's intent
const patchA = diffPatch(originalDoc, userAChanges)
const patchB = diffPatch(originalDoc, userBChanges)
// Apply both patches - they merge successfully because they target different aspects
// User A's reordering and typo fix + User B's content improvement both apply
const finalMergedResult = {
_id: 'blog-post-123',
_type: 'blogPost',
title: 'Getting Started with Sanity',
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system that gives developers control over the entire stack.', // ✅ User B's improvement
},
{
_key: 'conclusion', // ✅ User A's reordering
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers control over the entire stack.', // ✅ User A's typo fix
},
],
}
When comparing strings, the library attempts to use diff-match-patch to generate granular text patches instead of simple replacements. This preserves editing intent and enables better conflict resolution.
Automatic selection criteria:
set
operationsset
(indicates replacement vs. editing)_
(e.g. _type
, _key
) always use set
operations as these are not typically edited by usersPerformance rationale:
These thresholds are based on performance testing of the underlying @sanity/diff-match-patch
library on an M2 MacBook Pro:
The 40% change ratio threshold catches problematic replacement scenarios while allowing the algorithm to excel at insertions, deletions, and small edits.
Migration from v5:
Version 5 allowed configuring diff-match-patch behavior with lengthThresholdAbsolute
and lengthThresholdRelative
options. Version 6 removes these options in favor of tested defaults that provide consistent performance across real-world editing patterns. This allows us to change the behavior of this over time to better meet performance needs.
Keyed arrays: Arrays containing objects with _key
properties are diffed by key rather than index, producing more stable patches for collaborative editing.
Index-based arrays: Arrays without keys are diffed by index position.
Undefined values: When undefined
values are encountered in arrays, they are converted to null
. This follows the same behavior as JSON.stringify()
and ensures consistent serialization. To remove undefined values before diffing:
const cleanArray = array.filter((item) => typeof item !== 'undefined')
The following keys are ignored at the root of the document when diffing a document as they are managed by Sanity:
_id
_type
_createdAt
_updatedAt
_rev
_id
differs between documents and no explicit id
option provided_type
at document rootDiffError
ifRevisionID: true
but no _rev
in source documentMIT © Sanity.io
6.0.0 (2025-06-13)
diffItem
function is no longer exported. Its functionality is now primarily internal.diffValue(source: unknown, target: unknown, basePath?: Path): SanityPatchOperations[]
is introduced and exported. This function generates an array of SanityPatchOperations
(which are plain objects like {set: {...}}
, {unset: [...]}
) based on the differences between source
and target
values. It does not wrap these operations in the SanityPatchMutation
structure.diffPatch
function (which diffs documents and returns SanityPatchMutation[]
) now internally calls diffItem
and then uses the refactored serializePatches
to construct the final mutations. The logic for adding id
and ifRevisionID
to the patch mutations now resides within diffPatch
.SetPatch
, InsertAfterPatch
, SanitySetPatch
, SanityUnsetPatch
, SanityInsertPatch
, and SanityDiffMatchPatch
from the public API (some were previously exported from patches.ts
).SanitySetPatchOperation
({ set: Record<string, unknown> }
)SanityUnsetPatchOperation
({ unset: string[] }
)SanityInsertPatchOperation
({ insert: { before/after/replace: string, items: unknown[] } }
)SanityDiffMatchPatchOperation
({ diffMatchPatch: Record<string, string> }
)SanityPatchOperations
type is now a Partial
union of these new operation types, reflecting that a single patch object from diffValue
will contain one or more of these operations.SanityPatch
type (used within SanityPatchMutation
) now extends SanityPatchOperations
and includes id
and optional ifRevisionID
.Patch
type (used by diffItem
) remains but is now an internal detail.serializePatches
Function:
serializePatches
function now takes an array of internal Patch
objects and returns an array of SanityPatchOperation[]
(the raw operation objects like {set: {...}}
).id
or ifRevisionID
; this responsibility is moved to the diffPatch
function.set
, unset
, insert
, and diffMatchPatch
operations into distinct objects in the output array has been improved for clarity.diffPatch
Function:
diffItem
to get the raw patch list.serializePatches
to get SanityPatchOperations[]
.SanityPatchMutation[]
, adding the id
to each and ifRevisionID
only to the first patch mutation in the array.diffValue
to clearly explain its purpose, parameters, and return type.diffPatch
and internal types to reflect the changes.Rationale:
diffValue
provides a more intuitive name for diffing arbitrary JavaScript values and returning the raw operations, distinct from diffPatch
which is document-centric.Sanity...Operation
types are more precise and make it easier to work with the different kinds of patch operations programmatically.ifRevisionID
Handling: Ensuring ifRevisionID
is only on the first patch of a transaction is crucial for correct optimistic locking in Sanity.diffItem
focuses on generating a flat list of diffs, serializePatches
(as used by diffValue
) groups them into operations, and diffPatch
handles the document-specific concerns like _id
and ifRevisionID
.This refactor provides a cleaner and more robust API for generating patches, both for full documents and for arbitrary values.
* Removed the `diffMatchPatch` options (`enabled`, `lengthThresholdAbsolute`, `lengthThresholdRelative`) from `PatchOptions`.
* Removed the `DiffMatchPatchOptions` and `DiffOptions` (which included `diffMatchPatch`) interfaces from the public API.
* Removed the internal `mergeOptions` function and the DMP-specific parts of `defaultOptions`.
shouldUseDiffMatchPatch(source: string, target: string): boolean
. This function encapsulates the new logic for deciding whether to use DMP.DMP_MAX_DOCUMENT_SIZE
) will use set
operations.DMP_MAX_CHANGE_RATIO
) of the text changes, set
is used (indicates replacement vs. editing).DMP_MIN_SIZE_FOR_RATIO_CHECK
) always use DMP, as performance is consistently high for these._
(system keys) continue to use set
operations.shouldUseDiffMatchPatch
detailing the heuristic rationale, performance characteristics (based on testing @sanity/diff-match-patch
on an M2 MacBook Pro), algorithm details, and test methodology.getDiffMatchPatch
function now uses shouldUseDiffMatchPatch
to make its decision and no longer accepts DMP-related options.@sanity/diff-match-patch
library within getDiffMatchPatch
to use makePatches(source, target)
directly. This is more concise and leverages the internal optimizations of that library, with performance validated to be equivalent to the previous multi-step approach.SYSTEM_KEYS
, DMP_MAX_DOCUMENT_SIZE
, DMP_MAX_CHANGE_RATIO
, and DMP_MIN_SIZE_FOR_RATIO_CHECK
to define these thresholds.Rationale for Change:
The previous configurable thresholds for DMP were somewhat arbitrary and could lead to suboptimal performance or overly verbose patches in certain scenarios. This change is based on empirical performance testing of the @sanity/diff-match-patch
library itself. The new heuristics are designed to:
By hardcoding these well-tested heuristics, we aim for a more robust and performant string diffing strategy by default.
diffItem
with diffValue
(#39) (b8ad36a)FAQs
Generates a set of Sanity patches needed to change an item (usually a document) from one shape to another
The npm package @sanity/diff-patch receives a total of 144,768 weekly downloads. As such, @sanity/diff-patch popularity was classified as popular.
We found that @sanity/diff-patch demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 90 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket is launching experimental protection for Chrome extensions, scanning for malware and risky permissions to prevent silent supply chain attacks.
Product
Add secure dependency scanning to Claude Desktop with Socket MCP, a one-click extension that keeps your coding conversations safe from malicious packages.
Product
Socket now supports Scala and Kotlin, bringing AI-powered threat detection to JVM projects with easy manifest generation and fast, accurate scans.