Nighttime Peanut Migrations
    Have ideas to improve npm?Join in the discussion! »

    flipr

    2.2.1 • Public • Published

    node-flipr

    NPM

    Build Status

    Static and dynamic configuration for Node.js applications.

    Great for feature flags, authorization, A/B tests, dynamic routing, service discovery, and other things.

    node-flipr

    Check out our blog article to learn more about why flipr exists.

    How does it work?

    Simple static configuration example using a yaml source

    ---
    someConfigKey:
      description: >
        This is some config key that has some value.
      value: someValue
    const Flipr = require('flipr');
    const FliprYaml = require('flipr-yaml');
     
    const source = new FliprYaml({
      filePath: './config/example.yaml',
    });
    const flipr = new Flipr({
      source,
    });
     
    console.log(await flipr.getConfig());
    // { "someConfigKey": "someValue" }

    Complex dynamic configuration example using a yaml source

    ---
    someConfigKey:
      description: >
        This is some config key that has multiple values
        that change based on the user/request context.
      values:
        value: someValue
          isUserSpecial: true
          percent: 25
        value: someOtherValue
          userIds:
            - 1234
            - 5678
          percent: 75
    const Flipr = require('flipr');
    const FliprYaml = require('flipr-yaml');
     
    const rules = [
      {
        type: 'equal',
        input: (input) => input.user.userId === '1234',
        property: 'isUserSpecial'
      },
      {
        type: 'list',
        input: 'user.userId',
        property: 'userIds'
      },
      {
        type: 'percent',
        input: 'user.userId',
      }
    ];
    const source = new FliprYaml({
      filePath: './config/example.yaml',
    });
    const flipr = new Flipr({
      source,
      rules,
    });
     
    const input = {
      user: {
        userId: '1234'
      }
    };
     
    console.log(await flipr.getValue('someConfigKey', input));
    // someValue

    What is a flipr source?

    A flipr source is something that gives flipr configuration data using a known schema over a known interface. The schema can be seen in the examples above. Where does it get the data? It's up to you. There are some pre-built sources for flipr (see below), but you can easily create your own. A flipr source should expose the following interface:

    module.exports = {
      async getConfig() { },
      async preload() { },
      async flush() { },
      async validateConfig(options) { },
    };
    • getConfig: This method should return the config as an object. This action should cache the config for subsequent calls.
    • preload: This should load the config data and cache it. This gives users the option to load the config during a warmup process.
    • flush: This should flush all cached config data, so that the next call will grab it from the original source.
    • validateConfig: Flipr provides some robust validation for its config data via the flipr-validation library. If the flipr source is reading from a static resource, like yaml files, you can use this to validate the config in your unit tests. If the flipr source is reading from a dynamic resource, like etcd or a database, you'll want to use flipr-validation in the process that adds data to that resource, so that bad config doesn't get sent over to flipr.

    Available flipr sources

    • flipr-yaml: Read flipr configuration from yaml files.
    • OUT OF DATE flipr-etcd: This source is out of date, but remains as an example. This source will read configuration from Etcd. You should use this source if you want dynamic configuration that not only changes based on input, but can also be updated externally without redeploying your applications.

    Would you like to know more?

    Flipr Constructor Options

    • source - required - The source you want to use to retrieve config information.
    • rules - optional - array - The rules you want to use to drive your dynamic configuration.

    Flipr Rules

    Flipr uses rules along with input provided at the time of config retrieval to calculate a single config value from many, i.e. dynamic configuration. Rules are just objects with three properties.

    • type - string - The type of the rule. One of the following values.
    • input - string, function - The input that is provided to the getValue and getConfig methods is typically something like a user or request context. The input property in the rule is the logic to extract data from the provided input that will be used to compare to the values in the config. A string is the object-path of the value to use in the rule. A function accepts the input and returns the value to use in the rule. If you decide to use a function for a rule's input property, be aware that that function should be as safe as possible. If an exception is thrown by the input function, that rule will be silently skipped.
    • property - string - The name of the rule property used in the configuration.

    Percent

    The percent rule is used to change config values based on some percentage calculated using a unique identifier. One common use for this rule is rolling out changes to an arbitrary percentage of users. The example below shows how you would enable a feature for 15% of your users. Percents are cumulative, starting with the smallest percent and must add up to 100 for a single property, e.g. 1, 4, 95 would in reality be 0-1%, >1-5%, >5%-100%.

    isSomeFeatureEnabled:
      values:
        value: true
          percent: 15
        value: false
          percent: 85
    const rule = {
      type: 'percent',
      input: 'user.id'
    };

    List

    The list rule allows you to change config values based on some list of allowed values. One common use for this rule would be enabling features for specific users or groups of users. The example below shows you how you could enable a feature for any users living in Arizona or California.

    isSomeFeatureEnabled:
      values:
        value: true
          states:
            - AZ
            - CA
        value: false
    const rule = {
      type: 'list',
      input: 'user.state', // input can be a nested property
      property: 'states'
    };

    Equal

    The equal rule is much like the list rule, except it only allows a single value instead of a list of values. One common use for this rule would be enabling features based on a single user characteristic that only has a limited number of states (e.g. boolean). The example below shows you how you could enable a feature for any user that has been using your application for a number years, and is over the age of 18. This example also shows another feature of rules: the ability to accept a function in the input property. Note that all three rules let you pass a function for input.

    isSomeFeatureEnabled:
      values:
        value: true
          isAdmin: true
        value: false
    const someMinimumDate = new Date(2005, 1, 1);
    const rule = {
      type: 'equal',
      input: (input) => {
        return input.user.startDate > someMinimumDate
          && input.user.age >= 18;
      },
      property: 'isAdmin'
    };

    Path Equal

    The pathEqual rule is much like the equal rule, except it expects the rule's config property to be an object, where the key is an object path and the value is the value to be compared for equality. The object path will be used to extract a value from the rule input and compare it to the value in the config. This is useful when you're dealing with inputs that are nested objects with dynamic paths. Consider this example:

    isSomeFeatureEnabled:
      values:
        value: true
          accessControlBatch:
            someFeature.enabled: true
        value: false
    const input = {
      batch: {
        someFeature: {
          enabled: true,
          highlighted: false
        },
        anotherFeature: {
          enabled: false
        }
      }
    };
     
    const rule = {
      type: 'pathEqual',
      input: 'batch',
      property: 'accessControlBatch'
    };

    Includes

    The includes rule has three distinct behaviors, depending on whether the input is a string, an array, or an object. It uses lodash's includes method internally.

    Input is a String

    When input is a string, the includes rule will check if the rule property in the config is a substring of the input.

    isSomeFeatureEnabled:
      values:
        value: true
          name: john
        value: false
    const rule = {
      type: 'includes',
      input: input => input,
      property: 'name',
    };
    ...
    flipr.getValue('isSomeFeatureEnabled', 'john'); // true
    flipr.getValue('isSomeFeatureEnabled', 'johnathan'); // true
    Input is an Array

    When input is an array, the includes rule will check if the rule property in the config exists in the input array.

    isSomeFeatureEnabled:
      values:
        value: true
          groups: admins
        value: false
    const rule = {
      type: 'includes',
      input: input => input,
      property: 'groups',
    };
    ...
    flipr.getValue('isSomeFeatureEnabled', ['users', 'admins']); // true
    Input is an Object

    When input is an object, the includes rule will check if the rule property in the config exists as a value in the input object. Note that the includes check is shallow, so only the input's root property values will be compared.

    isSomeFeatureEnabled:
      values:
        value: true
          groups: admins
        value: false
    const rule = {
      type: 'includes',
      input: input => input,
      property: 'groups',
    };
    ...
    flipr.getValue('isSomeFeatureEnabled', { groupA: 'users', groupB: 'admins' }); // true

    IncludesListAll

    The includesListAll rule is like the includes rule, in that it has the same three distinct behaviors, depending on whether the input is a string, an array, or an object. However, unlike the includes rules, the rule property in the config must always be an array. And ALL values in that array must match the includes logic.

    isSomeFeatureEnabled:
      values:
        value: true
          groups:
            - admins
            - superAdmins
        value: false

    IncludesListAny

    The includesListAny rule is like the includesListAll rule, except at least one value in the rule property array must match the includes logic.

    Other Noteworthy Behavior

    • Flipr deals with two different types of configuration data: static and dynamic. Static configuration is created using the value: property, while dynamic is created using the values: property. Static configuration doesn't change based on input, so the rules you define are ignored. Dynamic configuration does change based on input. Each time you pass input to flipr, it will run through the rules you have defined to determine the correct values. While this is not an expensive operation, it would be a good idea to minimize the number of calls to flipr when getting dynamic configuration, especially if you have a large config.
    • If you define value and values on the same item, value will always be chosen. Config validation will throw an error for this situation.
    • Values from input are compared to values in config by making them both strings and using a case-insensitive comparison.
      • You can force case sensitivity by setting the 'isCaseSensitive' property on the rule to true.
    • Rules are executed in the order they are defined in the rules option. If a match is found for a rule, it will skip the remaining rules and return the matched value.
    • Generally, flipr sources should be caching any data they retrieve to build the config. Once it's cached, you'll need to explicitly call flush on flipr to get any updated values (unless the source implements some sort of automatic flushing).
    • Accessing config via flipr is treated as an asynchronous action for the sake of compatibility, even if the source's implementation is synchronous.

    Install

    npm i flipr

    DownloadsWeekly Downloads

    13

    Version

    2.2.1

    License

    MIT

    Unpacked Size

    267 kB

    Total Files

    62

    Last publish

    Collaborators

    • avatar