fcs-js
TypeScript icon, indicating that this package has built-in type declarations

2.4.5 • Public • Published

Feature Control System - FCS for JS

License MIT NPM version Downloads

What is it and why do I need it?

FCS (Feature Control System) is a simple library for feature-toggling and can be used to define list of features with simple and complex conditions and allowing to define environment settings and tags to resolve the status of each feature.

For example you can define features for specific users, specific active cookies, development environments, tags or even all of them combined in some logic pattern.

All your features can be managed from one place and easily updated to wider audience, rolled back to previous environment or completely removed.

FCS can be especially useful in Continues Integration workflow, where multiple changes are merged in short time to the mainline and not all of them are ready to be deployed to production.

It can be also used to limit some feature to very specific conditions, e.g. due to market regulations or conditions of some promotions.

It can be also useful for A/B testing and when enabling new features only for some segment of users to confirm new code working fine in real environment without risking issues for all the users.

How to configure list of features?

In case of small projects, you can simply keep configuration in array in some JS or JSON file.

In case of bigger projects, it's better to load it from redis or other cache provider, to distribute the list of features and their flags among different code repositories.

In case of redis integration, it's best to prepare some simple deployment script in backoffice to easily update flags for every feature, ideally with basic user interface. This way it can be used in emergency by anyone in your team with permissions, including PM or QA or even marketing team on handling particular temporary promotions (offering access to only some flags for different users).

FCS

Express.js (middlewares) and plain examples:

Examples for express.js (middlewares)

Basic plain examples (node.js and frontend)

Configuration methods:

Example of code to configure some settings:

    import {FCS, FLAGS} from 'fcs-js';
    
    const fcs = new FCS();
    
    fcs.setFlags({
        'my-feature': {flag: FLAGS.ACTIVE},
        'my-feature2': {flag: FLAGS.TAG, name: 'username', value: 'john123'}
    });
 
    fcs.setTag('env', 'preproduction');
    fcs.setTag('username', 'john123');
    fcs.setTag('lang', 'en');
  • setTag

Set a tag with a name and value

    // types
    function setTag(name: string, value: string | number | boolean): void;
    
    // example
    fcs.setTag('lang', 'en');
  • setFlags

Register a list of features with their flags, that can be used to determine if feature is active or not

    // types
    enum FLAGS {
        INACTIVE = 0,
        ACTIVE = 1,
        COOKIE = 2,
        TAG = 3,
        MULTI_AND = 4,
        MULTI_OR = 5,
    }
 
    interface IFlag {
        flag: FLAGS;
        name?: string;
        value?: string | string[] | number | number[] | boolean;
        flags?: IFlag[];
    }
    
    interface IFlags {
        [name: string]: IFlag;
    }
 
    function setFlags(flags: IFlags): void;
    
    // example
    fcs.setFlags({
        'my-feature': {flag: FLAGS.ACTIVE},
        'my-feature2': {flag: FLAGS.TAG, name: 'username', value: 'john123'},
        'my-feature3': {flag: FLAGS.TAG, name: 'lang', value: ['pl', 'de']}
    });

Getter methods:

  • feature

Check if feature is active and use returned boolean to enable some code

    // types
    function feature(name: string): boolean;
    
    // example
    if (fcs.feature('my-feature')) {
        // code is enabled
    }
  • getBodyCssClasses

Get string with list of CSS classes that can be appended to body element in your frontend

Returned CSS classes have "fcs-" prefix

Note: slash (/) in name of feature is automatically turned into double dash (--)

    // types
    function getBodyCssClasses(): string;
    
    // example
    const cssClasses = fcs.getBodyCssClasses(); //'fcs-my-feature fcs-my-feature2 fcs-test--my-feature100'
  • getActiveFeatures

Get array of names of features that can be for example injected to JS to enable some dynamic code on frontend

    // types
    function getActiveFeatures(): string[];
    
    // example
    const features = fcs.getActiveFeatures(); //['my-feature', 'my-feature2', 'my-feature100']

Available flags:

    import {FLAGS} from 'fcs-js';
 
    FLAGS.INACTIVE = 0;
    FLAGS.ACTIVE = 1;
    FLAGS.COOKIE = 2;
    FLAGS.TAG = 3;
    FLAGS.MULTI_AND = 4;
    FLAGS.MULTI_OR = 5;
  • FLAG_INACTIVE

Inactive flag means that feature is disabled for everyone

    fcs.setFlags({
        'my-feature': {flag: FLAGS.INACTIVE} // FLAGS.INACTIVE = 0
    });
 
    if (fcs.feature('my-feature')) { // false
        // no one can see it
    }
  • FLAG_ACTIVE

Active flag means that feature is enabled for everyone

    fcs.setFlags({
        'my-feature': {flag: FLAGS.ACTIVE} // FLAGS.ACTIVE = 1
    });
 
    if (fcs.feature('my-feature')) { // true
        // always working
    }
  • FLAG_COOKIE

Cookie flag means that feature is enabled for everyone who make request to the server with active cookie matching feature by its name

Cookies need to have "fcs-" prefix

Note: slash (/) in name of feature is automatically turned into double dash (--)

    fcs.setFlags({
        'my-feature': {flag: FLAGS.COOKIE} // FLAGS.COOKIE = 2
    });
    
    fcs.setCookies({
        'fcs-my-feature': '1'
    });
    
    if (fcs.feature('my-feature')) { // true, if fcs-my-feature cookie exists
        // working for anyone with enabled cookie "fcs-my-feature" in this case
    }
  • FLAG_TAG

Tag flag means that feature is enabled for a tag defined by name and for single value (string) or for list of values (array:string)

    fcs.setFlags({
        'my-feature': {flag: FLAGS.TAG, name: 'lang', value: 'en'}, // FLAGS.TAG = 3
        'my-feature2': {flag: FLAGS.TAG, name: 'lang', value: ['pl', 'de']} // FLAGS.TAG = 3
    });
    
    fcs.setTag('lang', 'en');
 
    if (fcs.feature('my-feature')) { // true
        // working for "lang" tag if its value is "en"
    }
 
    if (fcs.feature('my-feature2')) { // false
        // working for "lang" tag if its value is "pl" or "de"
    }
  • FLAG_MULTI_AND

Multi-and flag allows to define group of multiple flags and means that feature is enabled if all of the subflags meet their conditions

    fcs.setFlags({
        'my-feature': {flag: FLAGS.MULTI_AND, flags: [ // FLAGS.MULTI_AND = 4
            {flag: FLAGS.TAG, name: 'domain', value: 'domain.com'}, // FLAGS.TAG = 3
            {flag: FLAGS.TAG, name: 'lang', value: 'en'} // FLAGS.TAG = 3
        ]},
        'my-feature2': {flag: FLAGS.MULTI_AND, flags: [ // FLAGS.MULTI_AND = 4
            {flag: FLAGS.TAG, name: 'domain', value: 'domain.com'}, // FLAGS.TAG = 3
            {flag: FLAGS.TAG, name: 'lang', value: 'pl'} // FLAGS.TAG = 3
        ]}
    });
    
    fcs.setTag('domain', 'domain.com');
    fcs.setTag('lang', 'en');
 
    if (fcs.feature('my-feature')) { // true
        // available only if domain tag is equal "domain.com" AND lang tag is "en"
    }
 
    if (fcs.feature('my-feature2')) { // false
        // available only if domain tag is equal "domain.com" AND lang tag is "pl"
    }
  • FLAG_MULTI_OR

Multi-or flag allows to define group of multiple flags and means that feature is enabled if any of the subflags meets its condition

    fcs.setFlags({
        'my-feature': {flag: FLAGS.MULTI_OR, flags: [ // FLAGS.MULTI_OR = 5
            {flag: FLAGS.TAG, name: 'domain', value: 'domain.com'}, // FLAGS.TAG = 3
            {flag: FLAGS.TAG, name: 'domain', value: 'domain2.com'} // FLAGS.TAG = 3
        ]},
        'my-feature2': {flag: FLAGS.MULTI_OR, flags: [ // FLAGS.MULTI_OR = 5
            {flag: FLAGS.TAG, name: 'domain', value: 'domain3.com'}, // FLAGS.TAG = 3
            {flag: FLAGS.TAG, name: 'domain', value: 'domain4.com'} // FLAGS.TAG = 3
        ]}
    });
    
    fcs.setTag('domain', 'domain.com');
 
    if (fcs.feature('my-feature')) { // true
        // available only if domain tag is equal "domain.com" OR "domain2.com"
    }
 
    if (fcs.feature('my-feature2')) { // false
        // available only if domain tag is equal "domain3.com" OR "domain4.com"
    }

Examples:

    import {FCS, FLAGS} from 'fcs-js';
 
    const fcs = new FCS();
 
    // Define initial settings for your environment + project + user, usually set just once at the beginning
    fcs.setTag('env', 'preproduction');
    fcs.setTag('username', 'test123');
    fcs.setTag('IP', '0.0.0.0');
    fcs.setTag('role', 'user');
    fcs.setTag('country', 'UK');
    fcs.setTag('product', 'product1');
    fcs.setTag('domain', 'domain.com');
    fcs.setTag('lang', 'en');
    fcs.setCookies({
        'fcs-my-carousel': '1'
    });
    
    // Sometimes you can define also extra tags later
    fcs.setTag('section', 'promotions');
    fcs.setTag('promotion', '1');
    
    // Define list of features from array (loaded from configuration file or cache provider e.g. redis)
    fcs.setFlags({
        'experiment': {flag: FLAGS.INACTIVE},
        'blue-background': {flag: FLAGS.TAG, name: 'country', value: ['UK', 'DE']},
        'xmas-promo': {flag: FLAGS.MULTI_AND, flags: [
            {flag: FLAGS.TAG, name: 'domain', value: 'domain.com'},
            {flag: FLAGS.TAG, name: 'lang', value: 'pl'}
        ]},
        'chat': {flag: FLAGS.MULTI_OR, flags: [
            {flag: FLAGS.COOKIE},
            {flag: FLAGS.TAG, name: 'env', value: ['test123', 'test456']}
        ]},
        'auth': {flag: FLAGS.ACTIVE},
        'new-registration': {flag: FLAGS.TAG, name: 'env', value: 'preproduction'},
        'my-carousel': {flag: FLAGS.COOKIE},
        'new-bonus': {flag: FLAGS.TAG, name: 'env', value: ['test123', 'test456']}
    });
    
    // Get list of features to inject to your frontend to JS variable
    const activeFeatures = fcs.getActiveFeatures(); // ['blue-background', 'chat', 'auth', 'new-registration', 'new-bonus']
    
    // Get CSS classes to append them to <body> element
    const cssClasses = fcs.getBodyCssClasses(); // 'fcs-blue-background fcs-chat fcs-auth fcs-new-registration fcs-new-bonus'
    
    // Activate features in your app
    if (fcs.feature('auth')) { // true
        // active feature
    }
    if (fcs.feature('experiment')) { // false
        // inactive feature
    }
    if (fcs.feature('my-carousel')) { // true
        // feature active if cookie['fcs-my-carousel'] cookie is enabled
    }
    if (fcs.feature('new-bonus')) { // true
        // feature active for this user when logged in (test123)
    } else {
        // old feature for everyone else
    }
    if (fcs.feature('new-registration')) { // true
        // feature active in preproduction environment
    }
    if (fcs.feature('xmas-promo')) { // false
        // feature active if both tags domain AND lang meet the current settings
    }
    if (fcs.feature('chat')) { // true
        // feature active if cookie is enabled (cookie['fcs-chat']) OR if one of listed users is logged in
    }

FCSClient

Configuration methods:

Example of code to use feature flags on frontend side:

    import {FCSClient} from 'fcs-js';
    
    const ajaxResponseFeatures = ['my-feature', 'my-feature2', 'my-feature3'];
    
    FCSClient.setActiveFeatures(ajaxResponseFeatures);
  • setActiveFeatures

Set names of features returned by server

    FCSClient.setActiveFeatures(<array:string>);
    
    // e.g. (['my-feature', 'my-feature2', 'my-feature3'])

Getter methods:

  • feature

Check if feature is active and use returned boolean to enable some code

    // types
    function feature(name: string): boolean;
    
    // example
    if (FCSClient.feature('my-feature')) {
        // code is enabled
    }
  • multiFeature

More flexible version of feature() method that helps to easier accommodate complex conditions on frontend side

It accepts:

  • string (one feature)
  • string (comma-separated list of features)
  • array (list of features)
    // types
    function multiFeature(name: string | string[]): boolean;
    
    // example
    if (FCSClient.multiFeature('my-feature')) {
        // code is enabled
    }
    if (FCSClient.multiFeature('my-feature,my-feature2')) {
        // code is enabled
    }
    if (FCSClient.multiFeature(['my-feature', 'my-feature2'])) {
        // code is enabled
    }
  • getBodyCssClasses

Get string with list of CSS classes that can be appended to body element in your frontend

Returned CSS classes have "fcs-" prefix

Note: slash (/) in name of feature is automatically turned into double dash (--)

    // types
    function getBodyCssClasses(): string;
    
    // example
    const cssClasses = FCSClient.getBodyCssClasses(); //'fcs-my-feature fcs-my-feature2 fcs-test--my-feature100'
  • getActiveFeatures

Get array of names of active features

    // types
    function getActiveFeatures(): string[];
    
    // example
    const features = FCSClient.getActiveFeatures(); //['my-feature', 'my-feature2', 'my-feature100']

Examples:

    import {FCSClient} from 'fcs-js';
    
    FCSClient.setActiveFeatures(['chat', 'new-menu']);
    
    document.body.className = FCSClient.getBodyCssClasses(); // 'fcs-chat fcs-new-menu'
    
    if (FCSClient.feature('chat')) {
        console.log('chat is active');
    }
    
    if (FCSClient.multiFeature(['new-menu', 'new-sidebar'])) {
        console.log('new-menu or new-sidebar is active');
    }

License

MIT License

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 2.4.5
    2
    • latest

Version History

Package Sidebar

Install

npm i fcs-js

Weekly Downloads

2

Version

2.4.5

License

MIT

Unpacked Size

249 kB

Total Files

41

Last publish

Collaborators

  • jerzy.dominik.ogonowski