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

@yanick/updeep-remeda

v2.3.1

Published

Easily update nested frozen objects and arrays in a declarative and immutable manner.

Downloads

19

Readme

updeep-remeda

Easily update nested frozen objects and arrays in a declarative and immutable manner.

About

💡 This is a fork of the main updeep package. For ease of reading — not to mention ease of shamelessly lifting large pieces of the original documentation — in this documentation all mentions of updeep refers to this fork.

updeep makes updating deeply nested objects/arrays painless by allowing you to declare the updates you would like to make and it will take care of the rest. It will recursively return the same instance if no changes have been made, making it ideal for using reference equality checks to detect changes.

Because of this, everything returned by updeep is frozen. Not only that, but updeep assumes that every object passed in to update is immutable, so it may freeze objects passed in as well. Note that the freezing only happens in development.

This fork of updeep requires Remeda, but works very well with any other utility function ([lodash], [Ramda], etc).

Differences with the original Updeep

  • Under the hood, the use of lodash has been replaced by Remeda (for better type support and tree-shaking abilities).

  • The codebase has been ported to TypeScript (mostly for the lulz).

  • The order of parameters in the non-curryied invocation of functions has been modified. In the original updeep the input object is the last parameter, whereas here it's the first.

// original updeep
const dataIn = { a: 1, b: 2 };

let dataOut = u({ c: 3 }, dataIn); // simple call
dataOut = u({ c: 3 })(dataIn); // curried

// updeep-remeda
dataOut = u(dataIn, { c: 3 }); // simple call
dataOut = u({ c: 3 })(dataIn); // curried
  • withDefault has been removed as the behavior can be implemented using Remeda's pipe, or a simple ??.

  • u.omitted has been renamed u.skip.

Installation

$ npm install @yanick/updeep-remeda
# or
$ pnpm install @yanick/updeep-remeda

Full example

import u from "@yanick/updeep-remeda";

const person = {
  name: { first: "Bill", last: "Sagat" },
  children: [
    { name: "Mary-Kate", age: 7 },
    { name: "Ashley", age: 7 },
  ],
  todo: ["Be funny", "Manage household"],
  email: "[email protected]",
  version: 1,
};

const inc = (i) => i + 1;

const eq = (x) => (y) => x === y;

const newPerson = u(person, {
  // Change first name
  name: { first: "Bob" },
  // Increment all children's ages
  children: u.map({ age: inc }),
  // Update email
  email: "[email protected]",
  // Remove todo
  todo: u.reject(eq("Be funny")),
  // Increment version
  version: inc,
});
// => {
//  name: { first: 'Bob', last: 'Sagat' },
//  children: [
//    { name: 'Mary-Kate', age: 8 },
//    { name: 'Ashley', age: 8 }
//  ],
//  todo: [
//    'Manage household'
//  ],
//  email: '[email protected]',
//  version: 2
//}

API

💡 All functions are curried, Remeda-style, so if you see f(dataIn, ...others), it can be called with either f(dataIn, ...others) or f(...others)(dataIn).

Importing

updeep-remeda exports a default function that is an alias to u.update and has all the other functions available as props.

import u from '@yanick/updeep-remeda';

const foo = u({a:1}, { a: x => x + 1 });

const bar = u.updateIn({ a: { b: 2 } }, 'a.b', 3 );

Or you can import the functions piecemeal:

import { updateIn, omit } from '@yanick/updeep-remeda';

u(dataIn, updates)

u.update(dataIn, updates)

Update as many values as you want, as deeply as you want. The updates parameter can either be an object, a function, or a value. Everything returned from u is frozen recursively.

If updates is an object, for each key/value, it will apply the updates specified in the value to object[key].

If updates is a function, it will call the function with object and return the value.

If updates is a value, it will return that value.

Sometimes, you may want to set an entire object to a property, or a function. In that case, you'll need to use a function to return that value, otherwise it would be interpreted as an update. Ex. function() { return { a: 0 }; }.

Also available at u.update(...).

Simple update

Object properties:

const person = {
  name: {
    first: "Jane",
    last: "West",
  },
};

const result = u(person, { name: { first: "Susan" } });

expect(result).to.eql({ name: { first: "Susan", last: "West" } });

Array elements:

const scoreboard = {
  scores: [12, 28],
};

const result = u(scoreboard, { scores: { 1: 36 } });

expect(result).to.eql({ scores: [12, 36] });

Multiple updates

const person = {
  name: {
    first: "Mike",
    last: "Smith",
  },
  scores: [12, 28],
};

const result = u(person, { name: { last: "Jones" }, scores: { 1: 36 } });

expect(result).to.eql({
  name: { first: "Mike", last: "Jones" },
  scores: [12, 36],
});

Use a function

const increment = (i) => i + 1;

var scoreboard = {
  scores: {
    team1: 0,
    team2: 0,
  },
};

const result = u(scoreboard, { scores: { team2: increment } });

expect(result).to.eql({ scores: { team1: 0, team2: 1 } });

Array Manipulation

Non-trivial array manipulations, such as element removal/insertion/sorting, can be implemented with functions. Because there are so many possible manipulations, we don't provide any helpers and leave this up to you. Simply ensure your function is pure and does not mutate its arguments.

function addTodo(todos) {
  return [].concat(todos, [{ done: false }]);
}

const state = {
  todos: [{ done: false }, { done: false }],
};

const result = u({ todos: addTodo }, state);

expect(result).to.eql({
  todos: [{ done: false }, { done: false }, { done: false }],
});

Remeda is one of the many libraries providing good utility functions for such manipulations.

import { reject, concat, prop } from "remeda";

let state = {
  todos: [{ done: true }, { done: false }],
};

// add a new todo
state = u(state, { todos: concat({ done: false }) });
expect(state).to.eql({
  todos: [{ done: true }, { done: false }, { done: false }],
});

// remove all done todos
state = u(state, { todos: reject(prop("done")) });
expect(state).to.eql({ todos: [{ done: false }, { done: false }] });

Default input data

When the input data is null or undefined, updeep uses a empty plain object.

const result = u(null, { foo: "bar" });
expect(result).to.eql({ foo: "bar" });

Partial application

const inc = (i) => i + 1;

const addOneYear = u({ age: increment });
const result = addOneYear({ name: "Shannon Barnes", age: 62 });

expect(result).to.eql({ name: "Shannon Barnes", age: 63 });

u.freeze(dataIn)

Freeze your initial state to protect against mutations. Only performs the freezing in development, and returns the original object unchanged in production.

const state = u.freeze({ someKey: "Some Value" });
state.someKey = "Mutate"; // ERROR in development

u.updateIn(dataIn, path, value)

Update a single value with a simple string or array path. Can be use to update nested objects, arrays, or a combination. Can also be used to update every element of a nested array with '*'.

const result = u.updateIn(
  { bunny: { color: "black" } },
  "bunny.color",
  "brown"
);

expect(result).to.eql({ bunny: { color: "brown" } });
const result = u.updateIn(
  "0.1.color",
  "brown"
)([[{ color: "blue" }, { color: "red" }], []]);

expect(result).to.eql([[{ color: "blue" }, { color: "brown" }], []]);
const incr = (i) => i + 1;

const result = u.updateIn("bunny.age", incr)({ bunny: { age: 2 } });

expect(result).to.eql({ bunny: { age: 3 } });
const result = u(
  { pets: [{ bunny: { age: 2 } }] }
  { pets: u.updateIn([0, "bunny", "age"], 3) },
);

expect(result).to.eql({ pets: [{ bunny: { age: 3 } }] });
const result = u.updateIn(
  "todos.*.done",
  true
)({
  todos: [{ done: false }, { done: false }],
});

expect(result).to.eql({
  todos: [{ done: true }, { done: true }],
});

u.constant(dataIn)

Sometimes, you want to replace an object outright rather than merging it. You'll need to use a function that returns the new object. u.constant creates that function for you.

const user = {
  name: "Mitch",
  favorites: {
    band: "Nirvana",
    movie: "The Matrix",
  },
};

const newFavorites = {
  band: "Coldplay",
};

const result = u(user, { favorites: u.constant(newFavorites) });

expect(result).to.eql({ name: "Mitch", favorites: { band: "Coldplay" } });
const alwaysFour = u.constant(4);
expect(alwaysFour(32)).to.eql(4);

u.if(dataIn, predicate, updates)

Apply updates if predicate evaluates to true. The predicate can be a boolean, or a function taking in dataIn and returning a boolean, or an object, in which case it'll be treated as a shortcut for u.matches(predicate).

function isEven(x) {
  return x % 2 === 0;
}
function increment(x) {
  return x + 1;
}

const result = u({ value: 2 }, { value: u.if(isEven, increment) });

expect(result).to.eql({ value: 3 });

u.filter(arrayIn, predicate)

u.reject(arrayIn, predicate)

u.pickBy(objectIn, predicate)

u.omitBy(objectIn, predicate)

u.pick(objectIn, keys)

u.omit(objectIn, keys)

Essentially the same as their Remeda counterparts. The difference being that if the transformation results in no change, the original object/array is returned.

u.map(objectIn, updates)

Applies the updates on all entries of objectIn.

u.mapIf(objectIn, predicate, updates)

Shorthand for u.map( objectIn, u.if(predicate,updates) ).

u.mapIfElse(objectIn, predicate, updates, updatesElse)

Shorthand for u.map( objectIn, u.ifElse(predicate,updates,updatesElse) ).

u.matches(dataIn, condition)

Do a deep comparison with condition, and returns true if the dataIn object matches.

Scalar values are verified for equality (i.e., {foo: 12} will verify that the object has the prop foo set to 12), and functions are going to be invoked with the object value of the object and expected to return true upon matching.

u.matches(
  { name: "Bob", age: 32, address: "..." },
  {
    name: "Bob",
    age: (age) => age > 30,
  }
); // true