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

modelico-immutable

v19.0.5

Published

Universal library for serialisable immutable models

Downloads

9

Readme

Modélico [moˈðe.li.ko] is a universal-JS library for serialisable immutable models.

Build Status codecov.io Code Climate

Build Status

Note: babel-polyfill might be required for browsers other than Chrome, Firefox and Edge. Additionally, IE 9 & 10 also require a getPrototypeOf polyfill as the one in es5-sham. See browser tests for more details.

Installation

npm i modelico

To use it in the browser, grab the minified or the development files.

Run the current tests directly on your target browsers to see what setup is right for you:

  • Run with standard ES5 setup
  • Run with legacy ES3 setup

Quick intro

The goal is to parse JSON strings like the following into JavaScript custom objects

{
  "name": "Robbie"
}

so that we can do things like this:

const pet1 = M.fromJSON(Animal, petJson);

pet1.speak(); //=> 'my name is Robbie!'

// pet1 will not be mutated
const pet2 = pet1.set('name', 'Bane');

pet2.name(); //=> 'Bane'
pet1.name(); //=> 'Robbie'

M.fromJSON is a simpler way to do the following:

const { _ } = M.metadata;
const pet1 = JSON.parse(petJson, _(Animal).reviver);

Here is how Animal would look like:

const M = require('modelico');
const { string } = M.metadata;

class Animal extends M.Base {
  constructor(fields) {
    super(Animal, fields);
  }

  speak() {
    const name = this.name();

    return (name === '') ?
      `I don't have a name` :
      `My name is ${name}!`;
  }

  static innerTypes() {
    return Object.freeze({
      name: string()
    });
  }
}

A more complex example

The previous example features a standalone class. Let's look at a more involved example that builds on top of that:

{
  "givenName": "Javier",
  "familyName": "Cejudo",
  "pets": [
    {
      "name": "Robbie"
    }
  ]
}

Notice that the data contains a list of pets (Animal).

Again, our goal is to parse JSON into JavaScript classes to be able to do things like

const person1 = M.fromJSON(Person, personJson);

person1.fullName(); //=> 'Javier Cejudo'
person1.pets().inner()[0].speak(); //=> 'my name is Robbie!'

Note: pets() returns a Modelico.List, hence the need to use .inner() to grab the underlying array. See the proxies section for a way to use methods and properties of the inner structure directly.

We are going to need a Person class much like the Animal class we have already defined.

const M = require('modelico');
const { _, string, list } = M.metadata;

class Person extends M.Base {
  constructor(fields) {
    super(Person, fields);
  }

  fullName() {
    return [this.givenName(), this.familyName()].join(' ').trim();
  }

  static innerTypes() {
    return Object.freeze({
      givenName: string(),
      familyName: string(),
      pets: list(_(Animal))
    });
  }
}

A note on immutability

Following the examples above:

const person2 = person1.set('givenName', 'Javi');

// person2 is a clone of person with the givenName
// set to 'Javi', but person is not mutated
person2.fullName(); //=> 'Javi Cejudo'
person1.fullName(); //=> 'Javier Cejudo'

const person3 = person1.setPath(['pets', 0, 'name'], 'Bane');

person3.pets().inner()[0].name(); //=> 'Bane'
person1.pets().inner()[0].name(); //=> 'Robbie'

Optional / null values

In the examples above, a pet with a null or missing name would cause a TypeError while reviving.

const pet = M.fromJSON(Animal, '{"name": null}');
//=> TypeError: no value for key "name"

To support missing properties or null values, you can declare the property as a Maybe:

const M = require('modelico');
const { string, maybe } = M.metadata;

class Animal extends M.Base {

  // ... same as before

  static innerTypes() {
    return Object.freeze({
      name: maybe(string())
    });
  }
}

Then, we can use it as follows:

const pet = M.fromJSON(Animal, '{"name": null}');

pet.name().isEmpty(); //=> true
pet.name().getOrElse('Bane'); //=> Bane

ES2015 proxies

Most built-in types in Modélico (List, Set, Map, EnumMap and Date) are wrappers around native structures. By default, it is necessary to retrieve those structures to access their properties and methods (eg. list.inner().length).

However, if your environment supports ES2015 proxies, Modélico provides utilities to get around this:

const M = Modelico;
const p = M.proxyMap;

const defaultMap = M.Map.fromObject({a: 1, b: 2, c: 3});
const proxiedMap = p(defaultMap);

// without proxies
defaultMap.inner().get('b'); //=> 2
defaultMap.inner().size; //=> 3

// with proxies
proxiedMap.get('b'); //=> 2
proxiedMap.size; //=> 3

Please note that native methods that modify the structure in place will instead return a new modelico object:

const proxiedMap2 = proxiedMap.delete('b');

proxiedMap.size;  //=> 3 (still)
proxiedMap2.size; //=> 2

See proxy tests for more details.

ES5 classes

To support legacy browsers without transpiling, Modélico can be used with ES5-style classes. In the case of our Animal class:

(function(M) {
  var m = M.metadata;

  function Animal(fields) {
    M.Base.factory(Animal, fields, this);
  }

  Animal.prototype = Object.create(M.Base.prototype);
  Animal.prototype.constructor = Animal;

  Animal.prototype.speak = function() {
    var name = this.name();

    return (name === '') ?
      "I don't have a name" :
      'My name is ' + name + '!';
  };

  Animal.innerTypes = function() {
    return Object.freeze({
      name: m.string()
    });
  }
}(window.Modelico));

Acknowledgments :bow:

Inspired by Immutable.js, Gson and initially designed to cover the same use cases as an internal Skiddoo tool by Jaie Wilson.