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

normi

v0.6.1

Published

A zero-config normalized cache for MobX

Downloads

19

Readme

Motivation

One of the best things about MobX is that it lets you create and use observable objects without worrying about normalization. If you get a big blob of nested JSON back from an API, you can wrap it up as an observable and pass it into your React components. Badda bing, badda boom.

So why build a normalized cache on top of MobX? Because it's literally magic.

If you normalize all data coming into your application, there is a single copy of each "object" in your application. What do I mean by "object"? To understand that, let's look at an example.

Simple example

// First let's create a Normi instance
const normi = new Normi();

// Now let's pass some data into it!
normi.merge({ id: '123', name: 'Zendaya' });
// => { id: "123", name: "Zendaya" }

Makes sense! Normi is built with TypeScript so all type information is preserved. Now let's pass in a more detailed version of Zendaya.

normi.merge({
  id: '123',
  bestSong: 'Replay',
  bestMovie: 'Spiderman: Homecoming',
});
/* { 
  id: '123', 
  name: "Zendaya", 
  bestSong: "Replay", 
  bestMovie: "Spiderman: Homecoming" 
}
*/

As can see, Normi has merged the two objects, since their id properties were equivalent.

Example

let's look at an example. you're building a blog. your homepage fetches a list of posts and renders them as a list of links.

const posts = await getPosts();
// { id: string; title: string; }[]

Once a user clicks on one of the links, you fetch a "detail view" of the post:

const post = await getPost(id);
// { id: string; title: string; content: string }

As you can see the "detail view" contains the content field in addition to the fields from before.

Without normalization, you'd now have two JavaScript objects in memory that correspond to the same blog post. It's now possible for your representation of those blog posts to get out of sync. If you updated the title of a post, you would have to re-fetch both getPost() and getPosts() to ensure that all the data in your application is up-to-date.

For complex applications, this quickly gets unsustainable. You need to know exactly which objects get fetched by which APIs and trigger a set of "refetches" every time you update something in your database. This problem is compounded by the single-page application paradigm and client-side routing - data fetched at the beginning of a long session will stick around for a long time, because there is no need for a full-page refresh.

Normi is designed to be the easiest possible way to get the benefits of denormalization with none of the usual hassles. There's no need to define

unique identifiers

The hard part is knowing when two objects correspond to the same node. By default, Normi only has two criteria for a node:

  1. Must be a plain JavaScript object (not an array or instance)
  2. Must contain an id property

This works great if you're using UUIDs to uniquely identify every object in your database. If you're using auto-incrementing integers (SERIAL in Postgres) then this default configuration may not work. You'll run into problems where two objects from different tables have the same ID.

Custom ID key

If you use a different key to store object identifers, you can use that instead:

const normi = new Normi({ id: '__ID__' });

normi.merge({
  __ID__: '1234',
  size: 'Venti',
});

GraphQL

If you've used GraphQL or Apollo, you may be aware that Apollo's normalized cache generates a unique identifier by concatenating the id field and the __typename field. This works well for GraphQL APIs, where the __typename property is typically added into your data payloads automatically by your GraphQL server framework.

To configure similar behavior in Normi:

const normi = new Normi({ id: ['id', '__typename'] });

Fully custom identifiers

For any other use case, you're able to totally customize ID generation by passing a function into your params:

const normi = new Normi({
  id: data => {
    // generate a string from your object
    if (data.uid) return data.uid;
    if (data.id) return `__${data.id}`;
    return `${Math.random()}`;
  },
});