@sanity/diff-patch

Generates conflict-resistant Sanity patch mutations by diffing two JSON documents or values.

Library
npm
v6.0.0
34stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
28/100Needs Attention
Development Activity0
Maintenance20
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
77/100Good
Architecture82
Code Quality90
Innovation72
Learning Curve65

@sanity/diff-patch computes the set of Sanity patch mutations needed to transform one document (or arbitrary value) into another. Rather than diffing structurally and emitting a single blunt set per changed field, it walks strings, arrays, and objects with type-specific strategies designed for real-time, collaborative editing: string edits use character-level diff-match-patch operations where safe, arrays with _key-tagged items are diffed by key (not index) so reordering and concurrent edits merge cleanly, and everything is batched into the smallest practical number of mutation operations.

It is used internally by Sanity’s App SDK to power per-keystroke collaborative document editing, but the two exported functions — diffPatch for full documents and diffValue for bare values — work standalone in any code that needs to turn a before/after pair into Sanity’s patch format, with optional optimistic-locking via ifRevisionID.

What You Get

  • diffPatch() for diffing whole Sanity documents into ready-to-send mutation arrays, including automatic _id extraction and optional ifRevisionID optimistic locking
  • diffValue() for diffing bare values/subtrees with an optional basePath prefix, for cases outside full-document patching
  • Key-aware array diffing (_key-tagged items) that generates reorder/insert/unset patches instead of blunt index-based rewrites, so concurrent array edits merge without clobbering each other
  • Automatic string diffing via @sanity/diff-match-patch with tuned size/change-ratio heuristics that fall back to plain set for large replacements, keeping keystroke-level edits fast
  • A typed DiffError with path/value context for invalid Sanity document shapes (bad key names, multi-dimensional arrays, immutable _type changes)
  • Zero configuration required and zero network calls — pure, synchronous, dependency-light computation you can run anywhere

Common Use Cases

  • Powering real-time collaborative editing in a custom Sanity Studio input component, generating patches per keystroke
  • Building an offline-first or optimistic-update flow that reconciles local document edits into Sanity mutations before sending them
  • Writing a migration or sync script that computes minimal patches between two versions of a dataset’s documents instead of overwriting them wholesale
  • Implementing undo/redo or diffing tooling for editorial review flows that need to show or replay exact document changes

Under The Hood

Architecture The library is a small, purely functional module with no classes or DI container: src/diffPatch.ts holds the core recursive diffItem dispatcher that routes to diffString, diffArray (itself splitting into key-based vs index-based strategies), and diffObject based on the runtime type of the compared values, threading a path and mutable patches accumulator through the recursion; src/patches.ts defines the internal Patch union and public SanityPatchOperations types; src/paths.ts, src/validate.ts, and src/setOperations.ts hold supporting path-formatting, Sanity-key validation, and set-arithmetic helpers; src/index.ts re-exports the two public entry points (diffPatch, diffValue) plus DiffError and types. A final serializePatches pass batches same-type operations (merging consecutive set/diffMatchPatch ops, batching unset paths, anchor-based insert batching, and a two-phase temp-key reorder strategy that avoids _key collisions during array swaps) into the fewest Sanity mutation objects possible. Because every consumer (Sanity Studio, the App SDK) funnels through this single diffItem dispatch, it is the one abstraction that would ripple outward if changed.

Tech Stack Strict TypeScript compiled to dual ESM/CJS output (dist/index.js + dist/index.cjs plus .d.ts) via @sanity/pkg-utils (Vite-based), published as an ESM-only (type: module) package. The only runtime dependency is @sanity/diff-match-patch, Sanity’s own Myers-diff implementation used for character-level string diffing. Tests run on Vitest with @vitest/coverage-v8; linting is ESLint with eslint-config-sanity plus typescript-eslint and a tsc --noEmit check chained into posttest. Releases are cut with semantic-release from conventional commits, gated by a GitHub Actions matrix across Node 18/20/22 with an optional live integration-test job against a real Sanity project when repo secrets are configured. There is no server or database — this is a pure, synchronous computation library.

Code Quality An extensive test suite (11 files under test/, plus snapshot fixtures) covers primitive, object, and array diffing, portable-text-shaped nested arrays, diff-match-patch string behavior, ifRevisionID locking, validation/safeguard edge cases, and full integration snapshots. posttest chains ESLint and a type-check so lint/type errors fail CI the same as test failures. Errors are explicit and typed via a custom DiffError class carrying path and value context rather than being swallowed silently. Naming is consistent and descriptive (diffItem, diffArrayByKey, serializePatches), typing is used throughout with no loose any beyond a few deliberately narrow casts, and non-obvious heuristics (the diff-match-patch size/ratio thresholds, the two-phase reorder algorithm) carry dense explanatory comments.

API Design The public surface is deliberately tiny — diffPatch for documents, diffValue for bare values — with JSDoc on every exported symbol doubling as generated API reference (@public/@internal tags throughout). Getting started needs no configuration: diffPatch(source, target) alone returns ready-to-send mutations. Its real strength is encoded domain knowledge rather than novel general-purpose diffing: _key-addressed array diffing for collaboration-safe reordering, skipping diff-match-patch for system keys and for large-ratio string replacements, and exposing ifRevisionID optimistic locking directly in the options object — a lot of Sanity-specific collaborative-editing behavior behind a two-function API.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search