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

voryn-core

v0.1.3

Published

A change engine for JavaScript and TypeScript objects — diff, apply, reverse, merge, and undo/redo for structured data.

Readme

Voryn

npm version CI license

Git for JavaScript objects.

Turn changes between JavaScript/TypeScript objects into deterministic, serializable, reversible changesets — then use the same changeset to apply, reverse, store, transmit, or merge that change.

diff · apply · reverse · merge · history
npm install voryn-core

Works in Node.js (≥18), browsers, Bun, Deno, and Cloudflare Workers, with ESM and CommonJS builds plus TypeScript declarations.

⚠️ Early release (v0.1.x). Published on npm — this isn't a placeholder or a preview, it's a real, working package you can install today. The API and specification are still stabilizing based on real-world feedback before a 1.0 commitment — see Status below.


30 seconds

import { diff, apply, reverse } from "voryn-core";

const before = { name: "John", age: 30 };
const after = { name: "John", age: 31 };

const changes = diff(before, after);
// { ops: [{ operation: "replace", path: ["age"], oldValue: 30, newValue: 31 }] }

const updated = apply(before, changes);       // { name: "John", age: 31 }
const original = apply(updated, reverse(changes)); // back to { name: "John", age: 30 }
before                  after
──────────────          ──────────────
{                       {
  name: "John",           name: "John",
  age: 30                 age: 31
}                       }
             ↓
        voryn changeset
        replace ["age"]
           30 → 31
diff()
   ↓
Changeset
   ↓
apply()   ──────→ new state
reverse() ──────→ original state
merge()   ──────→ combined state (or a reported conflict)

That's the whole product. Everything below is detail.

Why

Most applications end up rebuilding the same handful of things, usually under different names: undo/redo, audit logs, version history, synchronization, merge-conflict handling, and — more recently — reviewing what an AI agent is proposing to change before letting it happen. All of these are really the same underlying question, asked repeatedly: what changed?

Voryn answers that once, as a plain, storable value, instead of every feature re-deriving it independently:

  • Store changes — object → changeset → database column
  • Send changes — client → changeset → server
  • Reverse changes — changeset → reverse() → the previous state
  • Combine changes — two changesets from a common source → merge()

Concrete use cases this shows up in: undo/redo, audit logs, AI agent action review, collaborative editing, version history, event sourcing, offline sync, and database change tracking.

Voryn isn't trying to replace every diff/patch/state-management library — it's built specifically around one thing: a portable, reversible changeset model, rather than just a list of mutations. If you only need a boolean "did these differ," a general deep-equal function is simpler. If you need to reverse, store, merge, or transmit a change, that's what Voryn is for. See Voryn vs. JSON Patch for a detailed, RFC-verified comparison against the closest existing standard.

What Voryn actually is

Voryn is an open changeset format — a specification for representing a change between two structured values. voryn-core is its TypeScript reference implementation.

That distinction matters because the specification is language-independent by design: nothing about the format requires TypeScript, so a Rust, Go, or Python implementation could exist someday, following the same spec, producing changesets that mean the same thing regardless of which implementation made them. See spec/changeset.md for the formal contract, and docs/philosophy.md for the reasoning behind it.

The core idea: a Changeset

A changeset describes how one value becomes another, as a flat, ordered, serializable array of operations:

{
  ops: [
    { operation: "replace", path: ["age"], oldValue: 30, newValue: 31 }
  ]
}

There are exactly three operation kinds — add, remove, replace — deliberately, not four or five. See docs/philosophy.md for why there's no move. A changeset is plain, inspectable data: log it, store it in a database column, send it over the wire, diff it against another changeset.

Usage

diff() — compare two values

import { diff } from "voryn-core";

const changes = diff(
  { user: { name: "Ada", tags: ["math"] } },
  { user: { name: "Ada", tags: ["math", "computing"] } },
);
// {
//   ops: [
//     { operation: "add", path: ["user", "tags", 1], newValue: "computing" }
//   ]
// }

Changes are scoped to exactly what changed — a small edit deep inside a large object produces a small changeset, not a wholesale replace of the whole tree.

apply() — apply a changeset

import { apply } from "voryn-core";

const updated = apply(before, changes);

apply() never mutates its input; it returns a new value, reusing untouched parts of the original structure by reference.

reverse() — invert a changeset

import { diff, apply, reverse } from "voryn-core";

const changes = diff(before, after);
const undone = apply(after, reverse(changes));
// undone deep-equals `before`

This is the basis of undo/redo — see createHistory() below, which is built entirely on top of apply() and reverse().

merge() — combine changesets from a common source

import { diff, merge, apply } from "voryn-core";

const source = { profile: { name: "Old", avatar: "old.png" } };

const fromUserA = diff(source, { profile: { name: "New", avatar: "old.png" } });
const fromUserB = diff(source, { profile: { name: "Old", avatar: "new.png" } });

const combined = merge(fromUserA, fromUserB);
apply(source, combined);
// { profile: { name: "New", avatar: "new.png" } }

If both changesets touch the same path with genuinely different results, merge() throws VorynMergeConflictError rather than silently picking a side — resolving a real conflict is application logic, not something Voryn decides for you. See spec §9 for the exact conflict-detection rules.

createHistory() — undo/redo

import { createHistory } from "voryn-core";

const history = createHistory({ count: 0 });

history.update({ count: 1 });
history.update({ count: 2 });

history.undo(); // state is now { count: 1 }
history.undo(); // state is now { count: 0 }
history.redo(); // state is now { count: 1 }

history.canUndo; // boolean
history.canRedo; // boolean

update() diffs against the current state internally — you never construct changesets by hand for ordinary undo/redo. history also exposes apply(changeset) directly for cases where you already have a changeset (e.g. the output of merge()).

Reviewing AI agent changes before they land

A changeset is also how you let an AI agent propose an edit without giving it direct write access — diff() the current record against whatever the agent proposes, inspect the resulting changeset, and only apply() it once approved:

import { diff, apply } from "voryn-core";

const proposedByAgent = { ...ticket, status: "in_progress", priority: "urgent" };
const changes = diff(ticket, proposedByAgent);

// inspect changes.ops — a human or a policy decides here
if (humanApproves(changes)) {
  const updated = apply(ticket, changes);
}

Full example: examples/ai-agent-review.ts and docs/guide/ai-agent-review.md.

More runnable examples: examples/ — undo/redo, audit logging, collaborative merging, and AI agent review, each paired with a guide in docs/guide/. There's also a browser playground you can open directly, no build step required: examples/playground.html.

Design principles

  • Small, orthogonal vocabulary. Three operations. No move, no copy, no custom comparators, no configuration sprawl.
  • Deterministic and canonical. The same inputs always produce the same changeset — a requirement for changesets to work as a real interchange format, not just an implementation convenience.
  • Secure by default, with no opt-out. Prototype-pollution guarding, circular-reference detection, and resource limits are built in and cannot be disabled — see docs/guide/error-handling.md and SECURITY.md.
  • Zero runtime dependencies.
  • The specification is the product. See "What Voryn actually is" above and docs/philosophy.md.

Status

Voryn is at 0.1.x — early, but real: the specification and all five verbs (diff, apply, reverse, merge, history) are implemented, invariant-tested (124 conformance tests, 300 fuzz cases), and benchmarked against jsondiffpatch and microdiff (see benchmarks/README.md). What that doesn't mean yet: the API and the specification itself may still change before a 1.0 release. If you adopt it now, pin your version and expect to read release notes before upgrading — see CHANGELOG.md and docs/roadmap.md for what's shipped versus still planned.

Documentation

Contributing

See CONTRIBUTING.md for development setup, commit conventions, and what a good pull request looks like here. This project follows the Code of Conduct.

License

MIT — see LICENSE.