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

immutable-ops

v0.7.0

Published

A collection of functions to perform immutable operations on plain JavaScript objects

Downloads

17,161

Readme

immutable-ops

NPM package GitHub Release Date npm bundle size NPM downloads NPM license

A collection of functions to perform immutable operations on plain JavaScript objects and arrays.

Like updeep but with batched mutations and no freezing.

Like icepick, but with batched mutations and a curried API that puts the target object as the last argument. No freezing.

Features

  • Small. It's just 10 functions.
  • Functional API with curried functions
  • JavaScript in, JavaScript out
  • Batched mutations

Installation

npm install immutable-ops --save

Example Usage

import compose from 'ramda/src/compose';
import ops from 'immutable-ops';

// These are all the available functions.
const {
    // Functions operating on objects.
    merge,
    mergeDeep,
    omit,
    setIn,

    // Functions operating on arrays.
    insert,
    splice,
    push,
    filter,

    // Functions operating on both
    set,

    // Placeholder for currying.
    __,
} = ops;

const arr = [1, 2, 3];

const pushFour = ops.push(4);
const pushFive = ops.push(5);

// All functions are curried. These functions
// still need the final argument, the array to
// operate on.
expect(pushFive).to.be.a('function');

const pushFourAndFive = compose(pushFive, pushFour);

const result = pushFourAndFive(arr);
// Two new arrays were created during `pushFourAndFive` execution.
expect(result).to.deep.equal([1, 2, 3, 4, 5]);



// Only one new array is created.
const sameResult = ops.batched(batchedOps => {
    // batchedOps is able to keep track of mutated
    // objects.
    return compose(
        batchedOps.push(5),
        batchedOps.push(4)
    )(arr);
});

expect(sameResult).to.deep.equal([1, 2, 3, 4, 5]);

Batched Mutations

A batch token is supplied by the user at the start of a batch, or created by immutable-ops. Each newly created object within a batch is tagged with that token. If a batch using token X operates on an object that is tagged with token X, it is free to mutate it. You can think of it as an ownership; the batch owns the newly created object and therefore is free to mutate it. New batches use a token Y that will never be equal to the previous token.

Tags are not removed; They are assigned to a non-enumerable property @@_______immutableOpsOwnerID which should avoid any collisions.

This token strategy is similar to what ImmutableJS uses to track batches.

Manually using batch tokens

ops.batch gives you access to all the immutable-ops functions that take a token as their additional first argument. Otherwise they are identical to the functions found in ops directly.

import ops from 'immutable-ops';
const token = ops.getBatchToken();

// This object has no batch token, since it was not created by immutable-ops.
const obj = {a: 1, b: 2};

// obj2 is a newly created object tagged with the token.
const obj2 = ops.batch.set(token, 'a', 10, obj);
expect(obj).to.not.equal(obj2)

// Because we operate on obj2 that has the same token as
// we passed to the function, obj2 is mutated.
const obj3 = ops.batch.set(token, 'b', 20, obj2);
expect(obj2).to.equal(obj3);

Handling batch tokens implicitly

import ops from 'immutable-ops';

const obj = {a: 1, b: 2};

const obj3 = ops.batched(batchedOps => {
    // batchedOps has functions that are bound to a new batch token.
    const obj2 = batchedOps.set('a', 10, obj);
    return batchedOps.set('b', 20, obj2);
});

Currying

All operations are curried by default. Functions are curried with ramda.curry. In addition to normal currying behaviour, you can use the ramda placeholder variable available in ops.__ to specify parameters you want to pass arguments for later. Example:

const removeNFromHead = ops.splice(/* startIndex */ 0, /* deleteCount */ops.__, /* valsToAdd */[]);
const removeTwoFromHead = removeNFromHead(2);
const arr = [1, 2, 3];

console.log(removeTwoFromHead(arr));
// [3];

Object API

merge(mergeObj, targetObj)

Performs a shallow merge on targetObj. mergeObj can be a single object to merge, or a list of objects. If a list is passed as mergeObj, objects to the right in the list will have priority when determining final attributes.

Returns the merged object, which will be a different object if an actual change was detected during the merge.

const result = ops.merge(
    // mergeObj
    {
        a: 'theA',
        b: {
            c: 'nestedC',
        },
    },
    // targetObj
    {
        a: 'theA2',
        b: {
            d: 'nestedD',
        },
        c: 'theC',
    }
);

console.log(result);
// {
//     {
//         a: 'theA',
//         b: {
//             c: 'nestedC'
//         },
//         c: 'theC',
//     },
// }

deepMerge(mergeObj, targetObj)

Same as merge, but performs merge recursively on attributes that are objects (not arrays).

const result = ops.deepMerge(
    // mergeObj
    {
        a: 'theA',
        b: {
            c: 'nestedC',
        },
    },
    // targetObj
    {
        a: 'theA2',
        b: {
            d: 'nestedD',
        },
        c: 'theC',
    }
);

console.log(result);
// {
//     {
//         a: 'theA',
//         b: {
//             c: 'nestedC',
//             d: 'nestedD',
//         },
//         c: 'theC',
//     },
// }

setIn(path, value, targetObj)

Returns an object, with the value at path set to value. path can be a dot-separated list of attribute values or an array of attribute names to traverse.


const obj = {
    location: {
        city: 'San Francisco',
    },
};

const newObj = ops.setIn(['location', 'city'], 'Helsinki', obj);
console.log(newObj);
// {
//     location: {
//         city: 'Helsinki',
//     },
// };

omit(keysToOmit, targetObj)

Returns a shallow copy of targetObj without the keys specified in keysToOmit. keysToOmit can be a single key name or an array of key names.

const obj = {
    a: true,
    b: true,
};

const result = ops.omit('a', obj);

console.log(result);
// {
//     b: true,
// }

Array API

insert(startIndex, values, targetArray)

Returns a new array with values inserted at starting at index startIndex to targetArray.

const arr = [1, 2, 4];
const result = ops.insert(2, [3], arr);
console.log(result);
// [1, 2, 3, 4]

push(value, targetArray)

Returns a shallow copy of targetArray with value added to the end. value can be a single value or an array of values to push.

const arr = [1, 2, 3];
const result = ops.push(4, arr);
console.log(result);
// [1, 2, 3, 4]

filter(func, targetArray)

Returns a shallow copy of targetArray with items that func returns true for, when calling it with the item.

const arr = [1, 2, 3, 4];
const result = ops.filter(item => item % 2 === 0, arr);
console.log(result);
// [2, 4]

splice(startIndex, deleteCount, values, targetArray)

Like Array.prototype.splice, but operates on a shallow copy of targetArray and returns the shallow copy.

const arr = [1, 2, 3, 3, 3, 4];
const result = ops.splice(2, 2, [], arr);
console.log(result);
// [1, 2, 3, 4]

API for both Object and Array

set(key, value, target)

Returns a shallow copy of target with its value at index or key key set to value.

const arr = [1, 2, 5];
const result = ops.set(2, 3, arr);
console.log(result);
// [1, 2, 3]

const obj = {
    a: 'X',
    b: 'theB',
};
const resultObj = ops.set('a', 'theA', obj);
console.log(resultObj);
// {
//     a: 'theA',
//     b: 'theB',
// }

Changelog

0.5.0: Major Changes

  • BREAKING: No getImmutableOps function, which was the main export, is exported anymore because options were removed. Now the object containing the operation functions is exported directly.

  • BREAKING: removed option to choose whether operations are curried. Functions are now always curried.

  • BREAKING: former batched mutations API totally replaced.

  • BREAKING: batched mutations implementation changed.

    Previously newly created objects were tagged with a "can mutate" tag, and references to those objects were kept in a list. After the batch was finished, the list was processed by removing the tags from each object in the list.

    Now a batch token is created at the start of a batch (or supplied by the user). Each newly created object is tagged with that token. If a batch using token X operates on an object that is tagged with token X, it is free to mutate it. New batches use a token Y that will never be equal to the previous token.

    Tags are not removed anymore; They are assigned to a non-enumerable property @@_______immutableOpsOwnerID which should avoid any collisions.

    This token strategy is similar to what ImmutableJS uses to track batches.

License

MIT. See LICENSE.