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

jigawatt

v0.2.1

Published

Influential's Functional, Promise-based Express Middleware

Downloads

30

Readme

jigawatt

Build Status Coverage Status codecov Code Climate standard-readme compliant

Influential's Functional, Promise-based Express Middleware

Table of Contents

Installation

npm install jigawatt --save

Using Jigawatt

First, require the module.

const JW = require('jigawatt')

For this walkthrough, we'll also be using Ramda.

const R = require('ramda')

Basic Usage Example

For this example, we have a poll and poll responses stored in a database.

Jigawatt middleware functions, such as getPollResults in this case, are created to sanitize, validate, and/or normalize the incoming data (awesomize), then use that awesomized data to query a database (io), and lastly, format the results to our liking and return the output (transform).

const getPollResults = {
  awesomize: (v) => ({
    pollId : {
      read     : R.path([ 'params', 'pollId' ])
    , sanitize : [ R.toLower ]
    , validate : [ v.required ]
    }
  })

, io: (req, data) => ({
    poll      : db.Poll.findOne({ _id: data.pollId })
  , responses : db.Vote.find({ poll_id: data.pollId })
  })

, transform: (req, data) => ({
    poll    : data.poll.title
  , results : tallyVotes(
                getCity(data.poll)
              , getAnswer(data.responses)
              )
  })
}

Our Jigawatt middleware can then handle a given Express route. Consider the following endpoint:

...

app.get('/poll/:pollId', JW(getPollResults))

Example output:

{
	"poll": "What is your favorite city?",
	"results": [ { "Juneau": 2 }
             , { "Vladivostok": 3 }
             , { "Redding": 1	}
             , { "Wilmington": 0 }
             , { "Galveston": 2	}
             ]
}

This might be a bit overwhelming at first sight, so let's break it down in a bit more detail...

Middleware Structure

Jigawatt Middleware expects at least one of the following three properties:

awesomize

awesomize :: Validator -> Request -> Object

  • Normalize/Sanitize/Validate an object
  • For more detailed documentation about awesomize, visit the awesomize documentation

Awesomize has four components, all of which are optional: read -> sanitize -> validate -> normalize

The read component has access to the entire object passed to the awesomize function. Here, we'll use Ramda to target a specific value of the object passed:

awesomize: (v) => ({
  id : {
    read : R.path([ 'order', 'id' ])

...

sanitize is an awesomize component that can manipulate the data before it is validated.

awesomize: (v) => ({
  id : {
    read : R.path([ 'order', 'id' ])
  , sanitize : [ R.drop(2) ]

...

validate is a validator that is passed to our function as v. Awesomize's validate component has a few built-in validator methods such as required, isInt, isFunction, etc...

awesomize: (v) => ({
  id : {
    read : R.path([ 'order', 'id' ])
  , validate : [ v.required, v.isInt ]

...

We can also chain validation methods, as seen above. For more info on awesomize validators, visit the documentation.

As a last note about validate, we can create our own custom validator functions as well:

const isCorrectLength = (str) => R.equals(24, str.length)

...

awesomize: (v) => ({
  pollId : {
    sanitize : [ R.toLower ]
  , validate : [ isCorrectLength ]
  }

...

normalize is the last awesomize component, called after the data has been validated.

awesomize: (v) => ({
  id : {
    read      : R.path([ 'order', 'id' ])
  , validate  : [ v.required, v.isInt ]
  , normalize : [ R.inc ]
  }
})

A complete awesomize function can awesomize more than one value:

awesomize: (v) => ({
  id : {
    read      : R.path([ 'order', 'id' ])
  , validate  : [ v.required, v.isInt ]
  }
, product  : { sanitize : [ R.trim ] }
, customer : { validate : [ v.required ] }
})

io

io :: Request -> Data -> Object

io's primary use is to fetch data using the information passed to it from the awesomize function. In the example below, we have io making two calls to two separate database tables. Once resolved, io will pass the data fetched from the database along to the transform method.

io: (req, data) => ({
    poll      : db.Poll.findOne({ _id: data.pollId })
  , responses : db.Vote.find({ poll_id: data.pollId })
  })

transform

transform :: Request -> Data -> Object

transform is used to structure the incoming data in a unique way. Remember that transform is optional, and if omitted, the Jigawatt middleware simply returns the raw results.

const getAnswer = (arr) => R.compose(
  R.map(R.dec)
, R.pluck('answer')
)(arr)

const getCity = (obj) => R.map(
  R.replace(/\,.*$/, '')
, obj.questions
)

const tallyVotes = (options, responses) => {
  // Tally total vote for a specific city
  return R.map((str) => {
    let ind = R.indexOf(str, options)

    let votes = R.compose(
      R.length
    , R.filter(R.equals(ind))
    )(responses)

    return R.assoc(str, votes, {})
  }, options)
}

...

transform: (req, data) => ({
    poll    : data.poll.title
  , results : tallyVotes(
                getCity(data.poll)
              , getAnswer(data.responses)
              )
  })

Summary

Our Jigawatt middleware was used to take an incoming ID, query two separate database tables for that ID, and format the results to our liking. The final output of our Jigawatt middleware looks like this:

{
	"poll": "What is your favorite city?",
	"results": [ { "Juneau": 2 }
             , { "Vladivostok": 3 }
             , { "Redding": 1	}
             , { "Wilmington": 0 }
             , { "Galveston": 2	}
             ]
}

Extras

Promisify

If you would like to use a Jigawatt middleware as a promise, you can use the JW.promisify method:

const getSingleVote = {
  awesomize: (v) => ...
, io: (req, data) => ...
, transform: (req, data) => ...
}

...

const voteDetails = JW.promisify(getSingleVote)

...
voteDetails(data).then((result) => // do with the data what you will

Pipe

JW.pipe can be used to chain multiple Jigawatt middleware together into a single unit:

const combinedJigawatts = JW.pipe(getPollResults, getSingleVote, ...)

app.get('/poll/:pollId', JW(combinedJigawatts))

Branch

JW.branch should be given a predicate, and two Jigawatt middlewares. If the predicate function returns true, the first Jigawatt middleware is called. If false, the latter is called:

const fetchUserDetails = JW(
  JW.branch(
    isAdmin         // Predicate
  , showAllData     // Called if predicate returns true
  , showMinimalData // Called if predicate returns false
  )
)

Contribute

If you find issues with Jigawatt, we encourage you to open an issue ticket on the issues page. Please open a ticket on the issues page before submitting any pull requests!

License

MIT © Influential, Nathan Sculli