pricing4react
Version:
A library of components that ease the integration of feature toggling driven by pricing plans into your React application's UI.
229 lines (228 loc) • 10.6 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.evaluateFeatureInPricing = exports.evaluatePricing = void 0;
var pricing4ts_1 = require("pricing4ts");
function evaluatePricing(stringifiedPricing, userSubscription, userContext) {
var pricing = (0, pricing4ts_1.retrievePricingFromYaml)(stringifiedPricing);
var subscription = _buildSubscription(pricing, userSubscription);
if (subscription.error) {
throw new Error(subscription.error);
}
var planContext = _extractContextToEvalFromSubscription(subscription);
var evaluationResult = {};
for (var featureName in pricing.features) {
evaluationResult[featureName] = evaluateFeature(pricing.features[featureName], subscription, planContext, userContext);
}
return evaluationResult;
}
exports.evaluatePricing = evaluatePricing;
function evaluateFeatureInPricing(featureName, stringifiedPricing, userSubscription, userContext) {
var pricing = (0, pricing4ts_1.retrievePricingFromYaml)(stringifiedPricing);
var feature = pricing.features[featureName];
if (!feature) {
return {
eval: false,
used: null,
limit: null,
error: {
code: "FLAG_NOT_FOUND",
message: "Feature ".concat(featureName, " is not present in the pricing")
}
};
}
var subscription = _buildSubscription(pricing, userSubscription);
var planContext = _extractContextToEvalFromSubscription(subscription);
return evaluateFeature(feature, subscription, planContext, userContext);
}
exports.evaluateFeatureInPricing = evaluateFeatureInPricing;
function evaluateFeature(feature, subscription, planContext, userContext) {
var _a, _b, _c;
var featureExpression = (_a = feature.serverExpression) !== null && _a !== void 0 ? _a : feature.expression;
if (!featureExpression) {
console.warn("[ERROR] Feature ".concat(feature.name, " has no expression defined!"));
return {
eval: false,
used: null,
limit: null,
error: {
code: "PARSE_ERROR",
message: "Feature ".concat(feature.name, " has no expression defined!")
}
};
}
else {
var evalResult = eval(featureExpression);
if (typeof evalResult !== "boolean") {
console.warn("[WARNING] Feature ".concat(feature.name, " has an expression that does not return a boolean!"));
return {
eval: false,
used: null,
limit: null,
error: {
code: "TYPE_MISMATCH",
message: "Feature ".concat(feature.name, " has an expression that does not return a boolean!")
}
};
}
else {
if (evalResult !== null && evalResult !== undefined) {
var numericLimitsOfSelectedFeature = Object.values(subscription.usageLimits).filter(function (u) {
var _a;
return ((_a = u.linkedFeatures) === null || _a === void 0 ? void 0 : _a.includes(feature.name)) &&
u.valueType === "NUMERIC";
});
var shouldLimitAppearInToken = numericLimitsOfSelectedFeature.length === 1;
return {
eval: evalResult,
used: shouldLimitAppearInToken
? (_b = userContext[feature.name]) !== null && _b !== void 0 ? _b : null
: null,
limit: shouldLimitAppearInToken
? (_c = numericLimitsOfSelectedFeature[0].value) !== null && _c !== void 0 ? _c : numericLimitsOfSelectedFeature[0].defaultValue
: null,
error: null
};
}
else {
return {
eval: false,
used: null,
limit: null,
error: {
code: "GENERAL",
message: "Error while evaluating expression for feature ".concat(feature.name, ". The returned expression is null or undefined")
}
};
}
}
}
}
function _buildSubscription(pricing, subscription) {
var _a, _b, _c, _d, _e, _f, _g;
var features = {};
var usageLimits = {};
var pricingPlans = pricing.plans;
var pricingAddOns = pricing.addOns;
// Step 1: Validate that the subscription contains no more than one plan,
// that every plan/add-on exists in the pricing, and clasify the subscription
var planAlreadyFound = false;
var subscriptionPlan;
var subscriptionAddOns = [];
for (var _i = 0, subscription_1 = subscription; _i < subscription_1.length; _i++) {
var pricingContainer = subscription_1[_i];
if (pricingPlans &&
!pricingPlans[pricingContainer] &&
pricingAddOns &&
!pricingAddOns[pricingContainer]) {
return _errorFallback("[ERROR] Plan/Add-on ".concat(pricingContainer, " not found in the pricing"));
}
else if (pricingPlans && pricingPlans[pricingContainer]) {
if (planAlreadyFound) {
return _errorFallback("[ERROR] You cannot have multiple plans in the same subscription");
}
else {
// Step 1.2: Identify the plan of the subscription
var plan = pricingPlans[pricingContainer];
planAlreadyFound = true;
subscriptionPlan = plan;
}
}
else if (pricingAddOns && pricingAddOns[pricingContainer]) {
// Step 1.3: Identify the add-ons of the subscription
var addOn = pricingAddOns[pricingContainer];
subscriptionAddOns.push(addOn);
}
}
// Step 2: Build the subscription
// Step 2.1: Add the features and usage limits of the plan
if (subscriptionPlan) {
var planFeatures = subscriptionPlan.features;
var planUsageLimits = (_a = subscriptionPlan.usageLimits) !== null && _a !== void 0 ? _a : {};
features = planFeatures;
usageLimits = planUsageLimits;
}
else {
var pricingFeatures = pricing.features;
var pricingUsageLimits = (_b = pricing.usageLimits) !== null && _b !== void 0 ? _b : {};
features = pricingFeatures;
usageLimits = pricingUsageLimits;
}
// Step 2.2: Apply the effects of the add-ons
for (var _h = 0, subscriptionAddOns_1 = subscriptionAddOns; _h < subscriptionAddOns_1.length; _h++) {
var addOn = subscriptionAddOns_1[_h];
var addOnFeatures = (_c = addOn.features) !== null && _c !== void 0 ? _c : {};
var addOnUsageLimits = (_d = addOn.usageLimits) !== null && _d !== void 0 ? _d : {};
var addOnUsageLimitsExtensions = (_e = addOn.usageLimitsExtensions) !== null && _e !== void 0 ? _e : {};
// Step 2.2.1: Substitute all feature values with the corresponding of the add-ons
for (var featureName in addOnFeatures) {
if (!features[featureName]) {
return _errorFallback("[ERROR] Feature ".concat(featureName, " of the add-on ").concat(addOn.name, " not found in the pricing"));
}
else {
features[featureName].value = addOnFeatures[featureName].value;
}
}
// Step 2.2.2: Substitute all usage limits values with the corresponding of the add-ons
for (var usageLimitName in addOnUsageLimits) {
if (!usageLimits[usageLimitName]) {
return _errorFallback("[ERROR] Usage limit ".concat(usageLimitName, " of the add-on ").concat(addOn.name, " not found in the pricing"));
}
else {
usageLimits[usageLimitName].value =
addOnUsageLimits[usageLimitName].value;
}
}
// Step 2.2.3: Extends usage limits with add-ons' usage limits extension values
for (var extensionName in addOnUsageLimitsExtensions) {
if (!usageLimits[extensionName]) {
return _errorFallback("[ERROR] Trying to extend usage limit ".concat(extensionName, " with add-on ").concat(addOn.name, ", but it is not found in the pricing"));
}
else {
if (typeof addOnUsageLimitsExtensions[extensionName].value !== "number") {
return _errorFallback("[ERROR] Usage limits extensions are expected to be numbers, but ".concat(extensionName, " of the add-on ").concat(addOn.name, " is not"));
}
else if (typeof usageLimits[extensionName].defaultValue !== "number") {
return _errorFallback("[ERROR] Usage limits extensions can only extend numeric usage limits, but ".concat(extensionName, " is not"));
}
else {
usageLimits[extensionName].value =
((_f = usageLimits[extensionName].value) !== null && _f !== void 0 ? _f : usageLimits[extensionName].defaultValue) +
addOnUsageLimitsExtensions[extensionName].value;
}
}
}
}
if (!planAlreadyFound && Object.values((_g = pricing.plans) !== null && _g !== void 0 ? _g : []).length > 0) {
return _errorFallback("[ERROR] Since the pricing have plans, you are forced to contract one on your subscription.");
}
// Step 3: Return the subscription
return {
features: features,
usageLimits: usageLimits,
error: null
};
}
function _extractContextToEvalFromSubscription(subscription) {
var _a, _b;
var subscriptionContextFeatures = subscription.features;
var subscriptionContextUsageLimits = subscription.usageLimits;
var contextToEval = { features: {}, usageLimits: {} };
for (var _i = 0, _c = Object.values(subscriptionContextFeatures); _i < _c.length; _i++) {
var feature = _c[_i];
contextToEval.features[feature.name] =
(_a = feature.value) !== null && _a !== void 0 ? _a : feature.defaultValue;
}
for (var _d = 0, _e = Object.values(subscriptionContextUsageLimits); _d < _e.length; _d++) {
var usageLimit = _e[_d];
contextToEval.usageLimits[usageLimit.name] =
(_b = usageLimit.value) !== null && _b !== void 0 ? _b : usageLimit.defaultValue;
}
return contextToEval;
}
function _errorFallback(errorMsg) {
return {
features: {},
usageLimits: {},
error: errorMsg
};
}