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

shrubbery

v1.0.0

Published

Shrubbery is small Map() backed plugin registry

Readme

shrubbery

Shrubbery provides a simple, async, map based plugin/hook registry. You can add, remove, and exec plugins/hooks.

Installation

The plugin is released in the public npm registry and can be installed by running:

npm install --save shrubbery

Usage

The module expose our shrubbery function. The function accepts the following arguments:

  • mapping A object that contains the Map() instances we should use.
  • options Options to configure the behavior of the returned methods.
    • context The this value for the plugin functions.
    • timeout Default timeout applied to the each plugin.
    • error A global error handler for when plugin fails to execute, if none is provided our exec function would re-throw the captured errors.
import shrubbery from 'shrubbery';

const pluginAPI = shrubbery({
  foo: new Map(),
  bar: new Map()
}, { /* options here */ })

If you don't want to provide references to the maps, and want to generate those on the fly, that is also possible.

const pluginAPI = shrubbery();
const pluginAPI = shrubbery({});

We generate maps automatically if they do not exist. The returned plugin API has the following methods:

add

Adds a new plugin to a given map. The function accepts the following arguments:

  • name The name of the map, should be the name of the maps you provided to the shrubbery function. If the name does not exist, it will be created.
  • key The key we should store the function under.
  • fn The actual plugin function, we assume that this is an async function or a function that returns a promise.
  • options Additional plugin configuration
    • priority Allows you to control the order of execution. Defaults to 100. The highest value will be executed first, and lowest as last.
    • timeout Time in milliseconds (or as human readable string) that the function is allowed to execute. We will forcefully reject if the function takes longer. Defaults to 20 seconds.
const pluginAPI = shrubbery({ foo: new Map() });

pluginAPI.add('foo', 'bar', async function () {});

//
// This function will execute before the function that we specified above while
// it was specified later. This is because it has a higher priority.
//
pluginAPI.add('foo', 'bar', async function () {}, {
  priority: 101
});

//
// Will create a new map, as `wut` was not specified in our shrubbery.
//
plugin.add('wut', 'bar', async function () {

});

remove

Removes a previously registered function, or all functions for a given key. The function accepts the following arguments:

  • name The name of the map, should be the name of the maps you provided to the shrubbery function. If the name does not exist, it will be created.
  • key The key we should stored the function under.
  • fn The actual plugin function. All identical functions will be removed. If no function is supplied we will remove all functions for the given key.
const pluginAPI = shrubbery({ foo: new Map() });

async function foo() {}
async function bar() {}

pluginAPI.add('foo', 'bar', foo);
pluginAPI.add('foo', 'bar', bar);

//
// Remove the foo function, this means that our `bar` function
// can still be executed.
//
pluginAPI.remove('foo', 'bar', foo);

//
// Every function is now removed.
//
pluginAPI.remove('foo', 'bar');

exec

Executes all the function that are specified a given map/key combination. The function accepts the following arguments:

  • name The name of the map, should be the name of the maps you provided to the shrubbery function. If the name does not exist, it will be created.
  • key The key who's function should be executed.
  • data The data that should be passed to each function. This is data a plugin can modify. Each modification will be accessible by the next function. When a plugin returns a different value, it will be used as new data source. Once all functions are executed the final result will be returned. You can basically see each plugin function as an async map operation.
  • ...args The rest arguments that are always passed to the plugin, not intended to be modified, and also not returned.
const example = {};
const pluginAPI = shrubbery({}, {
  context: example
});

pluginAPI.add('example', 'modify', async function (data, options) {
  console.log(options);               // { options: 'here', priority: 9000 }
  console.log(this === example);      // true

  data.bar = 'wat';
}, { options: 'here', priority: 9000 });

pluginAPI.add('example', 'modify', async function (data) {
  console.log(data);                  // { foo: 'bar', bar: 'wat' }

  return 'different';
});

const result = await pluginAPI.exec('example', 'modify', { foo: 'bar' });
console.log(result);                  // `different`

License

MIT