UNPKG

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
"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 }; }