UNPKG

@splitsoftware/splitio-commons

Version:
213 lines (212 loc) 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SplitsCachePluggable = void 0; var tslib_1 = require("tslib"); var lang_1 = require("../../utils/lang"); var constants_1 = require("./constants"); var AbstractSplitsCacheAsync_1 = require("../AbstractSplitsCacheAsync"); var sets_1 = require("../../utils/lang/sets"); /** * ISplitsCacheAsync implementation for pluggable storages. */ var SplitsCachePluggable = /** @class */ (function (_super) { (0, tslib_1.__extends)(SplitsCachePluggable, _super); /** * Create a SplitsCache that uses a storage wrapper. * @param log - Logger instance. * @param keys - Key builder. * @param wrapper - Adapted wrapper storage. */ function SplitsCachePluggable(log, keys, wrapper, splitFiltersValidation) { var _this = _super.call(this) || this; _this.log = log; _this.keys = keys; _this.wrapper = wrapper; _this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : []; return _this; } SplitsCachePluggable.prototype._decrementCounts = function (split) { var _this = this; var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); return this.wrapper.decr(ttKey).then(function (count) { if (count === 0) return _this.wrapper.del(ttKey); }); }; SplitsCachePluggable.prototype._incrementCounts = function (split) { var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); return this.wrapper.incr(ttKey); }; SplitsCachePluggable.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) { var _this = this; var removeFromFlagSets = (0, sets_1.returnDifference)(flagSetsOfRemovedFlag, flagSetsOfAddedFlag); var addToFlagSets = (0, sets_1.returnDifference)(flagSetsOfAddedFlag, flagSetsOfRemovedFlag); if (this.flagSetsFilter.length > 0) { addToFlagSets = addToFlagSets.filter(function (flagSet) { return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; }); }); } var items = [featureFlagName]; return Promise.all((0, tslib_1.__spreadArray)((0, tslib_1.__spreadArray)([], removeFromFlagSets.map(function (flagSetName) { return _this.wrapper.removeItems(_this.keys.buildFlagSetKey(flagSetName), items); }), true), addToFlagSets.map(function (flagSetName) { return _this.wrapper.addItems(_this.keys.buildFlagSetKey(flagSetName), items); }), true)); }; /** * Add a given split. * The returned promise is resolved when the operation success * or rejected if it fails (e.g., wrapper operation fails) */ SplitsCachePluggable.prototype.addSplit = function (split) { var _this = this; var name = split.name; var splitKey = this.keys.buildSplitKey(name); return this.wrapper.get(splitKey).then(function (splitFromStorage) { // handling parsing error var parsedPreviousSplit, stringifiedNewSplit; try { parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined; stringifiedNewSplit = JSON.stringify(split); } catch (e) { throw new Error('Error parsing feature flag definition: ' + e); } return _this.wrapper.set(splitKey, stringifiedNewSplit).then(function () { // avoid unnecessary increment/decrement operations if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName) return; // update traffic type counts return _this._incrementCounts(split).then(function () { if (parsedPreviousSplit) return _this._decrementCounts(parsedPreviousSplit); }); }).then(function () { return _this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets); }); }).then(function () { return true; }); }; /** * Remove a given split. * The returned promise is resolved when the operation success, with a boolean indicating if the split existed or not. * or rejected if it fails (e.g., wrapper operation fails). */ SplitsCachePluggable.prototype.removeSplit = function (name) { var _this = this; return this.getSplit(name).then(function (split) { if (split) { return _this._decrementCounts(split).then(function () { return _this._updateFlagSets(name, split.sets); }); } }).then(function () { return _this.wrapper.del(_this.keys.buildSplitKey(name)); }); }; /** * Get split. * The returned promise is resolved with the split definition or null if it's not defined, * or rejected if wrapper operation fails. */ SplitsCachePluggable.prototype.getSplit = function (name) { return this.wrapper.get(this.keys.buildSplitKey(name)) .then(function (maybeSplit) { return maybeSplit && JSON.parse(maybeSplit); }); }; /** * Get list of splits. * The returned promise is resolved with a map of split names to their split definition or null if it's not defined, * or rejected if wrapper operation fails. */ SplitsCachePluggable.prototype.getSplits = function (names) { var _this = this; var keys = names.map(function (name) { return _this.keys.buildSplitKey(name); }); return this.wrapper.getMany(keys).then(function (splitDefinitions) { var splits = {}; names.forEach(function (name, idx) { var split = splitDefinitions[idx]; splits[name] = split && JSON.parse(split); }); return Promise.resolve(splits); }); }; /** * Get list of all split definitions. * The returned promise is resolved with the list of split definitions, * or rejected if wrapper operation fails. */ SplitsCachePluggable.prototype.getAll = function () { var _this = this; return this.wrapper.getKeysByPrefix(this.keys.buildSplitKeyPrefix()) .then(function (listOfKeys) { return _this.wrapper.getMany(listOfKeys); }) .then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) { return JSON.parse(splitDefinition); }); }); }; /** * Get list of split names. * The returned promise is resolved with the list of split names, * or rejected if wrapper operation fails. */ SplitsCachePluggable.prototype.getSplitNames = function () { var _this = this; return this.wrapper.getKeysByPrefix(this.keys.buildSplitKeyPrefix()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); }); }; /** * Get list of feature flag names related to a given list of flag set names. * The returned promise is resolved with the list of feature flag names per flag set. * It never rejects (If there is a wrapper error for some flag set, an empty set is returned for it). */ SplitsCachePluggable.prototype.getNamesByFlagSets = function (flagSets) { var _this = this; return Promise.all(flagSets.map(function (flagSet) { var flagSetKey = _this.keys.buildFlagSetKey(flagSet); return _this.wrapper.getItems(flagSetKey).catch(function () { return []; }); })).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new Set(namesByFlagSet); }); }); }; /** * Check traffic type existence. * The returned promise is resolved with a boolean indicating whether the TT exist or not. * In case of wrapper operation failures, the promise resolves with a true value, assuming that the TT might exist. * It will never be rejected. */ SplitsCachePluggable.prototype.trafficTypeExists = function (trafficType) { var _this = this; // If there is a number there should be > 0, otherwise the TT is considered as not existent. return this.wrapper.get(this.keys.buildTrafficTypeKey(trafficType)) .then(function (ttCount) { if (ttCount === null) return false; // if entry doesn't exist, means that TT doesn't exist ttCount = parseInt(ttCount, 10); if (!(0, lang_1.isFiniteNumber)(ttCount) || ttCount < 0) { _this.log.info(constants_1.LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to data corruption of some sorts.")); return false; } return ttCount > 0; }).catch(function (e) { _this.log.error(constants_1.LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to an error: " + e + ".")); // If there is an error, bypass the validation so the event can get tracked. return true; }); }; /** * Set till number. * The returned promise is resolved when the operation success, * or rejected if it fails (e.g., wrapper operation fails). */ SplitsCachePluggable.prototype.setChangeNumber = function (changeNumber) { return this.wrapper.set(this.keys.buildSplitsTillKey(), changeNumber + ''); }; /** * Get till number or -1 if it's not defined. * The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails. * The promise will never be rejected. */ SplitsCachePluggable.prototype.getChangeNumber = function () { var _this = this; return this.wrapper.get(this.keys.buildSplitsTillKey()).then(function (value) { var i = parseInt(value, 10); return (0, lang_1.isNaNNumber)(i) ? -1 : i; }).catch(function (e) { _this.log.error(constants_1.LOG_PREFIX + 'Could not retrieve changeNumber from storage. Error: ' + e); return -1; }); }; // @TODO implement if required by DataLoader or producer mode SplitsCachePluggable.prototype.clear = function () { return Promise.resolve(true); }; return SplitsCachePluggable; }(AbstractSplitsCacheAsync_1.AbstractSplitsCacheAsync)); exports.SplitsCachePluggable = SplitsCachePluggable;