UNPKG

@splitsoftware/splitio-commons

Version:
210 lines (209 loc) 10.3 kB
import { __extends, __spreadArray } from "tslib"; import { isFiniteNumber, isNaNNumber } from '../../utils/lang'; import { LOG_PREFIX } from './constants'; import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync'; import { returnDifference } from '../../utils/lang/sets'; /** * ISplitsCacheAsync implementation for pluggable storages. */ var SplitsCachePluggable = /** @class */ (function (_super) { __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 = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag); var addToFlagSets = 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(__spreadArray(__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 (!isFiniteNumber(ttCount) || ttCount < 0) { _this.log.info(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(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 isNaNNumber(i) ? -1 : i; }).catch(function (e) { _this.log.error(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)); export { SplitsCachePluggable };