GithubHelp home page GithubHelp logo

kube / monolite Goto Github PK

View Code? Open in Web Editor NEW
146.0 7.0 6.0 989 KB

Statically-typed structural-sharing tree modifier

Home Page: https://www.npmjs.com/package/monolite

TypeScript 88.90% JavaScript 11.10%
immutable typescript persistent-data-structure structural-sharing

monolite's Introduction

https://github.com/kube/monolite/actions?query=workflow%3ABuild+branch%3Amaster

Type-safe structural-sharing tree modifier.

Install

yarn add monolite

TypeScript

The main motivation of this library is to preserve static-typing, type-inference and completion provided by TypeScript, which is not supported by Immutable.js when working on trees.

Monolite takes Plain-Old JavaScript Objects and returns Plain-Old JavaScript Objects. Accessors permit TypeScript to understand the types you're dealing with and to provide completion and linting as if you were working directly on these objects.

monolite's People

Contributors

chrisbouchard avatar corseman avatar kube 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

monolite's Issues

Possibly null values in the input data?

I'm using strictNullChecks in my project. Updating an object with missing values will result in a runtime exception like TypeError: Cannot read property 'bar' of undefined.

Here's an example. Am I doing something wrong here?

import * as monolite from "monolite";

interface IState {
    foo?: {
        bar?: {
            baz?: number;
        };
    };
}

const state: IState = {};

const newState = monolite.set(state, s => {
    const foo = s.foo;
    if (foo) {
        const bar = foo.bar;
        if (bar) {
            return bar.baz;
        }
    }
    return undefined;
})(345);

console.log(newState);

Convert Accessor Function to Accessors Chain statically during build.

Performance and compatibility optimizations

Provide a tool to convert Accessor Function to Accessors Chain statically, by transforming the AST during Production build process.

This tool will integrate somewhere in the build process, like directly in TypeScript / Babel, or better in Webpack / Browserify / Gulp.

Performance

By default Monolite converts Accessors Functions to Accessors Chains at runtime, by calling getAccessorChain.

set(root, _ => _.a.b.c)(42)

Forwards call to:

setFromAccessorChain(root, ['a', 'b', 'c'])(42)

This can be easily resolved statically.

Compatibility

Production code will avoid use of Proxy, which will permit to target ES5.

Possibility for IE11 support?

Since Proxy isn't available in IE11, wondered if there's a transpilation addon for babel or recommended polyfill, or if this library could benefit from using feature detection to provide an alternative to the Proxy used in accessorChain.ts. If you're open to a pull request, or have any ideas, let me know!

Arrays in accessors chain get transformed into Objects

When accessor chain contains an Array, as in:

const state = {
  title: 'Hello',
  exercises: [
    { name: 'Bonjour' },
    { name: 'Hello' },
    { name: 'Hola' }
  ]
}

const newState = set(state, _ => _.exercises[0].name)('Salut')

Here newState.exercises will be transformed in an Object:

const state = {
  title: 'Hello',
  exercises: {
    '0': { name: 'Salut' },
    '1': { name: 'Hello' },
    '2': { name: 'Hola' }
  }
}

This causes problem when using Array methods:

// Will throw an error as exercises does not have map method anymore
state.exercises.map(exercise => ... )

Can use an array as root object?

Hi, great little library. I wasn't sure if it was intended to work with an array as the root object, but it seems to be ok in my project.

I also added this to set tests and it passed:

  it('can use array as root', () => {
    const tree = [
      { name: 'John', age: 26 },
      { name: 'Jenny', age: 33 },
      { name: 'Marvin', age: 42 }
    ]

    const updatedTree = set(tree, _ => _[0].name)('Bobby')
    expect(updatedTree).to.be.an('array')
    expect(updatedTree.length).to.equal(3)
    expect(updatedTree[0].name).to.equal('Bobby')
    expect(updatedTree[1].name).to.equal('Jenny')
    expect(updatedTree[2].name).to.equal('Marvin')

    const updatedTree2 = set(tree, _ => _[2].age)(55)
    expect(updatedTree2).to.be.an('array')
    expect(updatedTree2.length).to.equal(3)
    expect(updatedTree2[0].age).to.equal(26)
    expect(updatedTree2[1].age).to.equal(33)
    expect(updatedTree2[2].age).to.equal(55)
  })

Would there be any gotchas to watch out for?

Allow chained calls

Rather than having to do

const newState = set(set(oldState, _ => _.foo)('bar'), _ => _.bob)('Alice')

or

let newState = set(oldState, _ => _.foo)('bar')
newState = set(newState, _ => _.bob)('Alice')

it would be cool if calls could be chained like

const newState = S(oldState)
                             .set(_ => _.foo)('bar')
                             .set(_ => _.bob)('Alice')

or better yet

const newState = S(oldState)
                             .set(_ => _.foo, 'bar')
                             .set(_ => _.bob, 'Alice')

Permit function as value replacement parameter.

Accept a function as new value in set, that will permit to use it like this:

set(tree, _ => _.child.target)(oldValue => newValue)

It would permit to push in an Immutable List for example:

set(tree, _ => _.child.immutableList)(list => list.push(item))

Or with a transformation function:

const square = x => x * x

set(tree, _ => _.child.numberValue)(square)

Add support for collection mapping

Currently mapping a collection using set is quite verbose and ugly:

// Current
return set(
  response,
  _ => _.results,
  xs =>
    xs.map(result =>
      set(
        result,
        _ => _.poster_path,
        path => 'http://image.tmdb.org/t/p/w185' + path
      )
    )
)

Proposal

set should accept multiple accessor function before the value transformer.
The first ones should only target arrays.
This would result in a much cleaner syntax:

// With map support
return set(
  response,
  _ => _.results,
  _ => _.poster_path,
  path => 'http://image.tmdb.org/t/p/w185' + path
)

Type inference fails with optional state parameters

We're using monolite to customize parts of our mock state trees for particular test suites, and I'm trying to upgrade from 0.4.6 to 0.8.0. I notice the syntax for set has changed to take the new state as a third argument. No problem, fixed that up. I'm having trouble with the second argument (the accessor function). When the portion of state being modified is an optional parameter of its interface, I get this error:

Argument of type '(_: StateTree) => string | undefined' is not assignable to parameter of type 'Accessor<StateTree, {}>'.
  Type '(_: StateTree) => string | undefined' is not assignable to type 'AccessorFunction<StateTree, {}>'.
    Type 'string | undefined' is not assignable to type '{}'.
      Type 'undefined' is not assignable to type '{}'. [2345]

It seems that the AccessorFunction's second generic type is not being properly inferred when it is optional (string | undefined in this case).

Here's some sample code that reproduces the above error:

interface StateTree {
  readonly myBranch: StateTreeBranch;
}

interface StateTreeBranch {
  readonly optionalState?: string;
}

const mockStateTree: StateTree = {
  myBranch: {
    optionalState: 'original_state',
  }
};

const updatedStateTree = monolite.set(
  mockStateTree,
  _ => _.myBranch.optionalState,
  'some_new_state',
);

This is literally the first issue I've ever opened on GitHub, so let me know what I'm doing wrong, what else you need, etc.

Add merge function

merge(
  { a: { hello: "world", x: 4 }, b: 42 },
  { a: { x: 42, y: 1337 } }
)
// { a: { hello: "world", x: 42, y: 1337 }, b: 42 },

Monolite with react, redux and array

Hello.
When I make set () for an array only, I get a state change. Redux gets a new state, but there is no mapStateToProps and re-render component respectively.
When set () for a regular field (not an array / object) everything works correctly.

How to get around this? Maybe someone had this problem.

In my case, it is necessary to change many objects in an array, so for each object the index search and the set () call are many times quite low-performing.

Thank you.

Missing null guard in deepEqual

objectEqual throws when passed a null. That, in turn, can happen because, unfortunately, typeof null === 'object', so none of the preceding checks in deepEqual are able to filter it out. A typeof wrapper could probably suffice (returning 'null' for x === null, and typeof x otherwise).

Use single Proxy to determine chain of accessors.

Chain of accessors is determined through an unnecessary recursive use of proxies, that cost performance because of multiple Proxy objects creation.

To optimize performance, use a single Proxy that will return itself recursively, and fill accessors chain.

Babel plugin: nested set not transformed

Nested set expressions are not transformed by the Babel Plugin:

set(
  state,
  _ => _.nested.collection,
  arr => arr.map(item =>
    set(item, _ => _.nested.prop, x => x + 1)
  )
)

After compilation it results in:

set(
  state,
  ['nested', 'collection'], // TRANSFORMED
  arr => arr.map(item =>
    set(item, _ => _.nested.prop, x => x + 1) // UNTOUCHED
  )
)

node/electron target

hi friend, the latest build is targeting web and references window causing it to throw a window is undefined when using in electron / node

Add a `setMap` function

Add setMap function that would permit to chain a set and an Array map.

A code like this

set(scale, _ => _.sections)(sections =>
  sections.map(section =>
    set(section, _ => _.questions)(questions =>
      questions.map(question =>
        set(question, _ => _.skills[skill])(
          ((question.skills[skill] || 0) * coefficient) | 0
        )
      )
    )
  )
)

Would become

setMap(scale, _ => _.sections)(section =>
  setMap(section, _ => _.questions)(question =>
    set(question, _ => _.skills[skill])(
      ((question.skills[skill] || 0) * coefficient) | 0
    )
  )
)

add support for ES6 Map's

E.g.:

const m = new Map([['a', 'b'], ['c', 'd']]); 
// Map { 'a' => 'b', 'c' => 'd' }
const m2 = set(m, _ => _.get('c') )('X')
// Map { 'a' => 'b', 'c' => 'X' }

Bundle Babel Plugin in Monolite

Bundle it with Monolite will prevent installing another dependency, and simply do:

// babel.config.js
module.exports = api => {
  return {
    presets: [
      // ..
    ],
    plugins: [
      'monolite/babel',
    ]
  }
}

This will also permit to synchronize the plugin and the library development.

Flow types not type validating correctly

Hello, I am trying to use this library with Flow. I am finding that the flow types included in this library are not validating the way I would expect. See the following examples:

// @flow
import { set } from 'monolite';

type State = {
    id: number,
    username: string,
};

const state: State = {
    id: 1,
    username: 'foo',
};

const stateWithStringId = set(state, s => s.id)('hello');

const stateWithNumberUsername = set(state, s => s.username)(1);

const stateWithNewKey = set(state, s => s.whatever)('hello');

I would expect an error on each of these usages of set. When I run flow, however, I see no errors.

➜  monolite-spike node_modules/flow-bin/cli.js version
Flow, a static type checker for JavaScript, version 0.51.1
➜  monolite-spike node_modules/flow-bin/cli.js index.js        
No errors!

Has anyone had success using monolite and flow together? What version of flow does flowgen use? Maybe some more recent flow language features have fixed this.

ReferenceError: window is not defined in node_modules/monolite/lib/monolite/index.js:1

I just installed the latest version of monolite, but when I import it into a script, I get the following error:

ReferenceError: window is not defined

/.../node_modules/monolite/lib/monolite/index.js:1

If I edit index.js and replace the one instance of window with {}, then everything runs as expected. Any idea why the superfluous window reference is in index.js?

Cheers!

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.