npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sanity/diff-patch

v6.0.0

Published

Generates a set of Sanity patches needed to change an item (usually a document) from one shape to another

Readme

@sanity/diff-patch

npm versionnpm bundle sizenpm weekly downloads

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.

Objectives

  • Conflict-resistant patches: Generate operations that work well in 3-way merges and collaborative scenarios
  • Performance: Optimized for real-time, per-keystroke patch generation
  • Intent preservation: Capture the user's intended change rather than just the final state
  • Reliability: Consistent, well-tested behavior across different data types and editing patterns

Used internally by the Sanity App SDK for its collaborative editing system.

Installation

npm install @sanity/diff-patch

API Reference

diffPatch(source, target, options?)

Generate patch mutations to transform a source document into a target document.

Parameters:

  • source: DocumentStub - The original document
  • target: DocumentStub - The desired document state
  • options?: PatchOptions - Configuration options

Returns: 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 value
  • target: unknown - The desired value state
  • basePath?: 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
//     }
//   }
// ]

Collaborative Editing Example

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
    },
  ],
}

Technical Details

String Diffing with diff-match-patch

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:

  • String size limit: Strings larger than 1MB use set operations
  • Change ratio threshold: If >40% of text changes (determined by simple string length difference), uses set (indicates replacement vs. editing)
  • Small text optimization: Strings <10KB will always use diff-match-patch
  • System key protection: Properties starting with _ (e.g. _type, _key) always use set operations as these are not typically edited by users

Performance rationale:

These thresholds are based on performance testing of the underlying @sanity/diff-match-patch library on an M2 MacBook Pro:

  • Keystroke editing: 0ms for typical edits, sub-millisecond even on large strings
  • Small insertions/pastes: 0-10ms for content <50KB
  • Large insertions/deletions: 0-50ms for content >50KB
  • Text replacements: Can be 70ms-2s+ due to algorithm complexity

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.

Array Handling

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')

System Keys

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

Error Handling

  • Missing document ID: Throws error if _id differs between documents and no explicit id option provided
  • Immutable _type: Throws error if attempting to change _type at document root
  • Multi-dimensional arrays: Not supported, throws DiffError
  • Invalid revision: Throws error if ifRevisionID: true but no _rev in source document

License

MIT © Sanity.io