New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

muton

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

muton - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

src/mutators/gene-pairing.js

2

package.json
{
"name": "muton",
"version": "0.0.2",
"version": "0.0.3",
"description": "A feature toggling/throttling and multivariate testing tool",

@@ -5,0 +5,0 @@ "main": "src/muton.js",

@@ -47,3 +47,3 @@ Muton

```javascript
var features = muton.getFeatureMutations(userProperties, featureInstructions);
var features = muton.getMutations(userProperties, featureInstructions);
```

@@ -81,5 +81,8 @@

```javascript
var featureInstructions = {
'superCoolFeature' : true,
'anotherCoolFeature' : false
{
'toggles': {
'superCoolFeature' : true,
'anotherCoolFeature' : false
}
}
```

@@ -116,3 +119,13 @@

```
A valid response for a portuguese user could be:
```javascript
{
'toggles' : {
'superCoolFeature' : true,
},
'throttles': ['superCoolFeature']
}
```
#### Buckets (A/B testing, multivariant testing)

@@ -143,8 +156,130 @@

```javascript
var featureInstructions = {
'superCoolFeature' : true,
'superCoolFeature.smallButton' : true
{
'toggles' : {
'superCoolFeature' : true,
'superCoolFeature.smallButton' : true
},
'buckets': ['superCoolFeature.smallButton']
}
```
### Matchers
Muton supports non-trivial user properties matching. For example, you might want to toggle a feature on when the user bought over 10000 books in your store. Or even to activate a feature for users whose referral site belongs to a specific domain.
#### Regular expressions
To have user properties being matched by regexes, you must enclose the regex inside two '/'. The above example referral example with Google can be written like this:
```javascript
var featureInstructions = {
'superCoolFeature' : {
'referral' : {
'/.*\.google\..{2,4}/' : {
'toggle' : 'true'
}
}
}
}
```
#### Numeric quantifiers
Numeric user properties can be matched against an expression. The property value must start with one of the following operators: '>', '<', '>=', '<='.
Picking the bookstore example, a user with over 10000 bought books would have a special feature toggle on:
```javascript
var featureInstructions = {
'superCoolFeature' : {
'boughtBooks' : {
'>=10000' : {
'toggle' : 'true'
}
}
}
}
```
### Gene inheritance
To inherit properties from previous mutations, you can use gene inheritance. This is useful when you want to maintain the same users on the same buckets or throttles to guarantee consistency in your features when the context changes.
This is only applied to instructions that are subject to mutations, namely 'buckets' and 'throttles'. Inherited mutations from ancestors will only work when the new instructions and ancestor genes have the same kind of mutations (e.g.: ancestor and predecessor have a throttle on the same feature). For plain toggles, ancestor genes will be ignored.
To inherit genes from an ancestor, you can use Muton in the following way:
```javascript
var features = muton.inheritMutations(userProperties, featureInstructions, ancestorGenes);
```
#### Throttle inheritance
For example, to inherit throttles, one could pass a set of instructions and a set of ancestor genes:
```javascript
var featureInstructions = {
'superCoolFeature' : {
'toggle' : true,
'location' : {
'PT' : {
'throttle' : '50%'
}
}
}
}
```
You can use a previous Muton output to feed new mutations and inherit them from ancestors. The default behaviour is to inherit from ancestor. In the bellow example, Muton will always return true for the 'superCoolFeature' throttle, because the previous throttle was enabled.
```javascript
var ancestorGenes = {
'toggles' : {
'superCoolFeature' : true
},
'throttles' : ['superCoolFeature']
};
```
If you choose to force mutations when inheriting throttles on a specific context, you can create a throttle object instead of a percentage value and pass a 'mutate' property and ignore the ancestor gene. You must specify it in the instructions:
```javascript
var featureInstructions = {
'superCoolFeature' : {
'toggle' : true,
'location' : {
'PT' : {
'throttle' : '50%',
'mutate': 'force'
}
}
}
}
```
#### Bucket inheritance
To inherit buckets, you can pass a set of ancestor genes to Muton containing buckets:
```javascript
var featureInstructions = {
'superCoolFeature' : {
'toggle' : true,
'location' : {
'PT' : {
'buckets' : ['bigRedButton', 'mediumSizeButton', 'smallButton']
}
}
}
}
```
The ancestor genes (a previous Muton output) could be passed to Muton and the predecessor will inherit the ancestor bucket. However, if the ancestor gene contains a bucket that is not defined in the predecessor instructions, then a bucket mutation will occur and a random bucket from the new instructions will be picked up.
```javascript
var ancestorGenes = {
'toggles' : {
'superCoolFeature.bigRedButton' : true
},
'buckets' : ['superCoolFeature.bigRedButton']
};
```
## Build

@@ -170,6 +305,11 @@

* 0.0.1 - Initial release
* 0.0.2 - Fixing bucket choice bug
* 0.0.1
- Initial release
* 0.0.2
- Fixing bucket choice bug
* 0.1.0
- Adding support for regex and numeric matchers
- Adding support for gene inheritance
[npm-url]: https://npmjs.org/package/muton
[npm-image]: https://badge.fury.io/js/muton.svg

@@ -15,3 +15,3 @@ 'use strict';

var chemicalReactions = require('./../reactions/chemical.js');
var chemicalReactions = require('../reactions/chemical.js');

@@ -18,0 +18,0 @@ return {

@@ -19,18 +19,24 @@ 'use strict';

define(function (require) {
var bucketMutator = require('./../mutators/bucket');
var throttleMutator = require('./../mutators/throttle');
var proofReader = require('./../reactions/proof-reading');
var _ = require('lodash');
var bucketMutator = require('../mutators/bucket');
var throttleMutator = require('../mutators/throttle');
var genePairing = require('../mutators/gene-pairing');
var proofReader = require('../reactions/proof-reading');
function addToFeatures(features, featureName, toggle) {
features[featureName] = toggle;
return features.push(_.merge({ name: featureName }, toggle));
}
function processFeatureInstructions(featureProperties) {
var toggle = false;
function processFeatureInstructions(featureProperties, gene) {
var toggle = {
type: 'toggle',
toggle: false
};
if (featureProperties.toggle !== false) {
if (throttleMutator.isThrottleValid(featureProperties.throttle)) {
toggle = throttleMutator.mutate(featureProperties.throttle);
toggle.toggle = throttleMutator.mutate(featureProperties.throttle, gene);
toggle.type = 'throttle';
} else if (featureProperties.toggle === true) {
toggle = true;
toggle.toggle = true;
}

@@ -43,8 +49,14 @@ }

function containsBuckets(toggle, featureInstructions) {
return toggle && bucketMutator.containsMultivariant(featureInstructions);
return toggle.toggle && bucketMutator.containsMultivariant(featureInstructions);
}
function addBucketToFeatures(features, featureName, featureInstructions, toggle) {
var bucketName = bucketMutator.mutate(featureInstructions);
addToFeatures(features, featureName + "." + bucketName, toggle);
function addBucketToFeatures(features, featureName, featureInstructions, toggle, gene) {
var bucketName = bucketMutator.mutate(featureInstructions, gene);
var bucketToggle = {
toggle : toggle.toggle,
type : 'bucket'
};
addToFeatures(features, featureName + "." + bucketName, bucketToggle);
}

@@ -60,16 +72,20 @@

* @returns A resolved feature toggle, which may mutate to a bucket feature toggle
* @param ancestorGenes An object containing 'genes' to inherit, this only applies to throttles and buckets
*/
assembleFeatures: function(featureName, primerInstructions) {
var features = {};
assembleFeatures: function(featureName, primerInstructions, ancestorGenes) {
var features = [];
if (proofReader.areInstructionsValid(primerInstructions)) {
var toggle = processFeatureInstructions(primerInstructions);
// Get the ancestor gene based on name to be able to copy it to the descendant
var gene = genePairing.pairGene(ancestorGenes, featureName);
var toggle = processFeatureInstructions(primerInstructions, gene);
addToFeatures(features, featureName, toggle);
if (containsBuckets(toggle, primerInstructions)) {
addBucketToFeatures(features, featureName, primerInstructions, toggle);
addBucketToFeatures(features, featureName, primerInstructions, toggle, gene);
}
} else {
console.log('There are invalid feature instructions!');
addToFeatures(features, featureName, false);
addToFeatures(features, featureName, { toggle: false, type: 'toggle' });
}

@@ -76,0 +92,0 @@ return features;

@@ -15,3 +15,4 @@ 'use strict';

var _ = require('lodash');
var chemicalReactions = require('./../reactions/chemical.js');
var chemicalReactions = require('../reactions/chemical.js');
var matchReading = require('../reactions/match-reading.js');

@@ -25,3 +26,3 @@ function mergeProperties(primer, feature) {

var toggle = _.get(primer, 'toggle');
return root && (_.isUndefined(toggle) || toggle === false);
return root && toggle === false;
}

@@ -38,6 +39,8 @@

function getPropertiesNode(userProperties, featurePropertyName, feature) {
var propertyName = featurePropertyName;
// Explode the current node to check if there are properties
var featureProperty = feature[propertyName];
var properties = featureProperty[userProperties[propertyName]];
var featureProperty = feature[featurePropertyName];
var userPropertyValue = userProperties[featurePropertyName];
var properties = matchReading.getMatchedProperties(userPropertyValue, featureProperty);
return pickMatchedProperties(properties, featureProperty);

@@ -50,2 +53,6 @@ }

function bindPrimer(primerInstructions, childPrimer) {
_.merge(primerInstructions, childPrimer);
}
return {

@@ -81,3 +88,4 @@ /**

var childPrimer = self.preparePrimer(userProperties, propertiesNode, childStrands);
_.merge(primerInstructions, childPrimer);
bindPrimer(primerInstructions, childPrimer);
}

@@ -84,0 +92,0 @@ });

@@ -16,5 +16,26 @@ 'use strict';

function pickOneElement(array) {
if (!_.isArray(array)) {
throw 'Not an array!';
}
var index = Math.floor(Math.random() * (array.length));
return array[index];
}
function isBucketGene(gene) {
return !_.isEmpty(gene) && gene.type === 'bucket';
}
function containsGene(featureProperties, gene) {
return _.includes(featureProperties.buckets, gene.toggle);
}
return {
mutate: function(featureProperties) {
return this.pickOneElement(featureProperties.buckets);
mutate: function(featureProperties, gene) {
if (isBucketGene(gene) && containsGene(featureProperties, gene)) {
return gene.toggle;
} else {
return pickOneElement(featureProperties.buckets);
}
},

@@ -29,13 +50,4 @@

},
pickOneElement: function(array) {
if (!_.isArray(array)) {
throw 'Not an array!';
}
var index = Math.floor(Math.random() * (array.length));
return array[index];
}
};
});

@@ -15,21 +15,52 @@ 'use strict';

function getPercentageDecimal(throttle) {
var percentage = extractPercentage(throttle);
var value = percentage.substr(0, percentage.length - 2);
return value / 10;
}
function extractPercentage(throttle) {
var percentage;
if (isThrottleNode(throttle)) {
percentage = throttle['value'];
} else {
percentage = throttle;
}
return percentage;
}
function isThrottleValid(throttle) {
return isThrottleNode(throttle) || isPercentage(throttle);
}
function isThrottleNode(throttle) {
return _.isPlainObject(throttle) && _.isString(throttle['value']) && isPercentage(throttle['value']);
}
function isPercentage(value) {
return !_.isUndefined(value) && _.isString(value) && value.match(/[0-100]%/);
}
function isThrottleGene(gene) {
return !_.isEmpty(gene) && gene.type === 'throttle';
}
function shouldMutate(throttle) {
return !_.isUndefined(throttle['mutate']) && throttle['mutate'] === 'force';
}
return {
mutate: function (throttle) {
var percentage = this.getPercentageDecimal(throttle);
return Math.random() < percentage;
mutate: function (throttle, gene) {
if (!shouldMutate(throttle) && isThrottleGene(gene)) {
return gene.toggle;
} else {
var percentage = getPercentageDecimal(throttle);
return Math.random() < percentage;
}
},
isThrottleValid: function (throttle) {
return !_.isUndefined(throttle) && this.isPercentage(throttle);
},
isPercentage: function (value) {
return value.match(/[0-100]%/);
},
getPercentageDecimal: function (percentage) {
var value = percentage.substr(0, percentage.length - 2);
return value / 10;
return isThrottleValid(throttle);
}
};
});

@@ -22,3 +22,3 @@ /**

*
* If you want to get a little deeper, please have a look at:
* If you want to get a little deeper, please have a look at:
* http://www.nature.com/scitable/topicpage/cells-can-replicate-their-dna-precisely-6524830

@@ -45,14 +45,73 @@ */

function joinToggles(features, resolvedFeatures) {
features.toggles = _.reduce(resolvedFeatures, function (result, elem) {
result[elem.name] = elem.toggle;
return result;
}, features.toggles);
}
function joinThrottles(features, resolvedFeatures) {
var buckets = _.chain(resolvedFeatures)
.filter({type : 'bucket'})
.pluck('name')
.value();
features.buckets = features.buckets.concat(buckets);
}
function joinBuckets(features, resolvedFeatures) {
var throttles = _.chain(resolvedFeatures)
.filter({type : 'throttle'})
.pluck('name')
.value();
features.throttles = features.throttles.concat(throttles);
}
function joinMutations(features, resolvedFeatures) {
joinToggles(features, resolvedFeatures);
joinThrottles(features, resolvedFeatures);
joinBuckets(features, resolvedFeatures);
}
var muton = {
/**
* Given a list of user properties and feature instructions, it returns a collections of features toggles.
* Given a list of user properties and feature instructions, it returns a collection of features toggles.
*
* @param userProperties A collection of user properties
* @deprecated use getMutations or inheritMutations instead
*
* @param userProperties (optional) A collection of user properties
* @param featureInstructions A collection of feature instructions which can be organized as a hierarchy of properties.
* @returns An collection of feature toggles that are toggled on or off
* @returns An collection of feature toggles
*/
getFeatureMutations: function (userProperties, featureInstructions) {
var features = {};
return this.getMutations(userProperties, featureInstructions).toggles;
},
/**
* Given a list of user properties and feature instructions, it returns a collection of features toggles.
*
* @param userProperties (optional) A collection of user properties
* @param featureInstructions A collection of feature instructions which can be organized as a hierarchy of properties.
* @returns {{toggles: {}, buckets: Array, throttles: Array}} An collection of feature toggles
*/
getMutations: function (userProperties, featureInstructions) {
return this.inheritMutations(userProperties, featureInstructions, {});
},
/**
* Given a list of user properties and feature instructions, it returns a collection of features toggles. If specified,
* it can inherit ancestor genes for buckets and throttle mutations
*
* @param userProperties (optional) A collection of user properties
* @param featureInstructions A collection of feature instructions which can be organized as a hierarchy of properties.
* @param ancestorGenes (optional) The ancestor genes, which is the output of previous mutations from Muton
* @returns {{toggles: {}, buckets: Array, throttles: Array}} An collection of feature toggles
*/
inheritMutations: function (userProperties, featureInstructions, ancestorGenes) {
var features = {
toggles: {},
buckets: [],
throttles: []
};
proofReading.checkFeatureInstructions(featureInstructions);

@@ -68,4 +127,6 @@

// Pick the primer, proof-read the instructions and then assemble the collection of feature toggles
var resolvedFeatures = polymerase.assembleFeatures(featureName, primer);
_.merge(features, resolvedFeatures);
var resolvedFeatures = polymerase.assembleFeatures(featureName, primer, ancestorGenes);
// Join all the mutations
joinMutations(features, resolvedFeatures);
});

@@ -72,0 +133,0 @@

@@ -14,4 +14,4 @@ 'use strict';

var _ = require('lodash');
var bucketMutator = require('./../mutators/bucket');
var throttleMutator = require('./../mutators/throttle');
var bucketMutator = require('../mutators/bucket');
var throttleMutator = require('../mutators/throttle');

@@ -40,5 +40,4 @@ return {

checkFeatureInstructions: function (featureInstructions) {
var valid = _.isUndefined(featureInstructions) ||
_.isNull(featureInstructions) ||
_.isPlainObject(featureInstructions);
var valid = _.isUndefined(featureInstructions) || _.isNull(featureInstructions) || !_.isArray(featureInstructions) && _.isObject(featureInstructions);
if (!valid) {

@@ -49,2 +48,2 @@ throw new Error('Invalid feature instructions!');

};
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc