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 🙏

© 2026 – Pkg Stats / Ryan Hefner

auto-block

v1.7.0

Published

Simplified controller creation built around async.auto

Readme

auto-block

Simplified controller creation built around async.auto

NPM version Build Status

Example usage

    var controller = {
        data: {
            context: context,
            event: event,
        },
        optionsMapping: {
            'slug': 'event.slug',
            'feed': 'event.feed',
            'dryrun': 'event.dryrun'
        },
        responseMapping: 'results.query'
    }

    controller.block = {
        'feedConfig': {
            func: helpers.clients.getClientConfig,
            after: ['options'],
            with: [
            'options.slug',
            'options.feed'
            ]
        },
        'redshiftPassword': {
            func: helpers.secrets.kmsDecrypt,
            after: ['feedConfig'],
            with: {
            'payload': 'feedConfig.import.redshiftPassword'
            }
        }
    }

    autoBlock.run(controller, context.done)

auto-block versus async.auto

async.auto is extremely useful for determining running order of interdependent async functions:

    async.auto({
        'one': function (results, cb) {
            // step one
        },
        'two': ['one', function (results, cb) {
            // step two
        }]
    }, function (err, results) {
        // all done
    })

When dealing with complex controllers, you often find it easier to move the step implementations into their own functions:

    function one(results, cb) {
        // step one
    }

    function two(results, cb) {
        // step two
    }

    async.auto({
        'one': one,
        'two': ['one', two]
    }, function (err, results) {
        // all done
    })

This allows for easier unit testing and debugging and helps keep your code clean. But you may often find yourself adding lots of small wrappers to convert values between the various steps:

    function one(results, cb) {
        cb(null, {
            'alpha': 'foo',
            'beta': 'bar'
        })
    }

    function two(results, cb) {
        var params = {
            'alpha': results.one.alpha,
            'beta': results.one.beta
        }

        // step two
    }

auto-block allows you to declare all of the mappings in the same place you declare the dependencies:

    block = {
        'one': {
            func: one
        },
        'two': {
            func: two,
            with: {
                'alpha': 'one.alpha',
                'beta': 'one.beta'
            }
        }
    }

Similarly, controllers often need to detect errors or attaching extra data if a step fails:

    async.auto({
        'one': utility.doStuff,
        'two': function (results, cb) {
            var options = {
                'alpha': results.one.alpha,
                'beta': results.one.beta
            }
            utility.findUser(options, function (err, result) {
                if (err) {
                    err.status = 404
                    err.alpha = options.alpha
                }
                cb(err, result);
            })
        }
    }, function (err, results) {
        if (err && err.status) {
            res.status(err.status).send({
                message: err.message
            })
        }
    })

auto-block lets you declare these error mappings using errorDefaults and errorMappings:

    var controller: {
        block: {
            'one': {
                func: one
            },
            'two': {
                func: two,
                with: {
                    'alpha': 'one.alpha',
                    'beta': 'one.beta'
                },
                errorDefaults: {
                    'status': 404
                },
                errorMappings: {
                    'alpha': 'one.alpha'
                }
            }
        },
        done: function (err, results) {
            if (err && err.status) {
                res.status(err.status).send({
                    message: err.message
                })
            }
        }
    }

These, along with other features, allow you to build complex controllers with needing to explicitly write function wrappers. auto-block does all of that for you.

Without the need for extra wrappers, the temptation to inline business logic is removed. You can comfortably move the business logic out of the controller without needing to know the details of which controller module you used.

Controller configuration

.done

    var controller = {
        done: function (error, response) {
            console.log('do stuff');
        }
    }

Called after the entire block has been completed. The values of error and response are generated by their respective mapping configurations (see below).

The done function can also be provided as the second parameter to autoBlock.run:

    autoBlock.run(controller, function (error, response) {
        console.log('do stuff');
    })

This second parameter will not override the .done field.

.data

    var controller = {
        data: {
            'foo': 'bar',
            'fizz': 'buzz'
        }
    }

Values provided in the data fields are available during options, error and response mappings but not during results mapping.

.block

    controller = {
        block: {
            'alpha': {
                // ...
            },
            'beta': {
                // ...
            }
        }
    }

block holds the actual steps used during autoBlock.run. See below for details on how to configure steps properly.

Option mapping

The values in .data are not exposed to the individual steps. You can explicitly expose them, however, using optionsMapping:

    controller = {
        data: {
            'foo': {
                'bar': 'zaz'
            }
        },
        optionsDefaults: {
            'fizz': 'buzz'
            'bar': 'not zaz'
        },
        optionsMapping: {
            'bar': 'foo.bar'
        },
        block: {
            'alpha': {
                func: utility.doAlpha,
                with: {
                    'bar': 'options.bar', // resolves to 'zaz'
                    'fizz': 'options.fizz' // resolves to 'buzz'
                }
            }
        }
    }

Behind the scenes, auto-block builds a special options step that runs before any other step you've declared on .block. If you don't need optionsMapping or optionsDefaults, you can set your own options step:

    controller = {
        block: {
            'options': {
                value: {
                    'fizz': 'buzz'
                }
            },
            'alpha': {
                func: utility.doAlpha,
                with: {
                    'fizz': 'options.fizz' // resolves to 'buzz'
                }
            }
        }
    }

Error mapping

If a step produces an error, you can map additional fields onto the error before it is sent to .done:

    controller = {
        data: {
            'foo': {
                'bar': 'zaz'
            }
        },
        errorDefaults: {
            'fizz': 'buzz'
            'bar': 'not zaz'
        },
        errorMapping: {
            'bar': 'foo.bar',
            'alpha': 'results.alpha'
        },
        block: {
            'alpha': {
                func: utility.doAlpha // result is 'alpha'
            },
            'beta': {
                func: utility.doBeta, // generates new Error('bad news')
                with: ['alpha']
            }
        },
        done: function (error, response) {
            // error will be similar to:
            // {
            //     message: 'bad news',
            //     data: {
            //         fizz: 'buzz',
            //         bar: 'zaz',
            //         alpha: 'alpha'
            //     }
            // }
        }
    }

If necessary, you can also add error mapping for particular steps:

    controller = {
        data: {
            'foo': {
                'bar': 'zaz'
            }
        },
        block: {
            'alpha': {
                func: utility.doAlpha // result is 'alpha'
            },
            'beta': {
                func: utility.doBeta, // generates new Error('bad news')
                with: ['alpha']
                errorDefaults: {
                    'fizz': 'buzz'
                    'bar': 'not zaz'
                },
                errorMapping: {
                    'bar': 'foo.bar',
                    'alpha': 'results.alpha'
                },
            }
        },
        done: function (error, response) {
            // error will be similar to:
            // {
            //     message: 'bad news',
            //     data: {
            //         fizz: 'buzz',
            //         bar: 'zaz',
            //         alpha: 'alpha'
            //     }
            // }
        }
    }

.errorSuppress

Errors that break out of the controller will be sent through to the .done handler. If you need to suppress these, use .errorSuppress:

    controller = {
        errorDefaults: {
            'retry': false
        },
        errorSuppress: {
            'data.retry': false
        },
        block: {
            'alpha': {
                func: utility.doAlpha // generates new Error('bad news')
            }
        },
        done: function (error, response) {
            // error will be undefined
        }
    }

Response mapping

After all steps are completed, you can map values into the response parameter of .done:

    controller = {
        data: {
            'foo': {
                'bar': 'zaz'
            }
        },
        responseDefaults: {
            'fizz': 'buzz'
            'bar': 'not zaz'
        },
        responseMapping: {
            'bar': 'foo.bar',
            'alpha': 'results.alpha'
        },
        block: {
            'alpha': {
                func: utility.doAlpha // result is 'alpha'
            }
        },
        done: function (error, response) {
            // response will be similar to:
            // {
            //    fizz: 'buzz',
            //    bar: 'zaz',
            //    alpha: 'alpha'
            // }
        }
    }

Hooks

auto-block provides five hooks that can be used for things like logging or debugging:

  • onStart(data) -- called exactly once before any step is run
  • onStartStep(name, data) -- called immediately after a step starts
  • onFinishStep(name, data, stepData) -- called just before the step callback is run
  • onSuccess(response, data) -- called after the response has been mapped if no error exists
  • onFailure(error, data) -- called after the response has been mapped if an error exists

The data parameter noted above is the same as the .data configuration with a few extra fields added.

stepData is a string with some debugging information in it but is not well defined.

Custom hook mapping

You can alter the payloads for hooks by using .func and .with:

    controller = {
        onStart: {
            func: console.log,
            with: {
                'foo': 'bar'
            }
        }
    }

Block configuration

Each key in .block represents one step that should be run. The definition for each step can include any number of settings:

.func

.func is the asynchronous function that will be run during .run:

    block: {
        'alpha': {
            func: utility.doAlpha
        }
    }

The last parameter of the function must be a callback. The number of other parameters is flexible (see .when below).

.sync

.sync is the synchronous function that will be run during .run:

    block: {
        'alpha': {
            sync: utility.doAlpha
        }
    }

The parameters work exactly like .func except no callback function is required. If the function returns a Promise, it will handle .then asynchronously as expected.

.value

.value will merely add an object to the internal results payload. This can be useful for adding extra fields for mapping:

    block: {
        'options': {
            value: {
                'foo': 'bar'
            }
        },
        'alpha': {
            func: utility.doAlpha,
            with: {
                'foo': 'options.foo'
            }
        }
    }

.with

.with defines the parameter mapping to be used with .func. You can either define an object:

    block: {
        'alpha': {
            func: utility.doAlpha,
            with: {
                'foo': 'options.foo',
                'fizz': 'fizz.buzz'
            }
        }
    }

    // calls utility.doAlpha({ 'foo': '...', 'fizz': '...' }, cb)

Or an array:

    block: {
        'alpha': {
            func: utility.doAlpha,
            with: [
                'options.foo',
                'fizz': 'fizz.buzz'
            ]
        }
    }

    // calls utility.doAlpha('...', '...', cb)

The dot syntax starts with the results from all previous steps and will automatically wait for those steps to complete:

    block: {
        'beta': {
            func: utility.doBeta, // runs after doAlpha completes
            with: {
                'foo': 'alpha.foo'
            }
        },
        'alpha': {
            func: utility.doAlpha, // runs immediately
        }
    }

.after

You can add explicit dependencies using .after:

    block: {
        'delta': {
            func: utility.doDelta, // runs after doAlpha and doBeta complete
            after: ['beta'],
            with: {
                'foo': 'alpha.foo'
            }
        },
        'alpha': {
            func: utility.doAlpha, // runs immediately
        },
        'beta': {
            func: utility.doBeta, // runs immediately
        }
    }

.when

Some steps are contingent on specific values or results from previous steps. You can add these sorts of value dependencies using .when:

    block: {
        'alpha': {
            func: utility.doAlpha,
        },
        'beta': {
            func: utility.doBeta,
            when: 'alpha.flag'
        }
    }

    // doBeta will only run if doAlpha results in a value similar to:
    // {
    //     "flag": true
    // }

.when settings will automatically add dependencies. In the above example, the "beta" step will still occur after the "alpha" step.

Value comparison is supported using objects:

    block: {
        'alpha': {
            func: utility.doAlpha,
        },
        'beta': {
            func: utility.doBeta,
            when: {
                'alpha.foo': 'bar'
            }
        }
    }

    // doBeta will only run if doAlpha result includes the follow key/value pair:
    // {
    //     "foo": "bar"
    // }

Negative checks can be made by using a ! prefix:

    block: {
        'alpha': {
            func: utility.doAlpha,
        },
        'beta': {
            func: utility.doBeta,
            when: '!alpha.flag'
        }
    }

Multiple checks are allowed (all checks must succeed):

    block: {
        'alpha': {
            func: utility.doAlpha,
        },
        'beta': {
            func: utility.doBeta,
            when: [
                '!alpha.flag',
                {
                    'alpha.foo': 'bar'
                }
            ]
        }
    }