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 🙏

© 2026 – Pkg Stats / Ryan Hefner

nano-rfc6902

v1.0.12

Published

Lightweight JSON Patch (RFC 6902) utilities for Node.js and the browser

Readme

nano-rfc6902

Lightweight JSON Patch (RFC 6902) utilities for Node.js and the browser.

npm version npm downloads CI license zero deps types bundle size node version

Highlights:

  • Zero dependencies

  • Tiny footprint (size-limit: 3 kB max, currently ~1.93 kB brotli)

  • Fast diff/patch

  • ESM + CJS + types

  • Works in Node.js and modern browsers

  • Generate patches: createPatch(oldValue, newValue)

  • Apply patches in-place: applyPatch(target, patch)

Installation

npm install nano-rfc6902

Benchmark (nano-rfc6902 vs rfc6902)

Run:

npm run benchmark

Per-operation benchmark (RFC ops):

npm run benchmark:ops

What it does:

  • Builds this library (dist) first.
  • Runs benchmark/compare.mjs with warmup and repeated iterations.
  • Prints diff and patch throughput for nano-rfc6902 and rfc6902.
  • benchmark:ops prints per-operation throughput for applyPatch (add, remove, replace, move, copy, test).
  • createPatch is diff-based and benchmarks per-op only where applicable (add, remove, replace); move/copy/test are reported as unsupported by design.

How to compare fairly:

  • Use the same machine and Node.js version.
  • Run several times and compare medians, not a single run.
  • Treat results as workload-specific (your real data shape may differ).

Sample results (May 30, 2026, Node 20, iterations=5000, warmup=500, runs=21 median):

Diff benchmark
nano-rfc6902  | total=   42.66ms | avg=  0.0085ms/op | throughput=  117207 ops/s
rfc6902       | total=  533.67ms | avg=  0.1067ms/op | throughput=    9369 ops/s

Patch benchmark
nano-rfc6902  | total=   34.07ms | avg=  0.0068ms/op | throughput=  146775 ops/s | patch=12
rfc6902       | total=   80.52ms | avg=  0.0161ms/op | throughput=   62096 ops/s | patch=12

Speed on this fixture (throughput):

  • Diff: nano-rfc6902 is ~12.51x faster than rfc6902.
  • Patch: nano-rfc6902 is ~2.36x faster than rfc6902.

Per-operation sample (npm run benchmark:ops, same env):

applyPatch
add      | nano=13025345 ops/s | rfc6902= 892836 ops/s
remove   | nano= 3707169 ops/s | rfc6902= 660545 ops/s
replace  | nano= 5354850 ops/s | rfc6902=2156075 ops/s
move     | nano= 2571448 ops/s | rfc6902= 820362 ops/s
copy     | nano= 6669690 ops/s | rfc6902= 990198 ops/s
test     | nano= 6751812 ops/s | rfc6902=1754571 ops/s

createPatch
add      | nano=2374326 ops/s | rfc6902= 130955 ops/s
remove   | nano=2372869 ops/s | rfc6902= 133718 ops/s
replace  | nano= 497928 ops/s | rfc6902= 176571 ops/s
move/copy/test: unsupported by createPatch (diff-based)

Who is faster (per operation, lower total time over 5000 iterations):

| Operation | API | Faster Library | Speedup | | --- | --- | --- | --- | | add | applyPatch | nano-rfc6902 | ~14.74x | | remove | applyPatch | nano-rfc6902 | ~5.61x | | replace | applyPatch | nano-rfc6902 | ~2.49x | | move | applyPatch | nano-rfc6902 | ~3.14x | | copy | applyPatch | nano-rfc6902 | ~6.73x | | test | applyPatch | nano-rfc6902 | ~3.85x | | add | createPatch | nano-rfc6902 | ~18.10x | | remove | createPatch | nano-rfc6902 | ~17.72x | | replace | createPatch | nano-rfc6902 | ~2.82x |

Quick start

Node.js (ESM)

import { createPatch, applyPatch } from "nano-rfc6902";

const before = { name: "Ada", skills: ["math"] };
const after = { name: "Ada Lovelace", skills: ["math", "programming"] };

// 1) Create a JSON Patch (RFC 6902 operations)
const patch = createPatch(before, after);
// Example patch (shape will depend on diff):
// [
//   { op: 'replace', path: '/name', value: 'Ada Lovelace' },
//   { op: 'add',     path: '/skills/1', value: 'programming' }
// ]

// 2) Apply the patch (mutates the target in-place)
applyPatch(before, patch);

console.log(before); // -> { name: 'Ada Lovelace', skills: ['math', 'programming'] }

Node.js (CommonJS)

const { createPatch, applyPatch } = require("nano-rfc6902");

const a = { count: 1 };
const b = { count: 2 };

const patch = createPatch(a, b);
applyPatch(a, patch);

console.log(a); // -> { count: 2 }

Browser

With a bundler (Vite, Webpack, etc.), import from the package name:

<script type="module">
  import { createPatch, applyPatch } from "nano-rfc6902";

  const oldState = { items: ["a"] };
  const newState = { items: ["a", "b"] };

  const patch = createPatch(oldState, newState);
  applyPatch(oldState, patch);

  console.log(oldState); // -> { items: ['a', 'b'] }
</script>

Without a bundler, you can use a CDN that serves ESM:

<script type="module">
  import {
    createPatch,
    applyPatch,
  } from "https://cdn.jsdelivr.net/npm/nano-rfc6902/+esm";

  const src = { a: 1 };
  const dst = { a: 1, b: 2 };

  const patch = createPatch(src, dst);
  applyPatch(src, patch);

  console.log(patch); // e.g., [{ op: 'add', path: '/b', value: 2 }]
</script>

API

createPatch(oldValue, newValue) => Operation[]

Computes a minimal set of RFC 6902 operations to transform oldValue into newValue.

  • Diffs primitives, objects, and arrays.
  • Arrays use an LCS-based strategy to produce intuitive insert/remove/replace operations and preserve nested diffs when elements are equal by deep comparison.

applyPatch(target, patch) => void

Applies an RFC 6902 patch to target in-place.

  • Supports add, remove, replace, move, copy, and test.
  • Uses JSON Pointer (RFC 6901) for path and from fields (e.g., /a/b/0).
  • Throws if paths are invalid or test fails.

Utils: isSafeApply(patch) => boolean

A small utility exported as a separate entry that validates whether a patch only targets object keys (and never array indices or the special - append). Returns true if the patch is safe to apply without mutating array positions. Implementation: TypeScript.isSafeApply()

Import

  • ESM:
    import { isSafeApply } from "nano-rfc6902/isSafeApply";
  • CommonJS:
    const { isSafeApply } = require("nano-rfc6902/isSafeApply");

Safe examples (true)

import { isSafeApply } from "nano-rfc6902/isSafeApply";

const patch = [
  { op: "add", path: "/user/name", value: "Ada" },
  { op: "replace", path: "/meta/title", value: "Dr." },
  { op: "test", path: "/count", value: 1 },
];
isSafeApply(patch); // true

Unsafe examples (false)

import { isSafeApply } from "nano-rfc6902/isSafeApply";

// Targets array index
isSafeApply([{ op: "add", path: "/items/0", value: "a" }]); // false

// Appends to array end
isSafeApply([{ op: "add", path: "/items/-", value: "a" }]); // false

// move/copy touching arrays (either from or path)
isSafeApply([{ op: "move", from: "/items/0", path: "/items/1" }]); // false
isSafeApply([{ op: "copy", from: "/a/0", path: "/b/0" }]); // false

Notes

  • For move and copy, both from and path must be object-key paths (no array indices or -).
  • Empty patches are considered safe.

Types (TypeScript)

This package ships first-class types. Core shapes mirror RFC 6902:

type JSONValue =
  | string
  | number
  | boolean
  | null
  | undefined
  | JSONValue[]
  | { [key: string]: JSONValue };

type Operation =
  | { op: "add"; path: string; value: JSONValue }
  | { op: "remove"; path: string }
  | { op: "replace"; path: string; value: JSONValue }
  | { op: "move"; from: string; path: string }
  | { op: "copy"; from: string; path: string }
  | { op: "test"; path: string; value: JSONValue };

Notes:

  • JSON Pointer escaping follows RFC 6901: ~ -> ~0, / -> ~1.
  • applyPatch mutates the target you pass in.
  • undefined is included in JSONValue for ergonomic diffs in JS/TS; be aware that literal JSON does not have undefined.

RFC 6902 JSON Patch Overview

RFC 6902 defines a JSON document structure for expressing a sequence of operations to apply to a JSON document. It's commonly used for:

  • Efficient API updates (send only changes, not entire documents)
  • Real-time collaboration and operational transformation
  • Version control and change tracking
  • Undo/redo functionality
  • Optimistic UI updates

JSON Pointer (RFC 6901)

Operations use JSON Pointer syntax to reference locations in documents:

  • / - Root document
  • /foo - Property "foo" at root
  • /foo/bar - Nested property "bar" inside "foo"
  • /array/0 - First element of array
  • /array/- - Append to end of array (add operation only)

Special characters must be escaped:

  • ~ becomes ~0
  • / becomes ~1

Example: To reference property "a/b~c", use path "/a~1b~0c"

All Operation Types

1. add - Add a value

Adds a value at the specified location. For objects, creates or overwrites the property. For arrays, inserts at the index (shifting elements right).

import { applyPatch } from "nano-rfc6902";

// Add object property
const obj = { name: "Alice" };
applyPatch(obj, [{ op: "add", path: "/age", value: 30 }]);
console.log(obj); // { name: "Alice", age: 30 }

// Add to array at specific index
const arr = ["a", "c"];
applyPatch(arr, [{ op: "add", path: "/1", value: "b" }]);
console.log(arr); // ["a", "b", "c"]

// Append to array end
const items = [1, 2];
applyPatch(items, [{ op: "add", path: "/-", value: 3 }]);
console.log(items); // [1, 2, 3]

// Add nested property (auto-creates intermediate objects)
const data = {};
applyPatch(data, [{ op: "add", path: "/user/profile/name", value: "Bob" }]);
console.log(data); // { user: { profile: { name: "Bob" } } }

2. remove - Remove a value

Removes the value at the specified location. For arrays, removes the element and shifts remaining elements left.

import { applyPatch } from "nano-rfc6902";

// Remove object property
const obj = { name: "Alice", age: 30, city: "NYC" };
applyPatch(obj, [{ op: "remove", path: "/age" }]);
console.log(obj); // { name: "Alice", city: "NYC" }

// Remove array element
const arr = ["a", "b", "c", "d"];
applyPatch(arr, [{ op: "remove", path: "/1" }]);
console.log(arr); // ["a", "c", "d"]

// Remove nested property
const data = { user: { profile: { name: "Bob", email: "[email protected]" } } };
applyPatch(data, [{ op: "remove", path: "/user/profile/email" }]);
console.log(data); // { user: { profile: { name: "Bob" } } }

3. replace - Replace a value

Replaces the value at the specified location. Equivalent to remove followed by add, but atomic.

import { applyPatch } from "nano-rfc6902";

// Replace object property
const obj = { name: "Alice", status: "pending" };
applyPatch(obj, [{ op: "replace", path: "/status", value: "active" }]);
console.log(obj); // { name: "Alice", status: "active" }

// Replace array element
const arr = [1, 2, 3];
applyPatch(arr, [{ op: "replace", path: "/1", value: 99 }]);
console.log(arr); // [1, 99, 3]

// Replace nested value
const config = { server: { port: 3000, host: "localhost" } };
applyPatch(config, [{ op: "replace", path: "/server/port", value: 8080 }]);
console.log(config); // { server: { port: 8080, host: "localhost" } }

4. move - Move a value

Removes the value at from location and adds it to path location. Atomic operation.

import { applyPatch } from "nano-rfc6902";

// Move property between objects
const obj = { temp: { value: 42 }, data: {} };
applyPatch(obj, [{ op: "move", from: "/temp/value", path: "/data/value" }]);
console.log(obj); // { temp: {}, data: { value: 42 } }

// Move array element
const arr = ["a", "b", "c", "d"];
applyPatch(arr, [{ op: "move", from: "/3", path: "/0" }]);
console.log(arr); // ["d", "a", "b", "c"]

// Rename property
const user = { firstName: "Alice", lastName: "Smith" };
applyPatch(user, [{ op: "move", from: "/firstName", path: "/name" }]);
console.log(user); // { name: "Alice", lastName: "Smith" }

5. copy - Copy a value

Copies the value at from location to path location. Creates a deep clone.

import { applyPatch } from "nano-rfc6902";

// Copy object property
const obj = { original: { value: 42 }, backup: {} };
applyPatch(obj, [
  { op: "copy", from: "/original/value", path: "/backup/value" },
]);
console.log(obj); // { original: { value: 42 }, backup: { value: 42 } }

// Copy array element
const arr = [{ id: 1, name: "Alice" }];
applyPatch(arr, [{ op: "copy", from: "/0", path: "/-" }]);
console.log(arr); // [{ id: 1, name: "Alice" }, { id: 1, name: "Alice" }]

// Duplicate nested structure
const data = { template: { x: 1, y: 2 } };
applyPatch(data, [{ op: "copy", from: "/template", path: "/instance" }]);
console.log(data); // { template: { x: 1, y: 2 }, instance: { x: 1, y: 2 } }

// Modifications to copy don't affect original
data.instance.x = 99;
console.log(data.template.x); // Still 1

6. test - Test a value

Tests that the value at the specified location equals the given value. Throws an error if the test fails. Useful for preventing conflicts in concurrent updates.

import { applyPatch } from "nano-rfc6902";

// Successful test
const obj = { version: 1, data: "hello" };
applyPatch(obj, [
  { op: "test", path: "/version", value: 1 },
  { op: "replace", path: "/data", value: "world" },
]);
console.log(obj); // { version: 1, data: "world" }

// Failed test throws error
const user = { age: 25 };
try {
  applyPatch(user, [
    { op: "test", path: "/age", value: 30 }, // Expects 30, but actual is 25
    { op: "replace", path: "/age", value: 31 },
  ]);
} catch (err) {
  console.log(err.message); // "Test operation failed at path /age"
}

// Test with nested objects
const config = { settings: { theme: "dark", lang: "en" } };
applyPatch(config, [
  { op: "test", path: "/settings/theme", value: "dark" },
  { op: "replace", path: "/settings/theme", value: "light" },
]);
console.log(config); // { settings: { theme: "light", lang: "en" } }

Complex Example: Multiple Operations

import { applyPatch } from "nano-rfc6902";

const document = {
  users: [
    { id: 1, name: "Alice", role: "admin" },
    { id: 2, name: "Bob", role: "user" },
  ],
  metadata: {
    version: 1,
    lastModified: "2024-01-01",
  },
};

applyPatch(document, [
  // Test version before applying changes
  { op: "test", path: "/metadata/version", value: 1 },

  // Update user role
  { op: "replace", path: "/users/1/role", value: "admin" },

  // Add new user
  {
    op: "add",
    path: "/users/-",
    value: { id: 3, name: "Charlie", role: "user" },
  },

  // Update metadata
  { op: "replace", path: "/metadata/lastModified", value: "2024-01-15" },
  { op: "replace", path: "/metadata/version", value: 2 },

  // Add new metadata field
  { op: "add", path: "/metadata/author", value: "System" },
]);

console.log(document);
// {
//   users: [
//     { id: 1, name: "Alice", role: "admin" },
//     { id: 2, name: "Bob", role: "admin" },
//     { id: 3, name: "Charlie", role: "user" }
//   ],
//   metadata: {
//     version: 2,
//     lastModified: "2024-01-15",
//     author: "System"
//   }
// }

Creating Patches Automatically

Instead of manually writing patches, use createPatch to generate them:

import { createPatch, applyPatch } from "nano-rfc6902";

const before = {
  name: "Alice",
  age: 30,
  hobbies: ["reading", "gaming"],
  address: { city: "NYC", zip: "10001" },
};

const after = {
  name: "Alice",
  age: 31,
  hobbies: ["reading", "gaming", "hiking"],
  address: { city: "NYC", zip: "10002", country: "USA" },
};

// Generate patch automatically
const patch = createPatch(before, after);
console.log(patch);
// [
//   { op: "replace", path: "/age", value: 31 },
//   { op: "add", path: "/hobbies/2", value: "hiking" },
//   { op: "replace", path: "/address/zip", value: "10002" },
//   { op: "add", path: "/address/country", value: "USA" }
// ]

// Apply to original
applyPatch(before, patch);
console.log(before); // Now matches 'after'

Development

Prerequisites

  • Node.js >= 20.0.0 (LTS)
  • npm or pnpm

Install dependencies

npm install

Build

npm run build

Outputs:

  • dist/index.js — ES module
  • dist/index.cjs — CommonJS
  • dist/index.d.ts — TypeScript declarations

Type checking

npm run type-check

Tests

npm test

Size check

npm run size

Current size-limit target and result:

  • Limit: 3 kB (dist/index.js)
  • Measured: ~1.93 kB (minified + brotli)

Features

  • Zero dependencies
  • Tiny footprint: 3 kB max (size-limit target), currently ~1.93 kB min+brotli
  • Fast and efficient diff/patch
  • JSON Patch (RFC 6902) create/apply
  • Small API surface
  • TypeScript types included
  • Works in Node.js and modern browsers
  • ESM and CJS builds

License

BSD-3-Clause