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

smart-mix

v3.0.2

Published

OOP mixin tool like no other

Downloads

33

Readme

smart-mix

OOP composition-based mixin tool like no other.

Contents

Installation

npm i smart-mix

Properties

  • mixes in public methods
  • mixes in private/shared methods
  • allows mixing in data and accessor properties
  • allows sharing private state and public state
  • each instance of the mixin has its own state
  • avoids keys collisions
  • works both with symbols and string keys
  • avoids the gorilla-banana problem by getting only the methods you need
  • works both in function contexts and class contexts
  • you clearly see what your object has mixed in in its definition context
  • you clearly see where your mixed in capabilities come from

Advantages over other mixin approaches

The common ways mixins have been implemented so far were:

  • naive approach - Object.assign, Reactjs mixins, Typescript mixins
  • based on prototypal inheritance
  • functional mixins

The main downsides of these approaches are:

  • Not being able to easily tell what your class/object can do by simply looking at its definition context (and having to jump to different files)
  • Not being able to easily tell where your class'/object's capabilities come from by simply looking at its definition context
  • Avoiding property collisions by making the last mixin object always win (which is not the best solution)
  • The gorilla-banana problem. You automatically get all the methods in the mixin objects even though you might not need all of them. This is a violation of the least knowledge principle.
  • The inability of sharing private state between the mixin context and the mixin functions.

The first 4 issues are solved in a composition based mixin mechanism by explicitly picking every mixin function.

Examples

Example 1: simple example with sharing methods with two different contexts

// chocolate eater mixin file

import mixin from 'smart-mix';

export default mixin(() => ({
    eatChocolate() {
        console.log('eating chocolate');
    },

    initiateTummyPain() {
        throw new Error('My tummy hurts!');
    }
}));
// dancer mixin file

import mixin from 'smart-mix';

export default mixin(() => ({
    dance() {
        console.log('dancing');
    }
}));
import chocolateEater from './chocolate-eater-mixin';
import dancer from './dancer-mixin';

class Bob {
    constructor() {
        chocolateEater({
            publicContext: this,
            publicMethods: ['eatChocolate', 'initiateTummyPain']
        });
        dancer({
            publicContext: this,
            publicMethods: ['dance']
        });
    }
}

class Alice {
    constructor() {
        chocolateEater({
            publicContext: this,
            publicMethods: ['eatChocolate']
        });
        dancer({
            publicContext: this,
            publicMethods: ['dance']
        });
    }
}

const bob = new Bob();
const alice = new Alice();

bob.dance(); // dancing
alice.dance(); // dancing
bob.eatChocolate(); // eating chocolate
alice.eatChocolate(); // eating chocolate
bob.initiateTummyPain(); // throws My tummy hurts!
'initiateTummyPain' in alice; // false - alice will not have any tummy pain

Example 2: share private state between two mixin providers

// woman secret keeper mixin

import mixin from 'smart-mix';

export default mixin((publicContext, sharedContext) => {
    return {
        womanUpdateSecret() {
            sharedContext.secret = 'woman secret';
        }
    };
});
// man secret keeper mixin

import mixin from 'smart-mix';

export default mixin((publicContext, sharedContext) => {
    return {
        manUpdateSecret() {
            sharedContext.secret = 'man secret';
        }
    };
});
import womanSecretKeeper from './woman-secret-keeper-mixin';
import manSecretKeeper from './man-secret-keeper-mixin';

const hybrid = (() => {
    const hybrid = {
        getSecret() {
            console.log(secretBox.secret);
        }
    };

    const secretBox = {};
    const womanMix = womanSecretKeeper({
        sharedContext: secretBox,
        mixMethods: ['womanUpdateSecret']
    });
    const manMix = manSecretKeeper({
        sharedContext: secretBox,
        mixMethods: ['manUpdateSecret']
    });

    womanMix.womanUpdateSecret();
    hybrid.getSecret(); // woman secret
    manMix.manUpdateSecret();

    return hybrid;
})();

hybrid.getSecret(); // man secret

Example 3: each context has its own state

// number mixin

import mixin from 'smart-mix';

export default mixin((publicContext, sharedContext) => {
    let privateMixinProviderNumber;

    return {
        setPrivateMixinProviderNumber(number) {
            privateMixinProviderNumber = number;
        },

        updateSharedNumber(number) {
            sharedContext.number = number;
        },

        getSumResult() {
            return privateMixinProviderNumber + sharedContext.number;
        }
    };
});
import numberMixin from './number-mixin';

const publicContext1 = {};
const sharedContext1 = {};

const publicContext2 = {};
const sharedContext2 = {};

numberMixin({
    publicContext: publicContext1,
    sharedContext: sharedContext1,
    publicMethods: [
        'setPrivateMixinProviderNumber', 
        'updateSharedNumber', 
        'getSumResult'
    ]
});

numberMixin({
    publicContext: publicContext2,
    sharedContext: sharedContext2,
    publicMethods: [
        'setPrivateMixinProviderNumber', 
        'updateSharedNumber', 
        'getSumResult'
    ]
});

publicContext1.setPrivateMixinProviderNumber(100);
publicContext1.updateSharedNumber(1000);
publicContext2.setPrivateMixinProviderNumber(200);
publicContext2.updateSharedNumber(2000);
console.log(publicContext1.getSumResult()); // 1100
console.log(publicContext2.getSumResult()); // 2200

Concepts

  • The mixin function, which is provided by smart-mix.
import mixin from 'smart-mix';

mixin();
  • The mixin provider function is obtained by calling the mixin function.
const mixinProviderFunction = mixin();
  • The mixin callback is passed to the mixin function in order to obtain the mixin provider function and it must return the mixin methods container object.
mixin(() => {
    return {
        method() {}
    }
});
  • The optional mixin public context is passed to the mixin callback and it becomes the target of the optional public methods.
const publicContext = {
    existingMethod() { return 2; }
};

const mixinProviderFunction = mixin((publicContext) => {
    return {
        method() {
            return publicContext.existingMethod() + 1;
        }
    };
});

mixinProviderFunction({
    publicContext,
    publicMethods: ['method']
});

publicContext.method(); // 3
  • The optional mixin shared context is the second argument passed to the mixin callback and it can contain any other state, including state that is private in the context in which the mixin public context is created.
const publicContext = (() => {
    const publicContext = {};
    const sharedContext = {
        property: 'private message'
    };

    const mixinProviderFunction = mixin((publicContext, sharedContext) => {
        return {
            method() {
                return sharedContext.property;
            }
        };
    })

    mixinProviderFunction({
        publicContext, 
        sharedContext,
        publicMethods: ['method']
    });

    return publicContext;
})();

publicContext.method(); // 'private message'
  • The mix object is returned by the mixin provider function and is the target of the optional mix methods. This mix object can stay private in the context in which the mixin public context was created so that the mixin public context can use private methods.
const publicContext = (() => {
    const publicContext = {
        existingMethod() { return mix.privateMethod(); }
    };

    const mixinProviderFunction = mixin(() => {
        return {
            privateMethod() {
                return 'private message';
            }
        };
    })

    const mix = mixinProviderFunction({
        mixMethods: ['privateMethod']
    });

    return publicContext;
})();

publicContext.existingMethod(); // 'private message'
  • Optionally, using the define option the resulted mixin provider function can have static methods attached that allows us mix in data and accessor properties. We can use a getPublicContext static method that provides properties for the mixin public context object. Also, a getSharedContext static method that provides properties for the mixin shared context object. These two functions receive the mixin public context and shared context objects and return an object whose properties will be installed on the mixin public context object or mixin shared context object by applying the same property descriptors of the returned object. This way we can install accessor properties on the mixin public context and shared context objects.
const publicContext = (() => {
    const publicContext = {
        x: 1
    };
    const sharedContext = {
        y: 2
    };

    const mixinProviderFunction = mixin((publicContext, sharedContext) => {
        return {
            getResult() {
                return publicContext.sum;
            }
        };
    })

    mixinProviderFunction.getPublicContext = (publicContext, sharedContext) => ({
        z: 3,

        get sum() {
            return publicContext.x + publicContext.z + sharedContext.y + sharedContext.u;
        }
    });

    mixinProviderFunction.getSharedContext = () => ({
        u: 4
    });

    const mix = mixinProviderFunction({
        define: true,
        publicContext,
        sharedContext,
        publicMethods: ['getResult']
    });

    return publicContext;
})();

console.log(publicContext.getResult()); // 10