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

cerebro

v1.3.1

Published

A generic configuration system for Node.js.

Downloads

429

Readme

Cerebro

Build Status Coverage Status

A generic configuration system for Node.js.

Cerebro receives two inputs:

  • the context, which is all data associated with a specific user or request. each individual piece of context is referred to as a dimension.
  • the configuration, which are the values which will be chosen based on what the context is.

Cerebro outputs the resolved configuration.

Usage

import Cerebro from 'cerebro';

const configuration = [{
  setting: 'timer',
  value: 30,
  except: [{
    value: 15,
    environment: ['alpha']
  }]
}];

const context = {
  environment: 'alpha'
};

// initialize Cerebro
const cerebro = new Cerebro(configuration);
const resolvedConfiguration = cerebro.resolveConfig(context);

console.log(resolvedConfiguration);
// -> { timer: 15 } because the setting `timer` is set to 15 when the environment is alpha.
// for all other environments, the resolvedConfiguration would be { timer: 30 }

Configuration Rules

For configuration, Cerebro expects an array that contains objects that have a setting and a value. setting is the name of the configuration setting, and value is the value that will be assigned to it. value can be any JSON data type.

Here is an example configuration object:

[{
  setting: 'timer',
  value: 30,
  except: [{
    value: 15,
    environment: ['alpha']
  }]
}]

Settings must explicitly set the value in the except blocks.

Settings are expected to follow these rules:

  • Setting must be a string.
  • Value must not be undefined.
  • Except must be an array.
  • The elements of except must be objects.
  • An element of except must contain value, and this value must not be undefined.

Settings are defined formally in src/validators/schema.json.

Evaluation Order

If all the criteria in an except block is met, the value in the except block will be used. The except blocks are checked in order, and it exits once it finds a match. If no except block matches fully, then the default value is used.

Here is an example:

import Cerebro from ('cerebro');

const configuration = [{
  setting: 'timer',
  value: 30,
  except: [{
    value: 15,
    environment: ['alpha']
  }, {
    value: 20,
    environment: ['alpha'],
    bucket: 'a'
  }]
}];

const context = {
  environment: 'alpha',
  bucket: 'a'
};

// initialize Cerebro
const cerebro = new Cerebro(configuration);
const resolvedConfiguration = cerebro.resolveConfig(context);

console.log(resolvedConfiguration);
// -> { timer: 15 } because the setting `timer` is set to 15 when the environment is alpha.
// in this case, {timer: 20} would never be hit because {timer: 15} will cause the evaluation
// to end immediately when environment is alpha

Supported Formats for Context Checks in Except

Enums

Given an except block that accepts multiple values for the same dimension, enable the setting if a dimension in the context object matches one of those values. For example, if the configuration has something like this:

[{
  setting: 'enableNewFeature',
  value: false,
  except: [{
    value: true,
    bucket: ['a', 'b']
  }]
}];

The setting should be enabled if the context object contains the dimension bucket: 'a' or bucket: 'b'.

Enums also support two additional options, none and all. Here's what the configuration would look like:

[{
    setting: 'enableNewFeature',
    value: false,
    except: [{
        value: true,
        partner: [
            'all'
        ]
    }]
}];

In this case, if the context object contains any partner, the setting will be enabled. If the context contains no partner, the setting will be disabled. In other words, the setting is only enabled for partners.

[{
  setting: 'enableNewFeature',
  value: false,
  except: [{
    value: true,
    partner: ['none']
  }]
}];

Similar logic applies to none. If the context contains no partner, the setting will be enabled. If the context contains any partner, the setting will be disabled. In other words, the setting is disabled only for partners.

Ranges

A configuration may contain a range of values. The range may be inclusive or exclusive.

An inclusive range looks like this: rangeExample: ['1000..2000']. An exclusive range looks like this: rangeExample: ['1000...2000'].

Here’s an example of a setting using a range:

[{
  setting: 'enableNewFeature',
  value: false,
  except: [{
    value: true,
    userBirthdayYear: ['2000...2010']
  }]
}];

Thus, if the context contains a dimension called userBirthdayYear that is anywhere between 2000 and 2010, exclusive, the setting will be enabled.

Setting Dependency

One setting may depend on another setting. In this case, the configuration would look something like this:

[{
  setting: 'independent',
  value: false,
  except: [{
    value: true,
    environment: ['alpha']
  }]
}, {
  setting: 'dependent',
  value: false,
  except: [{
    value: true,
    setting: 'independent'
  }]
}];

In this case, the dependent setting will not be enabled unless the independent setting is also enabled.

Custom Evaluators

We may want to evaluate a context in a custom way. For example, we may want to use do a partial string match using a regex.

Below is an example of the configuration:

[{
    setting: 'enableNewFeature',
    value: false,
    except: [{
        value: true,
        partialLocale: ['en']
    }]
}]

To use a custom evaluator, you must pass an object called customEvaluators through the options object in the Cerebro constructor. This object will have the name of the dimension it is to evaluate as a key and the evaluation function as value.

The evaluation function will receive two parameters, a dimensionValue, which is the value given in the configuration (en in the above example), and testValue, which is the value passed in the context.

The evaluation function must return a boolean. If it does not return a boolean, the return value will be coerced to a boolean.

For the above configuration, the customEvaluators would look like this:

var customEvaluators = { customDimension: function(dimensionValue, testValue) {} };

The dimensionValue would be [“en”], and the testValue would be whatever the value of customDimension was in the context.

Overrides

We may want to override a certain setting, regardless of what the configuration says. To do this, the client must pass an object, overrides, into the Cerebro constructor. The keys of the overrides object will be the setting names and the value will be the value of the setting. Typically, these overrides would come from the request URL, but any source can be used.

Labels

Label metadata may be added to each entry in the form of an array of strings. This does not affect the evaluation of setting entries into their resulting values. It's stricly meant as a way to contextualize those values.

For instance, values that are not wanted as part of a client payload can be marked "server".

Note that this could be used to create subsets of values that no longer have all of the original entries with contraints such as dependencies.

An alternative way to organize and contextualize is to group settings into separate files.

[{
    setting: 'enableNewFeature',
    labels: ['server', 'namedFeatureGroup'],
    value: false,
    except: [{
        value: true,
        partialLocale: ['en']
    }]
}]

Configuration polling

This option is useful for having live configuration that updates without having to re-deploy your application. A poller object continuously calls a fetch function to retrieve a newer version of the configuration. This is very open ended to allow for different security mechanisms such as mTLS or signed responses.

Optionally (but recommended), a configuration schema can be given to the poller to ensure that the response is a valid configuration.

const Cerebro = require('cerebro');

const poller = new Cerebro.ConfigPoller({
  clientSchema: /* optional schema */,
  interval: 5000,
  fetch: async function() {
    const response = await fetch(/* ... */);

    return response.json();
  }
});

const cerebro = new Cerebro(config, {
  poller
});

Thanks

  • Many thanks to Alasdair Mercer for donating the naming rights :)