flagsmith-nodejs
Version:
Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.
214 lines (213 loc) • 9.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SegmentModel = exports.SegmentRuleModel = exports.SegmentConditionModel = exports.getMatchingFunctions = exports.semverMatchingFunction = exports.matchingFunctions = exports.any = exports.all = void 0;
const semver = require("semver");
const models_js_1 = require("../features/models.js");
const index_js_1 = require("../utils/index.js");
const constants_js_1 = require("./constants.js");
const util_js_1 = require("./util.js");
const constants_js_2 = require("../features/constants.js");
const models_js_2 = require("../evaluation/models.js");
const all = (iterable) => iterable.filter(e => !!e).length === iterable.length;
exports.all = all;
const any = (iterable) => iterable.filter(e => !!e).length > 0;
exports.any = any;
exports.matchingFunctions = {
[constants_js_1.CONDITION_OPERATORS.EQUAL]: (thisValue, otherValue) => thisValue == otherValue,
[constants_js_1.CONDITION_OPERATORS.GREATER_THAN]: (thisValue, otherValue) => otherValue > thisValue,
[constants_js_1.CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue, otherValue) => otherValue >= thisValue,
[constants_js_1.CONDITION_OPERATORS.LESS_THAN]: (thisValue, otherValue) => thisValue > otherValue,
[constants_js_1.CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue, otherValue) => thisValue >= otherValue,
[constants_js_1.CONDITION_OPERATORS.NOT_EQUAL]: (thisValue, otherValue) => thisValue != otherValue,
[constants_js_1.CONDITION_OPERATORS.CONTAINS]: (thisValue, otherValue) => {
try {
return !!otherValue && otherValue.includes(thisValue);
}
catch {
return false;
}
}
};
// Semver library throws an error if the version is invalid, in this case, we want to catch and return false
const safeSemverCompare = (semverMatchingFunction) => {
return (conditionValue, traitValue) => {
try {
return semverMatchingFunction(conditionValue, traitValue);
}
catch {
return false;
}
};
};
exports.semverMatchingFunction = {
...exports.matchingFunctions,
[constants_js_1.CONDITION_OPERATORS.EQUAL]: safeSemverCompare((conditionValue, traitValue) => semver.eq(traitValue, conditionValue)),
[constants_js_1.CONDITION_OPERATORS.GREATER_THAN]: safeSemverCompare((conditionValue, traitValue) => semver.gt(traitValue, conditionValue)),
[constants_js_1.CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: safeSemverCompare((conditionValue, traitValue) => semver.gte(traitValue, conditionValue)),
[constants_js_1.CONDITION_OPERATORS.LESS_THAN]: safeSemverCompare((conditionValue, traitValue) => semver.lt(traitValue, conditionValue)),
[constants_js_1.CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: safeSemverCompare((conditionValue, traitValue) => semver.lte(traitValue, conditionValue))
};
const getMatchingFunctions = (semver) => semver ? exports.semverMatchingFunction : exports.matchingFunctions;
exports.getMatchingFunctions = getMatchingFunctions;
class SegmentConditionModel {
EXCEPTION_OPERATOR_METHODS = {
[constants_js_1.NOT_CONTAINS]: 'evaluateNotContains',
[constants_js_1.REGEX]: 'evaluateRegex',
[constants_js_1.MODULO]: 'evaluateModulo',
[constants_js_1.IN]: 'evaluateIn'
};
operator;
value;
property;
constructor(operator, value, property) {
this.operator = operator;
this.value = value;
this.property = property;
}
matchesTraitValue(traitValue) {
const evaluators = {
evaluateNotContains: (traitValue) => {
return (typeof traitValue == 'string' &&
!!this.value &&
!traitValue.includes(this.value?.toString()));
},
evaluateRegex: (traitValue) => {
try {
if (!this.value) {
return false;
}
const regex = new RegExp(this.value?.toString());
return !!traitValue?.toString().match(regex);
}
catch {
return false;
}
},
evaluateModulo: (traitValue) => {
const parsedTraitValue = parseFloat(traitValue);
if (isNaN(parsedTraitValue) || !this.value) {
return false;
}
const parts = this.value.toString().split('|');
if (parts.length !== 2) {
return false;
}
const divisor = parseFloat(parts[0]);
const remainder = parseFloat(parts[1]);
if (isNaN(divisor) || isNaN(remainder) || divisor === 0) {
return false;
}
return parsedTraitValue % divisor === remainder;
},
evaluateIn: (traitValue) => {
if (!traitValue || typeof traitValue === 'boolean') {
return false;
}
if (Array.isArray(this.value)) {
return this.value.includes(traitValue.toString());
}
if (typeof this.value === 'string') {
try {
const parsed = JSON.parse(this.value);
if (Array.isArray(parsed)) {
return parsed.includes(traitValue.toString());
}
}
catch { }
}
return this.value?.split(',').includes(traitValue.toString());
}
};
// TODO: move this logic to the evaluator module
if (this.EXCEPTION_OPERATOR_METHODS[this.operator]) {
const evaluatorFunction = evaluators[this.EXCEPTION_OPERATOR_METHODS[this.operator]];
return evaluatorFunction(traitValue);
}
const defaultFunction = (x, y) => false;
const matchingFunctionSet = (0, exports.getMatchingFunctions)((0, util_js_1.isSemver)(this.value));
const matchingFunction = matchingFunctionSet[this.operator] || defaultFunction;
const traitType = (0, util_js_1.isSemver)(this.value) ? 'semver' : typeof traitValue;
const castToTypeOfTraitValue = (0, index_js_1.getCastingFunction)(traitType);
return matchingFunction(castToTypeOfTraitValue(this.value), traitValue);
}
}
exports.SegmentConditionModel = SegmentConditionModel;
class SegmentRuleModel {
type;
rules = [];
conditions = [];
constructor(type) {
this.type = type;
}
static none(iterable) {
return iterable.filter(e => !!e).length === 0;
}
matchingFunction() {
return {
[constants_js_1.ANY_RULE]: exports.any,
[constants_js_1.ALL_RULE]: exports.all,
[constants_js_1.NONE_RULE]: SegmentRuleModel.none
}[this.type];
}
}
exports.SegmentRuleModel = SegmentRuleModel;
class SegmentModel {
id;
name;
rules = [];
featureStates = [];
constructor(id, name) {
this.id = id;
this.name = name;
}
static fromSegmentResult(segmentResults, evaluationContext) {
const segmentModels = [];
if (!evaluationContext.segments) {
return [];
}
for (const segmentResult of segmentResults) {
if (segmentResult.metadata?.source === models_js_2.SegmentSource.IDENTITY_OVERRIDE) {
continue;
}
const segmentMetadataId = segmentResult.metadata?.id;
if (!segmentMetadataId) {
continue;
}
const segmentContext = evaluationContext.segments[segmentMetadataId.toString()];
if (segmentContext) {
const segment = new SegmentModel(segmentMetadataId, segmentContext.name);
segment.rules = segmentContext.rules.map(rule => new SegmentRuleModel(rule.type));
segment.featureStates = SegmentModel.createFeatureStatesFromOverrides(segmentContext.overrides || []);
segmentModels.push(segment);
}
}
return segmentModels;
}
static createFeatureStatesFromOverrides(overrides) {
if (!overrides)
return [];
return overrides
.filter(override => {
const overrideMetadataId = override?.metadata?.id;
return typeof overrideMetadataId === 'number';
})
.map(override => {
const overrideMetadataId = override.metadata.id;
const feature = new models_js_1.FeatureModel(overrideMetadataId, override.name, override.variants?.length && override.variants.length > 0
? constants_js_2.CONSTANTS.MULTIVARIATE
: constants_js_2.CONSTANTS.STANDARD);
const featureState = new models_js_1.FeatureStateModel(feature, override.enabled, override.priority || 0);
if (override.value !== undefined) {
featureState.setValue(override.value);
}
if (override.variants && override.variants.length > 0) {
featureState.multivariateFeatureStateValues = this.createMultivariateValues(override.variants);
}
return featureState;
});
}
static createMultivariateValues(variants) {
return variants.map(variant => new models_js_1.MultivariateFeatureStateValueModel(new models_js_1.MultivariateFeatureOptionModel(variant.value, variant.id), variant.weight, variant.id));
}
}
exports.SegmentModel = SegmentModel;