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

abstraction

v11.0.0

Published

A library for modeling abstract business logic or wrapping data structures.

Downloads

52

Readme

abstraction

A way of modeling your business logic or data structures in a compact and compose-able way.

  • Does one job
  • Functions, Records, and Arrays, that's it.
  • Works for many uses: ODM/ORM, Presenter, Serializer, Deserializer, Decorator, etc.

Version Tests Stability Dependencies

using

Lets say you have some data from a server that you want to display to the user:

const incomingData = {
  id: "1",
  type: "accounts",
  attributes: {
    name: "Kurtis Rainbolt-Greene",
    age: "27",
    email: "[email protected]",
    "created-at": "2013-02-04T10:35:24-08:00",
  }
}

The age is a string, the timestamp for created at is also a string, and created at isn't camel case. We can fix this:

const accountResource = abstraction({
  attributes: {
    id: {source: prop("id")},
    type: {source: prop("type")},
    attributes: {
      source: prop("attributes"),
      coerce: accountAttributes,
    }
  }
})

There's a lot happening here so lets break it down:

const accountResource = abstraction({
  attributes: {
    ...
  }
}

Every abstraction has a set of attributes. These are properties that'll be exposed on the returning abstract object.

Lets take the id and type attributes for a moment:

const accountResource = abstraction({
  attributes: {
    id: {source: prop("id")},
    type: {source: prop("type")},
  }
})

For these we're just defining a source. The source is a function that tells us how to get to our data for id and type. This is easy for us, so we'll just use path(). Now it's time to get a little more complicated with attributes.

const accountResource = abstraction({
  attributes: {
    id: {source: prop("id")},
    type: {source: prop("type")},
    attributes: {
      source: prop("attributes"),
      coerce: accountAttributes,
    },
  },
})

So here we're not only sourcing from the raw data, but also using a fun little coerce function. This function should take the data from source and convert it to what you need. In this case we're doing something really cool in that we're actually coercing the data into another abstraction!

const accountAttributes = abstraction({
  attributes: {
    username: {source: pipe(prop("email"), split("@"), head)},
    email: {source: prop("email")},
    name: {source: prop("name")},
    age: {
      source: prop("age"),
      coerce: (value) => parseInt(value, 10),
    },
    createdAt: {
      source: prop("created-at"),
      coerce: moment,
    },
  },
})

There's some interesting things going on here as well, specifically in username's source and createdAt's coerce function. Notice that there is no username on the raw data and is instead really a derived value. In addition, your final property name doesn't have to be the same as your raw property.

Finally we also give a built in validations logic:

const accountResource = abstraction({
  attributes: {
    id: {source: prop("id")},
    type: {source: prop("type")},
    attributes: {
      source: prop("attributes"),
      coerce: accountAttributes,
    }
  },
  validations: {
    id: {
      "Must have an id": propSatisfies(isPresent, "id"),
      "Must be an string": propSatisfies(isType("String"), "id"),
      "Must have characters": propSatisfies(isPopulated, "id"),
    },
    type: {
      "Must have an type": propSatisfies(isPresent, "type"),
      "Must be an string": propSatisfies(isType("String"), "type"),
      "Must have characters": propSatisfies(isPopulated, "type"),
    },
    attributes: {
      "Must have attributes": propSatisfies(isPresent, "attributes"),
      "Must have properties": propSatisfies(isPopulated, "attributes"),
      "Must be an Object": propSatisfies(isType("Object"), "attributes"),
    }
  }
})

Each validation is a group, so you have a group of id validations. The keys can be messages, like above, or slugs (regular property names). You can read more about it in the [validus.js documentation][https://github.com/krainboltgreene/validus.js].

Now time to talk about what you'll get back when you run this:

const account = accountResource(incomingData)

Here's the return value:

{
  id: "1",
  type: "accounts",
  attributes: {
    name: "Kurtis Rainbolt-Greene",
    age: 27,
    email: "[email protected]",
    username: "kurtis",
    createdAt: 2013-02-04T18:35:24.000Z,
    __abstraction__: {
      ...
    }
  },
  __abstraction__: {
    ...
  }
}

Okay, wait, what's this __abstraction__ nonsense? Well, that's where we keep the validations. Lets say id was missing, here's what you'd see from:

account._errors

It would return:

{
  id: [
    "Must have an id",
    "Must be an string",
    "Must have characters",
  ],
  ...
}

Isn't that great? You can also ask for individual validations using validus.js directly:

import {validate} from "validus"

validate(account._validations)("id")(account)

And that would return:

[
  "Must have an id",
  "Must be an string",
  "Must have characters",
]

Again, assuming id was missing. The initial validations are done when you pass in the data, and the validate is for later validation.