@splitsoftware/splitio-commons
Version:
Split JavaScript SDK common components
210 lines (209 loc) • 10.5 kB
JavaScript
import { __extends, __spreadArray } from "tslib";
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
import { LOG_PREFIX } from './constants';
import { AbstractDefinitionsCacheAsync } from '../AbstractDefinitionsCacheAsync';
import { returnDifference } from '../../utils/lang/sets';
/**
* IDefinitionsCacheAsync implementation for pluggable storages.
*/
var DefinitionsCachePluggable = /** @class */ (function (_super) {
__extends(DefinitionsCachePluggable, _super);
/**
* Create a DefinitionsCache that uses a storage wrapper.
* @param log - Logger instance.
* @param keys - Key builder.
* @param wrapper - Adapted wrapper storage.
*/
function DefinitionsCachePluggable(log, keys, wrapper, splitFiltersValidation) {
var _this = _super.call(this) || this;
_this.log = log;
_this.keys = keys;
_this.wrapper = wrapper;
_this.setsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
return _this;
}
DefinitionsCachePluggable.prototype._decrementCounts = function (definition) {
var _this = this;
var ttKey = this.keys.buildTrafficTypeKey(definition.trafficTypeName);
return this.wrapper.decr(ttKey).then(function (count) {
if (count === 0)
return _this.wrapper.del(ttKey);
});
};
DefinitionsCachePluggable.prototype._incrementCounts = function (definition) {
var ttKey = this.keys.buildTrafficTypeKey(definition.trafficTypeName);
return this.wrapper.incr(ttKey);
};
DefinitionsCachePluggable.prototype._updateSets = function (definitionName, setsOfRemovedDefinition, setsOfAddedDefinition) {
var _this = this;
var removeFromSets = returnDifference(setsOfRemovedDefinition, setsOfAddedDefinition);
var addToSets = returnDifference(setsOfAddedDefinition, setsOfRemovedDefinition);
if (this.setsFilter.length > 0) {
addToSets = addToSets.filter(function (set) {
return _this.setsFilter.some(function (filterSet) { return filterSet === set; });
});
}
var items = [definitionName];
return Promise.all(__spreadArray(__spreadArray([], removeFromSets.map(function (setName) { return _this.wrapper.removeItems(_this.keys.buildSetKey(setName), items); }), true), addToSets.map(function (setName) { return _this.wrapper.addItems(_this.keys.buildSetKey(setName), items); }), true));
};
/**
* Add a given definition.
* The returned promise is resolved when the operation success
* or rejected if it fails (e.g., wrapper operation fails)
*/
DefinitionsCachePluggable.prototype.add = function (definition) {
var _this = this;
var name = definition.name;
var definitionKey = this.keys.buildDefinitionKey(name);
return this.wrapper.get(definitionKey).then(function (definitionFromStorage) {
// handling parsing error
var parsedPreviousDefinition, stringifiedNewDefinition;
try {
parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
stringifiedNewDefinition = JSON.stringify(definition);
}
catch (e) {
throw new Error('Error parsing feature flag definition: ' + e);
}
return _this.wrapper.set(definitionKey, stringifiedNewDefinition).then(function () {
// avoid unnecessary increment/decrement operations
if (parsedPreviousDefinition && parsedPreviousDefinition.trafficTypeName === definition.trafficTypeName)
return;
// update traffic type counts
return _this._incrementCounts(definition).then(function () {
if (parsedPreviousDefinition)
return _this._decrementCounts(parsedPreviousDefinition);
});
}).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); });
}).then(function () { return true; });
};
/**
* Remove a given definition.
* The returned promise is resolved when the operation success, with a boolean indicating if the definition existed or not.
* or rejected if it fails (e.g., wrapper operation fails).
*/
DefinitionsCachePluggable.prototype.remove = function (name) {
var _this = this;
return this.get(name).then(function (definition) {
if (definition) {
return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); });
}
}).then(function () {
return _this.wrapper.del(_this.keys.buildDefinitionKey(name));
});
};
/**
* Get definition.
* The returned promise is resolved with the definition or null if it's not defined,
* or rejected if wrapper operation fails.
*/
DefinitionsCachePluggable.prototype.get = function (name) {
return this.wrapper.get(this.keys.buildDefinitionKey(name))
.then(function (maybeDefinition) { return maybeDefinition && JSON.parse(maybeDefinition); });
};
/**
* Get list of definitions.
* The returned promise is resolved with a map of names to their definition or null if it's not defined,
* or rejected if wrapper operation fails.
*/
DefinitionsCachePluggable.prototype.getMany = function (names) {
var _this = this;
var keys = names.map(function (name) { return _this.keys.buildDefinitionKey(name); });
return this.wrapper.getMany(keys).then(function (stringifiedDefinitions) {
var definitions = {};
names.forEach(function (name, idx) {
var definition = stringifiedDefinitions[idx];
definitions[name] = definition && JSON.parse(definition);
});
return Promise.resolve(definitions);
});
};
/**
* Get list of all definitions.
* The returned promise is resolved with the list of definitions,
* or rejected if wrapper operation fails.
*/
DefinitionsCachePluggable.prototype.getAll = function () {
var _this = this;
return this.wrapper.getKeysByPrefix(this.keys.buildDefinitionKeyPrefix())
.then(function (listOfKeys) { return _this.wrapper.getMany(listOfKeys); })
.then(function (definitions) { return definitions.map(function (definition) {
return JSON.parse(definition);
}); });
};
/**
* Get list of definition names.
* The returned promise is resolved with the list of names,
* or rejected if wrapper operation fails.
*/
DefinitionsCachePluggable.prototype.getNames = function () {
var _this = this;
return this.wrapper.getKeysByPrefix(this.keys.buildDefinitionKeyPrefix()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
};
/**
* Get list of definition names related to a given list of set names.
* The returned promise is resolved with the list of names per set.
* It never rejects (If there is a wrapper error for some set, an empty set is returned for it).
*/
DefinitionsCachePluggable.prototype.getNamesBySets = function (sets) {
var _this = this;
return Promise.all(sets.map(function (set) {
var setKey = _this.keys.buildSetKey(set);
return _this.wrapper.getItems(setKey).catch(function () { return []; });
})).then(function (namesBySets) { return namesBySets.map(function (namesBySet) { return new Set(namesBySet); }); });
};
/**
* 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.
*/
DefinitionsCachePluggable.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 ".concat(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 ".concat(trafficType, " due to an error: ").concat(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).
*/
DefinitionsCachePluggable.prototype.setChangeNumber = function (changeNumber) {
return this.wrapper.set(this.keys.buildDefinitionsTillKey(), 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.
*/
DefinitionsCachePluggable.prototype.getChangeNumber = function () {
var _this = this;
return this.wrapper.get(this.keys.buildDefinitionsTillKey()).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
DefinitionsCachePluggable.prototype.clear = function () {
return Promise.resolve(true);
};
return DefinitionsCachePluggable;
}(AbstractDefinitionsCacheAsync));
export { DefinitionsCachePluggable };