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 🙏

© 2024 – Pkg Stats / Ryan Hefner

transmutable

v0.17.0

Published

immutable objects that pretend to be mutable

Downloads

125

Readme

Transmutable - immutable objects that pretend to be mutable

logo: on left there is a red circle
(representing original state). On right there are two circles: red (because original state is the same before and after), and a blue one (new state derived from first one)

Transmutable is a small library (4.85kB) which allows to write immutable code in a way which is very similar to writing mutable code.

Instead of using object spread (...) or Object.assign you just call transform with your transforming function as an argument. Your function will get draft, i.e. special Proxy object (or just a copy of state, if ES6 Proxies aren't available). Then you can "mutate" your draft object.

Example:

const copy = transform(draft => {
    draft.some.object.animal = 'dog';
}, original);

After that Transmutable will go through your mutations and will make automatically a new object derived from previous object + mutations you have provided. Things your modify will be copied with changes applied to the copy, things you didn't touch will be copied just by reference (structural sharing) so you don't lose your immutable references.

screenshot

It allows for reducing of boilerplate traditionally associated with writing immutable code in JavaScript (especially in libraries like Redux).

Transmutable is based on idea that immutability should not come at the cost of developer experience.

So instead of forcing user to manually copying objects with Object.assign / ..., it leaves this part to the library. The library presents you a draft (proxy object which records your mutations and creates some kind of patch).

Then the patch is applied and you have effect similar to updating your state with ... or Object.assign but handled automatically, without boilerplate.

Usage

API

transform

const { transform, transformAt } = require('transmutable');

const original = {a: 123};

const copy = transform(draft => {
	draft.a = 456;
}, original);

console.log({original, copy});
// { original: { a: 123 }, copy: { a: 456 } }

or you could use also this variable:

const copy = transform(function () {
	this.a = 456;
}, original);

Notice that this won't work with arrow-functions because they just don't have this at all.

transformAt

transformAt for applying changes only in the slice of state (concept similar to functional lenses):

const original = {
	some: {
		deep: {
			object: {
				foo: 123,
				bar: 'hello'
			}
		}
	}
}
const copy = transformAt(['some', 'deep', 'object'], d => {
    d.foo = 456;
    d.bar += ' world';
}, original);

Result will be:

{
	some: {
		deep: {
			object: {
				foo: 456,
				bar: 'hello world'
			}
		}
	}
}

integrations

with Redux:

const { transform } = require('transmutable');
const { createStore } = require('redux');

// when transform gets only one argument it returns curried function
const reducer = transform((state, action) => {
	switch (action.type) {
		case 'inc':
			state.counter++;
			break;
		case 'concat':
			state.text += action.text;
			break;
	}
});
const initialState = {counter: 1, text: ''};
const store = createStore(reducer, initialState);

store.dispatch({type: 'inc'});
store.dispatch({type: 'inc'});
store.dispatch({type: 'inc'});
store.dispatch({type: 'concat', text: 'Hello'});
store.dispatch({type: 'concat', text: ' '});
store.dispatch({type: 'concat', text: 'world'});

assert.deepStrictEqual(
	store.getState(),
	{counter: 4, text: 'Hello world'}
);
// initial state has not changed :)
assert.deepStrictEqual(initialState, {counter: 1, text: ''});

New in 0.12.0:
Now you can replace the whole state just by returning a new value. It works in both transform and transformAt.

This allows you for deciding when you want to transform only some properties and when you just want to replace a whole state:

function dec(d) {
    if (d.counter > 0)
        d.counter--; // mutation-like mode
    else
        return {message: 'countdown finished'}  // "returnish" mode
}

for (var i = 0, state = {counter:3}; i < 4; i++) {
    state = transform(dec, state);
    console.log(state)
}
// logs:
// { counter: 2 }
// { counter: 1 }
// { counter: 0 }
// { message: 'countdown finished' }

You can also use it feature for transforming selected properties when using transformAt:

transformAt(['foo', 'bar'], bar => bar + 1, {
    foo: {
        bar: 10
    }
}); // returns: { foo: { bar: 11 } }

Performance

Check out benchmark code.

Times in ms (the lower the better).

changing 100 objects in array of 1000 items. Repeated 10000 times.

ES6 Proxies

  • Time for transmutable (ES6 - Proxy): 2036ms
  • Time for immer (ES6 - proxies, without autofreeze): 2619ms

ES5 fallback

  • Time for transmutable (ES5 - diffing): 830ms
  • Time for immer (ES5 - getters/setters, without autofreeze): 12881ms

updating arbitrary object, one nested update. Repeated 10000 times

ES6 Proxies

  • Time for transmutable (ES6 - proxies): 52ms
  • Time for immer - (ES6 - proxies, without autofreeze): 94ms

ES5 fallback

  • Time for transmutable (ES5 - diffing) - example: 473ms
  • Time for immer - (ES5 - getters/setters, without autofreeze): 249ms

Tested on:

Node v8.4.0

Transmutable: 0.15.4

Immer: 1.0.1

Comparison with other libraries

Differences with Immer.

  • ES6 Proxy implementation of Transmutable is faster than ES6 Proxy implementation of Immer
  • comparison of ES5 implementations seems to be inconclusive (look above into detailed results of the benchmark)
  • Transmutable has additional function transformAt for transforming only a slice of state
  • Transmutable support "returnish" style of transformer, Immer does not and displays a warning when you try to do this.
  • transform/produce functions. Both libraries support parameter order: function, object. Both libraries support currying. But immer also supports object, function order.
  • Immer supports frozen objects (it can be disabled). Transmutable does not support frozen objects.

Gotchas

General:
  • Transmutable assumes immutability, so you should not perform any mutation of your objects outside the transmutable API.
Things dependent on current implementation:
  • In current version of Transmutable your state should be plain JS objects (numbers, strings, booleans, arrays, nested objects). You should not currently use e.g. ES6 Maps in your state. This may change in future versions.

  • Transmutable assumes by default that your state is a tree (one root object containing hierarchy of child objects), so no circular references, no repeated references etc.

  • Transmutable currently does not support frozen objects. Even if you freeze them by yourself (file an issue if this is a matter for you).