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

mutate-cow

v9.0.1

Published

Update immutable objects as if they were mutable with copy-on-write

Readme

mutate-cow

import mutate from 'mutate-cow';

const animals = deepFreeze({
  cats: ['ragamuffin', 'shorthair', 'maine coon'],
});

const newAnimals = mutate(animals)
  .set('dogs', ['hound'])
  .update('cats', (ctx) => {
    ctx.write().push('bobtail');
  })
  .final();

This module allows you to update an immutable object as if it were mutable. It has copy-on-write semantics, so properties are only changed if you write to them. (In fact, if you perform no writes, the same object is returned back.) This makes it useful in conjuction with libraries like React, where state may be compared by reference.

No cows were harmed in the making of this code.

API

const ctx = mutate(source)

Returns a "context" object which can modify a copy of source.

const foo = deepFreeze({bar: {baz: []}});
const ctx = mutate(foo);

You can mutate primitives, arrays, and plain objects. However, note that null prototypes and frozenness are not preserved.

ctx.read()

Returns the current working copy of the context's source object, or just source if no changes were made.

ctx.read() === foo; // no changes
ctx.set('bar', 'baz', ['qux']);
ctx.read().bar.baz[0] === 'qux'; // changes

ctx.write()

Returns the current working copy of the context's source object. Makes a shallow copy of source first if no changes were made.

You normally don't need to call write. It's mainly useful for accessing methods on copied objects (e.g., array methods).

ctx.get('bar', 'baz').write().push('qux');
ctx.read().bar.baz[0] === 'qux';

ctx.get(...path: [prop1, ...])

Returns a child context object for the given path.

Passing zero arguments returns ctx.

ctx.get() === ctx;
ctx.get('bar').read() === foo.bar;
ctx.get('bar', 'baz').read().length === 0;

ctx.set(...path: [prop1, ...], value)

Sets the given path to value on the current working copy. Returns ctx.

Passing zero property names (i.e., only a value) sets the current context's value.

Only own properties are written.

const qux = ['qux'];
// these all do the same thing
ctx.set({bar: {baz: qux}});
ctx.set('bar', {baz: qux});
ctx.set('bar', 'baz', qux);
ctx.get('bar').set({baz: qux});
ctx.get('bar').set('baz', qux);
ctx.get('bar', 'baz').set(qux);

// sets the own property '__proto__', not the prototype
ctx.set('__proto__', {});

ctx.merge(object)

Recursively merges a plain object into the current working copy, as if set were called with each of its own enumerable key/value pairs. Returns ctx.

Keys that aren't defined in object are preserved on the working copy. Values that aren't plain objects (including arrays, class instances, and primitives) replace the existing value.

Only own properties are written.

ctx.merge({bar: {qux: 1}});
ctx.read().bar.qux === 1;
ctx.read().bar.baz === foo.bar.baz; // other properties are preserved

// these all do the same thing
ctx.merge({bar: {qux: 1}});
ctx.get('bar').merge({qux: 1});
ctx.set('bar', 'qux', 1);

ctx.update(...path: [prop1, ...], updater)

Calls updater(ctx.get(...path)) and returns ctx.

const copy = ctx
  .update('bar', 'baz', (bazCtx) => {
    bazCtx.write().push('qux');
  })
  .final();
copy.bar.baz[0] === 'qux';

ctx.parent()

Returns the parent context of ctx.

ctx.parent() === null;
ctx.get('bar').parent() === ctx;
ctx.get('bar', 'baz').parent() === ctx.get('bar');

ctx.root()

Returns the root context of ctx.

ctx.root() === ctx;
ctx.get('bar').root() === ctx;
ctx.get('bar', 'baz').root() === ctx;

ctx.revoke()

Revokes ctx so that it can no longer be used. Returns undefined.

Attempting to use any method other than isRevoked on a revoked context will throw an error. This sets all internal properties to null so that there's no longer any reference to the source object or copy.

ctx.isRevoked()

Returns a boolean indicating whether ctx has been revoked.

ctx.final()

This is the same as read, except it also revokes the context. This is what you call to get the final copy.

const copy = mutate(foo).set('bar', 'baz', 'qux').final();

ctx.finalRoot()

Returns ctx.root().final().