pricing4ts
Version:
 Pricing4TS is a TypeScript-based toolkit designed to enhance the server-side functionality of a pricing-driven SaaS by enabling the seamless integration of pricing plans into the application logic. T
266 lines (265 loc) • 14.9 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePricing = parsePricing;
exports.formatObjectToArray = formatObjectToArray;
exports.formatObject = formatObject;
exports.formatArrayIntoObject = formatArrayIntoObject;
var pricing_1 = require("../../models/pricing2yaml/pricing");
var pricing_validators_1 = require("../pricing-validators");
function parsePricing(extractedPricing) {
var pricing = (0, pricing_1.generateEmptyPricing)();
parseBasicAttributes(extractedPricing, pricing);
// Format and parse features
(0, pricing_validators_1.validateFeatures)(extractedPricing.features);
var formattedFeatures = formatObjectToArray(extractedPricing.features);
formattedFeatures = formattedFeatures.map(function (f) { return parseFeature(f, pricing.tags); });
pricing.features = formatArrayIntoObject(formattedFeatures);
// Format and parse usage limits, considering they can be null/undefined
if (extractedPricing.usageLimits == null || extractedPricing.usageLimits == undefined) {
pricing.usageLimits = {};
}
else {
(0, pricing_validators_1.validateUsageLimits)(extractedPricing.usageLimits);
var formattedUsageLimits = formatObjectToArray(extractedPricing.usageLimits);
formattedUsageLimits = formattedUsageLimits.map(function (u) { return parseUsageLimit(u, pricing); });
pricing.usageLimits = formatArrayIntoObject(formattedUsageLimits);
}
// Format and parse plans, considering they can be null/undefined
if (extractedPricing.plans == null || extractedPricing.plans == undefined) {
pricing.plans = {};
}
else {
(0, pricing_validators_1.validatePlans)(extractedPricing.plans);
var plansToFormat = formatObjectToArray(extractedPricing.plans);
var formattedPlans = [];
for (var _i = 0, plansToFormat_1 = plansToFormat; _i < plansToFormat_1.length; _i++) {
var plan = plansToFormat_1[_i];
var formattedPlan = parsePlan(plan, pricing);
formattedPlans.push(formattedPlan);
}
pricing.plans = formatArrayIntoObject(formattedPlans);
}
// Format and parse add-ons, considering they can be null/undefined
if (extractedPricing.addOns == null || extractedPricing.addOns == undefined) {
pricing.addOns = {};
}
else {
var formattedAddOns = formatObjectToArray(extractedPricing.addOns);
formattedAddOns = formattedAddOns.map(function (a) { return parseAddOn(a, pricing); });
pricing.addOns = formatArrayIntoObject(formattedAddOns);
Object.values(pricing.addOns).forEach(function (a) { return (0, pricing_validators_1.postValidateDependsOnOrExclude)(a.dependsOn, pricing); });
Object.values(pricing.addOns).forEach(function (a) { return (0, pricing_validators_1.postValidateDependsOnOrExclude)(a.excludes, pricing); });
}
if (!pricing.plans && !pricing.addOns) {
throw new Error('At least one of the following must be provided: plans, addOns');
}
return pricing;
}
// --------- PRICING ELEMENTS FORMATTERS ---------
function parseBasicAttributes(extractedPricing, pricing) {
pricing.syntaxVersion = (0, pricing_validators_1.validateSyntaxVersion)(extractedPricing.syntaxVersion); // Assumes that the version has been processed to be the last one
pricing.saasName = (0, pricing_validators_1.validateName)(extractedPricing.saasName, 'SaaS');
pricing.url = (0, pricing_validators_1.validateUrl)(extractedPricing.url);
pricing.createdAt = (0, pricing_validators_1.validateCreatedAt)(extractedPricing.createdAt);
pricing.version = (0, pricing_validators_1.validateVersion)(extractedPricing.version, pricing.createdAt); // Assumes that the version has been processed to be the last one
pricing.currency = (0, pricing_validators_1.validateCurrency)(extractedPricing.currency);
pricing.billing = (0, pricing_validators_1.validateBilling)(extractedPricing.billing);
pricing.variables = (0, pricing_validators_1.validateVariables)(extractedPricing.variables);
pricing.tags = (0, pricing_validators_1.validateTags)(extractedPricing.tags);
}
function parseFeature(feature, tags) {
var featureName = feature.name;
try {
(0, pricing_validators_1.validateFeature)(feature);
feature.name = (0, pricing_validators_1.validateName)(feature.name, 'Feature');
feature.description = (0, pricing_validators_1.validateDescription)(feature.description);
feature.valueType = (0, pricing_validators_1.validateValueType)(feature.valueType);
feature.defaultValue = (0, pricing_validators_1.validateDefaultValue)(feature, 'feature');
feature.value = (0, pricing_validators_1.validateValue)(feature, 'feature');
feature.expression = (0, pricing_validators_1.validateExpression)(feature.expression, 'expression');
feature.serverExpression = (0, pricing_validators_1.validateExpression)(feature.serverExpression, 'serverExpression');
feature.type = (0, pricing_validators_1.validateFeatureType)(feature.type);
feature.integrationType = (0, pricing_validators_1.validateFeatureIntegrationType)(feature.integrationType, feature.type);
feature.pricingUrls = (0, pricing_validators_1.validatePricingUrls)(feature.type, feature.integrationType, feature.pricingUrls);
feature.docUrl = (0, pricing_validators_1.validateDocUrl)(feature.type, feature.docUrl);
feature.automationType = (0, pricing_validators_1.validateFeatureAutomationType)(feature.automationType, feature.type);
feature.render = (0, pricing_validators_1.validateRenderMode)(feature.render);
feature.tag = validateTag(feature.tag, tags);
}
catch (err) {
throw new Error("Error parsing feature ".concat(featureName, ". Error: ").concat(err.message));
}
return feature;
}
function parseUsageLimit(usageLimit, pricing) {
var _a, _b;
try {
(0, pricing_validators_1.validateUsageLimit)(usageLimit);
usageLimit.name = (0, pricing_validators_1.validateName)(usageLimit.name, 'Usage Limit');
usageLimit.description = (0, pricing_validators_1.validateDescription)(usageLimit.description);
usageLimit.valueType = (0, pricing_validators_1.validateValueType)(usageLimit.valueType);
usageLimit.defaultValue = (0, pricing_validators_1.validateDefaultValue)(usageLimit, 'usage limit');
usageLimit.value = (0, pricing_validators_1.validateValue)(usageLimit, 'usage limit');
usageLimit.unit = (0, pricing_validators_1.validateUnit)(usageLimit.unit);
usageLimit.type = (0, pricing_validators_1.validateUsageLimitType)(usageLimit.type);
if (usageLimit.type === 'RENEWABLE') {
usageLimit.period = {
unit: (0, pricing_validators_1.validatePeriodUnit)((_a = usageLimit.period) === null || _a === void 0 ? void 0 : _a.unit),
value: (0, pricing_validators_1.validatePeriodValue)((_b = usageLimit.period) === null || _b === void 0 ? void 0 : _b.value),
};
}
else if (usageLimit.type === 'NON_RENEWABLE') {
usageLimit.trackable = (0, pricing_validators_1.validateTrackable)(usageLimit.trackable);
}
if (usageLimit.trackable === undefined && !usageLimit.period) {
throw new Error("Usage limit ".concat(usageLimit.name, " must have a trackable property or a period defined"));
}
usageLimit.linkedFeatures = (0, pricing_validators_1.validateLinkedFeatures)(usageLimit.linkedFeatures, pricing);
usageLimit.render = (0, pricing_validators_1.validateRenderMode)(usageLimit.render);
}
catch (err) {
throw new Error("Error parsing usage limit ".concat(usageLimit.name, ". Error: ").concat(err.message));
}
return usageLimit;
}
function parsePlan(plan, pricing) {
var _a;
try {
(0, pricing_validators_1.validatePlan)(plan);
plan.name = (0, pricing_validators_1.validateName)(plan.name, 'Plan');
plan.description = (0, pricing_validators_1.validateDescription)(plan.description);
plan.price = (0, pricing_validators_1.validatePrice)(plan.price, pricing.variables);
plan.unit = (0, pricing_validators_1.validateUnit)(plan.unit);
plan.private = (0, pricing_validators_1.validatePrivate)(plan.private);
var planFeatures = JSON.parse(JSON.stringify(pricing.features)); // This is performed in order to avoid modifying the original object
if (plan.features !== null && plan.features !== undefined) {
plan.features = formatObject((_a = plan.features) !== null && _a !== void 0 ? _a : {});
}
else {
plan.features = {};
}
plan.features = (0, pricing_validators_1.validatePlanFeatures)(plan, planFeatures);
var planUsageLimits = JSON.parse(JSON.stringify(pricing.usageLimits));
if (plan.usageLimits !== null && plan.usageLimits !== undefined) {
plan.usageLimits = formatObject(plan.usageLimits);
}
else {
plan.usageLimits = {};
}
plan.usageLimits = (0, pricing_validators_1.validatePlanUsageLimits)(plan, planUsageLimits);
}
catch (err) {
throw new Error("Error parsing plan ".concat(plan.name, ". Error: ").concat(err.message));
}
return plan;
}
function parseAddOn(addon, pricing) {
var _a, _b, _c;
try {
addon.name = (0, pricing_validators_1.validateName)(addon.name, 'Addon');
addon.description = (0, pricing_validators_1.validateDescription)(addon.description);
addon.availableFor = (0, pricing_validators_1.validateAvailableFor)(addon.availableFor, pricing);
addon.dependsOn = (0, pricing_validators_1.validateDependsOnOrExcludes)(addon.dependsOn, pricing, "dependsOn");
addon.excludes = (0, pricing_validators_1.validateDependsOnOrExcludes)(addon.excludes, pricing, "excludes");
addon.price = (0, pricing_validators_1.validatePrice)(addon.price, pricing.variables);
addon.unit = (0, pricing_validators_1.validateUnit)(addon.unit);
addon.private = (0, pricing_validators_1.validatePrivate)(addon.private);
// Parse Features if provided
if (addon.features !== null && addon.features !== undefined) {
var addonFeatures = JSON.parse(JSON.stringify(pricing.features));
addon.features = formatObject(addon.features);
addon.features = (0, pricing_validators_1.validateAddonFeatures)(addon, addonFeatures);
}
else {
addon.features = {};
}
// Parse UsageLimits if provided
if (addon.usageLimits !== null && addon.usageLimits !== undefined) {
var addonUsageLimits = JSON.parse(JSON.stringify(pricing.usageLimits));
addon.usageLimits = formatObject(addon.usageLimits);
addon.usageLimits = (0, pricing_validators_1.validateAddonUsageLimits)(addon, addonUsageLimits);
}
else {
addon.usageLimits = {};
}
// Parse usageLimitsExtensions if provided
if (addon.usageLimitsExtensions !== null && addon.usageLimitsExtensions !== undefined) {
var addonUsageLimitsExtensions = JSON.parse(JSON.stringify(pricing.usageLimits));
addon.usageLimitsExtensions = formatObject(addon.usageLimitsExtensions);
addon.usageLimitsExtensions = (0, pricing_validators_1.validateAddonUsageLimitsExtensions)(addon, addonUsageLimitsExtensions);
}
else {
addon.usageLimitsExtensions = {};
}
if (Object.keys(addon.features).length === 0 && Object.keys(addon.usageLimits).length === 0 && Object.keys(addon.usageLimitsExtensions).length === 0) {
throw new Error('An add-on cannot be empty. It must have at least one feature, usage limit or usage limit extension');
}
var minQuantity = (0, pricing_validators_1.validateSubscriptionConstraintMinQuantity)((_a = addon.subscriptionConstraints) === null || _a === void 0 ? void 0 : _a.minQuantity);
var isScalableAddon = Object.keys(addon.features).length === 0 && Object.keys(addon.usageLimits).length === 0 && Object.keys(addon.usageLimitsExtensions).length > 0;
if (isScalableAddon) {
addon.subscriptionConstraints = {
minQuantity: minQuantity,
maxQuantity: (0, pricing_validators_1.validateSubscriptionConstraintMaxQuantity)((_b = addon.subscriptionConstraints) === null || _b === void 0 ? void 0 : _b.maxQuantity, minQuantity),
quantityStep: (0, pricing_validators_1.validateSubscriptionConstraintQuantityStep)((_c = addon.subscriptionConstraints) === null || _c === void 0 ? void 0 : _c.quantityStep, minQuantity),
};
}
}
catch (err) {
throw new Error("Error parsing addon ".concat(addon.name, ". Error: ").concat(err.message));
}
return addon;
}
function validateTag(tag, tags) {
try {
if (tag && tags && !tags.includes(tag)) {
throw new Error("Feature tag '".concat(tag, "' must be one of the values defined in 'tags'"));
}
return tag;
}
catch (err) {
throw new Error("Error validating tag: ".concat(err.message));
}
}
// --------- UTILITY FUNCTIONS ---------
function formatObjectToArray(object) {
return Object.entries(object).map(function (_a) {
var name = _a[0], details = _a[1];
return (__assign({ name: name }, details));
});
}
function formatObject(object) {
return Object.entries(object).reduce(function (acc, _a) {
var key = _a[0], value = _a[1];
acc[key] = __assign({ name: key }, value);
return acc;
}, {});
}
function formatArrayIntoObject(array) {
return array.reduce(function (acc, _a) {
var name = _a.name, rest = __rest(_a, ["name"]);
acc[name] = __assign({ name: name }, rest);
return acc;
}, {});
}