Neatly Positioned Magazines

    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

    Install

    npm i fcs-js

    DownloadsWeekly Downloads

    7

    Version

    2.4.5

    License

    MIT

    Unpacked Size

    249 kB

    Total Files

    41

    Last publish

    Collaborators

    • jerzy.dominik.ogonowski