GithubHelp home page GithubHelp logo

zenika-open-source / immutadot Goto Github PK

View Code? Open in Web Editor NEW
177.0 13.0 5.0 4.69 MB

immutadot is a JavaScript library to deal with nested immutable structures.

Home Page: https://immutadot.zenika.com

License: MIT License

JavaScript 91.15% TypeScript 8.85%
javascript immutable dot-notation react redux lodash nested-structures open-source

immutadot's Introduction

immutadot logo

A JavaScript library to deal with nested immutable structures.

set({ english: { greeting: 'Hi' } }, 'english.greeting', 'Hello')
// → { english: { greeting: 'Hello' } }

push({ i18n: { languages: ['English', 'French'] } }, 'i18n.languages', 'German', 'Spanish')
// → { i18n: { languages: ['English', 'French', 'German', 'Spanish'] } }

immutadot gives you a short and meaningful syntax to apply operations on immutable structures.

npm version Try on RunKit Documentation

CircleCI codecov gitmoji-changelog

🚧 I'm currently rewriting immutadot

Hi 👋, I'm currently rewriting entirely immutadot, and hopefully I will publish a v3 very soon (v2 never reached a satisfying state).

Here is a pick at the new API which uses tagged template literals:

import { set } from 'immutadot'

const animals = {
  weasels: {
    lutraLutra: {
      commonNames: ['eurasian otter'],
    },
  },
}

const newAnimals = set`${animals}.weasels.lutraLutra.name'('Lutrinae')

Check out the v3 branch if you're interested.

Installation

immutadot is available on npm repository.

using yarn:

$ yarn add immutadot

using npm:

$ npm install immutadot

Usage

ES modules:

import { set } from 'immutadot'

CommonJS:

const { set } = require('immutadot')

Example

Quickly set nested properties using set()

import { set } from 'immutadot'

const animals = {
  weasels: {
    lutraLutra: {
      commonNames: ['eurasian otter'],
    },
  },
}

const newAnimals = set(animals, 'weasels.lutraLutra.name', 'Lutrinae')

Learn more about what immutadot can do in the Getting started.

Feel free to try immutadot on runkit.

Documentation

Getting started

A fast overview of immutadot's features is available in the Getting started guide.

API

The detailed API documentations of the different packages are available here:

Looking for older versions API documentation? Links are available here.

Performances

A simple benchmark (freely inspired from one made by mweststrate for immer) reveals that immutadot shows good results compared to other libraries.

⚠️ The following results should be taken with caution, they may vary depending on the hardware, the JavaScript engine, and the kind of operations performed. This particular test updates 10% out of a list of todos items, and was ran with Node 9.8.0 on an Intel® Core™ i7-6560U CPU @ 2.20GHz.

Update small todos list (1000 items):
  ES2015 destructuring: ~17775ops/s (0.06ms/op) on 50000ops
  immutable 3.8.2 (w/o conversion to plain JS objects): ~6737ops/s (0.15ms/op) on 50000ops
  immutable 3.8.2 (w/ conversion to plain JS objects): ~109ops/s (9.17ms/op) on 3274ops
  immer 1.2.0 (proxy implementation w/o autofreeze): ~1132ops/s (0.88ms/op) on 34025ops
  immer 1.2.0 (ES5 implementation w/o autofreeze): ~521ops/s (1.92ms/op) on 15680ops
  qim 0.0.52: ~12042ops/s (0.08ms/op) on 50000ops
  immutadot 1.0.0: ~2351ops/s (0.43ms/op) on 50000ops
Update medium todos list (10000 items):
  ES2015 destructuring: ~1801ops/s (0.56ms/op) on 5000ops
  immutable 3.8.2 (w/o conversion to plain JS objects): ~630ops/s (1.59ms/op) on 5000ops
  immutable 3.8.2 (w/ conversion to plain JS objects): ~10ops/s (95.70ms/op) on 314ops
  immer 1.2.0 (proxy implementation w/o autofreeze): ~111ops/s (9.04ms/op) on 3319ops
  immer 1.2.0 (ES5 implementation w/o autofreeze): ~51ops/s (19.76ms/op) on 1519ops
  qim 0.0.52: ~1257ops/s (0.80ms/op) on 5000ops
  immutadot 1.0.0: ~234ops/s (4.28ms/op) on 5000ops
Update large todos list (100000 items):
  ES2015 destructuring: ~120ops/s (8.34ms/op) on 500ops
  immutable 3.8.2 (w/o conversion to plain JS objects): ~58ops/s (17.28ms/op) on 500ops
  immutable 3.8.2 (w/ conversion to plain JS objects): ~1ops/s (998.81ms/op) on 31ops
  immer 1.2.0 (proxy implementation w/o autofreeze): ~21ops/s (48.68ms/op) on 500ops
  immer 1.2.0 (ES5 implementation w/o autofreeze): ~4ops/s (264.16ms/op) on 114ops
  qim 0.0.52: ~91ops/s (11.01ms/op) on 500ops
  immutadot 1.0.0: ~21ops/s (48.22ms/op) on 500ops

Immutability

In the last few years one of our biggest challenge has been to find an efficient way to detect changes in our data to determine when to re-render our interfaces.

An immutable object is an object that cannot be changed once created. It brings several benefits1:

  • Data changes detection made simple (Shallow comparison)
  • Memoization
  • Improve rendering performances
  • Explicit data changes
  • Avoid side effects

Our approach

Concise

ES2015+ new features are great to deal with arrays and objects. As data structures expand, the code you write to make data immutable gets bigger and less readable. immutadot uses the dot notation to address this issue.

Interoperability

immutadot uses plain JavaScript objects so you can access your data using standard ways. Moreover, it lets you freely enjoy your favorite libraries.

Exhaustive and yet extensible

immutadot comes with a large set of built-in utilities, mostly based on ES2015+. You can also find a package called immutadot-lodash with some of lodash's utilities. You haven't found what you're looking for? Do it yourself with the convert feature.

Learning curve

If you are already familiar with ES2015+ and lodash then you should be able to use immutadot quickly.

Contributing

We want contributing to immutadot to be fun, enjoyable, and educational for anyone, and everyone.

In the interest of fostering an open and welcoming environment, we have adopted a Code of Conduct that we expect project participants to commit to. Please read the full text so that you can understand what behavior will and will not be tolerated.

If you are interested in contributing to immutadot, please read our contributing guide to learn more about how to suggest bugfixes and improvements.

License

immutadot is MIT licensed.

Notes

immutadot's People

Contributors

charlyx avatar clementvanpeuter avatar denis-sokolov avatar dependabot[bot] avatar emrysmyrddin avatar frinyvonnick avatar greenkeeper[bot] avatar hgwood avatar jlandure avatar nlepage avatar pgu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

immutadot's Issues

Unnecessary object copies

Description

lodash/fp/update sends a shallow clone of the updated value to the updater :

  • It's not necessary to use lodash/fp version of functions mutating at first level only
  • It's not mandatory to pass arrays through lodash/toArray before using mutating Array methods.

Add chain

Add chain allowing to do :

chain(object)
  .set('nested.prop1', 'value')
  .update('nested.prop2', value => value + 1)
  .value()

Add Array functions

  • push based on Array.prototype.push
  • splice based on Array.prototype.splice
  • unshift based on Array.prototype.unshift

Automate immutad●t conversion of lodash/fp functions

Functions such as mapValues, pickBy, etc. are always converted with the exact same code.
It could be interesting to "automate" the conversion of such functions.
A prerequisite for the solution would be to keep the documentation as it is right now.

Write an apply function

Description

Write an apply function allowing to browse a path generated by toPath and apply an operation at the end of the path...

using('<path>').update(...) crashes with "TypeError: updater.apply is not a function"

Description

using('<path>').update(...) crashes with TypeError: updater.apply is not a function

Version : 0.2.0

Expected behavior

Call updater with the '<path>' argument.

Actual behavior

'<path>' argument is used as the updater.

How to reproduce

    const input = {
      pages: [1, 2, 3],
      selectedPages: [4, 5, 6],
    }
    const output = using('pages').update(input, 'selectedPages', (selectedPages, pages) => pages.map(page => page * 2))
    expect(output).toEqual({
      pages: [1, 2, 3],
      selectedPages: [2, 4, 6],
    })

Documentation improvements

Description

Aliases

JsDoc doesn't allow the creation of synonyms for a function.
JsDoc's @alias has been designed to "to treat all references to a member as if the member had a different name", but not rhe creation of synonyms.
lodash uses docdown, which has been tweaked to process @alias as a list of synonyms.

Runnable examples

All examples of the documentation should be runnable using runkit.
This is the case in lodash's documentation, I don't know how it is done.

External references

Have the ability to reference a function regardless of the version.
For now the functions are organized in namespaces, and the namespace is part of the path in the URL :
https://zenika.github.io/immutadot/immutadot/0.2/array.html#concat
This forces to put also the version in the URL.
The namespace would be better placed in the hash of the URL, allowing shorter links :
https://zenika.github.io/immutadot#array.concat
Which would redirect to the latest version of the documentation.
Lodash uses @category instead of @namespace to do this.

TODO

  • Have a look at doclets.io which seems to fulfill at least the Runnable examples and External references...

Tests : Imports from namespace

In order to avoid bugs such as #91, import tested functions from namespace instead of a direct import.

import { capitalize } from 'string'

instead of

import { capitalize } from './capitalize'

Produce bundles

Use rollup ? Or webpack ?

When a new tag is created :

  • Build a dist and commit it in dist repository
  • Attach it to the associated github release ?
  • Deliver the files with https://www.jsdelivr.com/

seq.chain : Second parameter path

Add a second parameter path to reduce the scope of the chained operations :

const o = { nested: { a: 2, b: 3 } }
chain(o, 'nested')
  .add('a', 1)
  .substract('b', 1)
  .value() // => { nested: { a: 3, b: 2 } }

Add a README.md

  • Description and philosophy (fix in #38)
  • Usage
  • Documentation
  • Contributing
  • Troubleshooting
  • Credits

Add lodash functions

  • Collection :

    • filter
    • orderBy
    • reject
    • shuffle
    • sortBy
  • Math :

    • divide
    • multiply
    • substract
  • Object :

    • defaults
    • mapKeys
    • merge
    • omit
    • omitBy
    • pick
  • String :

    • capizalize
    • replace
    • toLower
    • toUpper

Add core.using

Add core.using, allowing to pass more arguments from the source object to update and derived functions.
⚠️ using will not have unset core function.
⚠️ using().update() will not have the "function only" version of update.
ℹ️ First version will not be integrated with chain, see #15 for integration.

Example 1 with update :

const o = { nested: { a: 2, b: 3 } }
using('nested.b')
  .update(o, 'nested.a', (a, b) => a * b) // => { nested: { a: 6, b: 3 } }

Example 2 with add :

const o = { nested: { a: 2, b: 3 } }
using('nested.b')
  .add(o, 'nested.a') // => { nested: { a: 5, b: 3 } }

Example 3 with update and several arguments :

const o = { nested: { a: 2, b: 3, c: 4 } }
using('nested.b', 'nested.c')
  .update(o, 'nested.a', (a, b, c) => a * b + c) // => { nested: { a: 10, b: 3, c: 4 } }

Example 4 with set :

const o = { nested: { a: 2 } }
using('nested.a')
  .set(o, 'nested.b') // => { nested: { a: 2, b: 2 } }

Add map

Modify every element of the value like map

const result = map(object, 'props.values', val => val * val)

Manage lock files more smoothly

Prerequisites

Description

We are currently saving both npm 5 and yarn lock files in github.
For sure it is not realistic to ask of contributors to think of updating both these files when adding or upgrading dependencies.
There is currently a discussion at yarnpkg/yarn#3614 about supporting npm 5 package-lock.json, which would be nice...

In the mean time, it seems there is no elegant way of dealing with this.
We could choose to keep only one of the lock files, but we can not make sure that contributors will use npm over yarn or yarn over npm.

Currently npm seems to suffer a minor drawback in comparison to yarn, related to unwanted updates to package-lock.json when switching between Linux (or Windows !) and Mac npm/npm#17722, in particular with fsevents which is a Mac specific dependency.
@frinyvonnick Have you used npm 5 and experienced unwanted package-lock.json updates on the project ?

Integrate util.using and seq.chain

Allow the use of using within a chain sequence :

const o = { nested: { a: 2, b: 3 } }
chain(o)
  .using('nested.b').add('nested.a')
  .using('nested.a').multiply('nested.b')
  .value() // => { nested: { a: 5, b: 6 } }

Allow to do it with the second chain parameter 🚀 :

const o = { nested: { a: 2, b: 3 } }
chain(o, 'nested')
  .using('b').add('a')
  .using('a').multiply('b')
  .value() // => { nested: { a: 5, b: 6 } }

Add array traversing in path notation

Prerequisites

Description

I cannot specify array properties in path.

Only for feature requests ✨

Example

// An example of code using the feature
const categories = [{
  sections: [{
    attributes: [{
      name: 'name',
      value: 'value'
    }]
  }]
}]

const filteredCategories = filter(
  categories,
  'sections.attributes',
  ({ value }) => isEmpty(value)
)

Avoid inline export default

Description

Avoid inline export default

Example

// Do
const myFunc = ...
export default myFunc
// Instead of
export default ...

Add a documentation

Add a documentation generated using jsdoc.

  • Add and configure jsdoc
  • Document existing code
  • Generate and commit on tag
  • Publish documentation on github.io
  • Add a page linking documentation versions (README.md in docs/)

Add object.assign

const o = { nested: { a: 1, b: 2 } }
assign(o, 'nested', { b: 3, c: 4 }) // => { nested: { a:1, b: 3, c: 4 } }

Add mapValues function

Add the lodash equivalent of mapValues :

const obj = {nested: { a: 1, b: 2, c: 3 } }
const obj2 = mapValues(obj, "nested", (value, key, o) => value * value)
// obj2 = {nested : { a: 1, b: 4, c: 9 } }

Split in several modules

  • Put the lodash functions in a different module in the form of a plugin (in a different repository) #138
  • Have an immutadot core with no peer dependency on lodash #143
  • Add more plain JS functions to immutadot core (Array.push, Object.assign, etc.) #165
  • Update documentation to reflect the new organization -> #183

Add push

Add a method to push into an array

newObject = push(object, 'props.array', value)

Add other lodash Array functions

Description

  • differenceBy
  • differenceWith
  • dropRight
  • dropRightWhile
  • dropWhile
  • fill
  • intersection
  • intersectionBy
  • intersectionWith
  • pullAll
  • pullAllBy
  • pullAllWith
  • pullAt
  • reverse
  • slice
  • take
  • takeRight
  • takeRightWhile
  • takeWhile
  • union
  • unionBy
  • without -> pull
  • xor
  • xorBy
  • xorWith

typo in string namespace for capitalize function

Prerequisites

Description

There is a typo in index.js file in string namespace. There is a z instead of a t in method name.

import { capizalize } from './capizalize'
import { replace } from './replace'
import { toLower } from './toLower'
import { toUpper } from './toUpper'

/**
 * String functions.
 * @namespace string
 * @since 0.3.0
 */
export {
  capizalize,
  replace,
  toLower,
  toUpper,
}

Only for bugs 🐛

Version : 3.0.0

Add object.pickBy

Add the lodash pickBy function like this :

const objectOne = {
  nested: {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
  },
}

const result = pickBy(objectOne, 'nested', v => v < 3)
// => result = {
  nested: {
    a: 1,
    b: 2
  },
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.