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

chained-config

v0.2.1

Published

Use a chaining API to generate and simplify the modification of configs

Readme

chained-config

NPM version Downloads Build Status Coverage Status Dependency status Dev Dependency status Greenkeeper badge

Use a chaining API to generate and simplify the modification of configs.

Installation

$ npm install chained-config

Motivation

With webpack-chain, manipulating the webpack config is a breeze. This is possible thanks to ChainedMap and ChainedSet.

chained-config offers those constructors in a separate package so that we can apply the same concepts to other configurations other than Webpack. There are small differences that are noted in each API section.

Usage

const {
    ChainedMap,
    OrderableChainedMap,
    ChainedSet,
} = require('chained-config');

Either you may use these directly or extend them, just like DevServer from webpack-chain extends ChainedMap.

const chainedMap = new ChainedMap();

// ..or

class MyConfig extends ChainedMap {
    // your own methods here..
}

const myConfig = new MyConfig();

API

Chainable

This is the base class that others inherit from, it's not meant to be used directly.

constructor(parent)

The constructor expects a parent pointing to the parent chain, if any.

.end()

Ends the current chain, going back to the parent chain.

Returns the parent.

.batch(fn)

Execute a function against the current chain.
This is useful to execute various operations while not breaking the chain.

Returns itself to allow chaining.

.when(condition, whenTruthy, whenFalsy)

Conditionally execute a function on the current chain.
whenTruthy and whenFalsy are called if condition is truthy or falsy respectively.

Returns itself to allow chaining.

#isChainable(value)

Test if value is an instance of Chainable.

Returns true if it is, false otherwise.

ChainedMap

A ChainedMap operates similarly to a JavaScript Map, with some conveniences for chaining and generating configs. It extends Chainable which means that all its methods are also available.

Some of the changes detailed above are breaking but they will mostly affect developers implementing the configs and not the consumers.

constructor(parent, [options])

At the moment, ChainedMap accepts a single option named asArray.

A Set (and ChainedSet) is very limited in its capacities. With asArray, we can leverage ChainedMap features while keeping the return type of toConfig as an array. In this model, the keys on the backing map act as labels that identify items, making it easier for consumers to perform manipulations. Moreover, when using OrderableChainedMap, consumers may even change the items' order.

.get(key)

Retrieve the value of the item corresponding to key.

.set(key, value)

Set the value of the item corresponding to key.

If value is a Chainable, a getter/setter will automatically be created with the name key:

class MyConfig extends ChainedMap {
    constructor(parent) {
        super(parent);

        this.set('foo', new ChainedMap(this));
    });
}

const myConfig = new MyConfig();

// myConfig.foo is a `ChainedMap` instance

Returns itself to allow chaining.

.tap(key, fn)

Execute fn with the current value of the item corresponding to key.
The return value of fn will replace the current value.

Returns itself to allow chaining.

.delete(key)

Remove the item corresponding to key.

Returns itself to allow chaining.

.has(key)

Check if there's an item corresponding to key.

Returns true if it exists, false otherwise.

.clear()

Remove all items from the Map.

Returns itself to allow chaining.

.keys()

Return an array of the keys stored in the Map.

.values()

Return an array of the values stored in the Map.

.entries()

Return an array of all the [key, value] pairs for each element stored in the Map.

.forEach(fn, [thisArg])

Execute fn once per each key/value pair in the Map.

.merge(ob, [omit])

Provide an object which maps its properties and values into the backing Map as keys and values.
You can also provide an array as the second argument for property names to omit from being merged.

Returns itself to allow chaining.

.shorthands(keys)

Add shorthands for every key specified in keys array.

Essentially, this method is a sugar for:

class MyConfig extends ChainedMap {
    foo(value) {
        return this.set('foo', value);
    }

    bar(value) {
        return this.set('bar', value);
    }
};

which can be written as:

class MyConfig extends ChainedMap {
    constructor(parent) {
        super(parent);

        this.shorthands(['foo', 'bar']);
    }
};

Returns itself to allow chaining.

.toConfig()

Return the plain object representation of the config.
Calls .toConfig() recursively for each item that is a chainable.

Example usage of ChainedMap

class Jest extends ChainedMap {
    constructor(parent) {
        super(parent);

        this.shorthands([
            'rootDir',
            'runner',
        ]);

        this.set('coverageThresholds', new ChainedMap(this));
        this.set('coveragePathIgnorePatterns', new ChainedMap(this, { asArray: true }));
    }
}

// Then use it like so:
const jestConfig = (new Jest())
    .rootDir('my-project')
    .runner('jest-runner-mocha')
    .coverageThresholds
        .set('global', {
            branches: 80,
            functions: 80,
            lines: 80,
            statements: -10
        })
        .tap('global', (thresholds) => Object.assign(thresholds, { branches: 100 }))
        .end()
    .coveragePathIgnorePatterns
        .set('node_modules', '/node_modules/')
        .end()
    .toConfig();

// {
//     rootDir: 'my-project',
//     runner: 'jest-runner-mocha',
//     coverageThresholds: {
//         global: {
//             branches: 100,
//             functions: 80,
//             lines: 80,
//             statements: -10,
//         },
//     },
//     coveragePathIgnorePatterns: ['/node_modules/'],
// }

OrderableChainedMap

OrderableChainedMap extends Chainable and allows to re-order keys via the move(key, specifier) method. It also adds before(key) and after(key) methods on Chainables added via set. This allows an item to declare that it comes before or after another by calling the aforementioned methods on itself.

Consequently, keys(), values(), entries(), forEach() and toConfig() methods of OrderableChainedMap will have into consideration any changes to the items order.

.move(key, specifier)

Moves the item corresponding to key before of after another, according to the specifier.

specifier is a function that will be called with a single argument which is an object with before(relativeKey) and after(relativeKey) functions.

Returns itself to allow chaining.

Methods added to Chainables included via set

.before(key)

Marks the item to be positioned before key on the OrderableChainedMap they belong.

Returns itself to allow chaining.

.after(key)

Marks the item to be positioned after key on the OrderableChainedMap they belong.

Returns itself to allow chaining.

Example usage of OrderableChainedMap

class Jest extends ChainedMap {
    constructor(parent) {
        super(parent);

        this.shorthands([
            'rootDir',
            'runner',
        ]);

        this.set('setupFiles', new OrderableChainedMap(this, { asArray: true }));
        this.set('projects', new OrderableChainedMap(this, { asArray: true }));
    }

    project(name) {
        if (!this.projects.has(name)) {
            this.projects.set(name, new ChainedMap(this));
        }

        return this.projects.get(name);
    }
}

// Then use it like so:
const jestConfig = (new Jest())
    // Using `move` to re-order items
    .setupFiles
        .set('setup-foo', '/path/to/setup/foo.js')
        .set('setup-bar', '/path/to/setup/bar.js')
        .set('setup-baz', '/path/to/setup/baz.js')
        .move('setup-bar', ({ before }) => before('setup-foo'))
        .move('setup-baz', ({ after }) => after('setup-foo'))
        .end()
    // Using `before` and `after` which where added automatically by
    // OrderableChainedMap to items added via `set`
    .project('examples')
        .set('displayName', 'lint')
        .set('runner', 'jest-runner-eslint')
        .set('testMatch', ['<rootDir>/**/*.js'])
        .end()
    .project('test')
        .set('displayName', 'test')
        .before('examples')
        .end()
    .toConfig();

// {
//     setupFiles: [
//         '/path/to/setup/bar.js',
//         '/path/to/setup/foo.js',
//         '/path/to/setup/baz.js',
//     ],
//     projects: [
//         {
//             displayName: 'test',
//         },
//         {
//             displayName: 'lint',
//             runner: 'jest-runner-eslint',
//             testMatch: ['<rootDir>/**/*.js'],
//         },
//     ],
// }

ChainedSet

A ChainedSet operates similarly to a JavaScript Set, with some conveniences for chaining and generating configs. It extends Chainable which means that all its methods are also available.

.add(value)

Adds value to the end of the Set.

Returns itself to allow chaining.

.prepend(value)

Adds value to the start of the Set.

Returns itself to allow chaining.

.delete(value)

Removes value from the Set.

Returns itself to allow chaining.

.has(value)

Check if value is within the Set.

Returns true if it exists, false otherwise.

.clear()

Remove all items from the Set.

Returns itself to allow chaining.

.values()

Returns an array of the values stored in the Set.

.forEach(fn, [thisArg])

Execute fn once per each value in the Set.

.merge(arr)

Provide an array whose items will be added to the Set.

Returns itself to allow chaining.

.toConfig()

Return the array representation of the config.
Calls .toConfig() recursively for each item that is a chainable.

Example usage of ChainedSet

class Jest extends ChainedMap {
    constructor(parent) {
        super(parent);

        this.shorthands([
            'rootDir',
            'runner',
            // ...
        ]);

        this.set('moduleFileExtensions', new ChainedSet(this));
    }
}

// Then use it like so:
const jestConfig = (new Jest())
    .rootDir('my-project')
    .runner('jest-runner-mocha')
    .moduleFileExtensions
        .add('ts')
        .add('tsx')
        .end()
    .toConfig();

// {
//     rootDir: 'my-project',
//     runner: 'jest-runner-mocha',
//     moduleFileExtensions: ['ts', 'tsx'],
// }

Tests

$ npm test
$ npm test -- --watch during development

License

Released under the MIT License.