@splitsoftware/splitio-commons 1.12.1-rc.0 → 1.12.1-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -2
- package/cjs/sdkClient/client.js +4 -4
- package/cjs/sdkManager/index.js +2 -2
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -3
- package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +1 -5
- package/cjs/storages/inRedis/RedisAdapter.js +12 -10
- package/cjs/storages/inRedis/SplitsCacheInRedis.js +38 -17
- package/cjs/storages/pluggable/SplitsCachePluggable.js +5 -5
- package/cjs/utils/inputValidation/index.js +5 -5
- package/cjs/utils/inputValidation/{splitExistance.js → splitExistence.js} +3 -3
- package/cjs/utils/inputValidation/{trafficTypeExistance.js → trafficTypeExistence.js} +6 -6
- package/cjs/utils/redis/RedisMock.js +1 -7
- package/esm/sdkClient/client.js +4 -4
- package/esm/sdkManager/index.js +3 -3
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -3
- package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +1 -5
- package/esm/storages/inRedis/RedisAdapter.js +12 -10
- package/esm/storages/inRedis/SplitsCacheInRedis.js +39 -18
- package/esm/storages/pluggable/SplitsCachePluggable.js +5 -5
- package/esm/utils/inputValidation/index.js +2 -2
- package/esm/utils/inputValidation/{splitExistance.js → splitExistence.js} +1 -1
- package/esm/utils/inputValidation/{trafficTypeExistance.js → trafficTypeExistence.js} +4 -4
- package/esm/utils/redis/RedisMock.js +1 -7
- package/package.json +2 -2
- package/src/sdkClient/client.ts +4 -4
- package/src/sdkManager/index.ts +3 -3
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +4 -5
- package/src/storages/inRedis/EventsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +4 -8
- package/src/storages/inRedis/ImpressionsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/RedisAdapter.ts +16 -12
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/SplitsCacheInRedis.ts +48 -23
- package/src/storages/inRedis/TelemetryCacheInRedis.ts +2 -2
- package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +3 -3
- package/src/storages/pluggable/SplitsCachePluggable.ts +5 -5
- package/src/utils/inputValidation/index.ts +2 -2
- package/src/utils/inputValidation/{splitExistance.ts → splitExistence.ts} +1 -1
- package/src/utils/inputValidation/{trafficTypeExistance.ts → trafficTypeExistence.ts} +4 -4
- package/src/utils/redis/RedisMock.ts +1 -11
- package/types/storages/inRedis/EventsCacheInRedis.d.ts +2 -2
- package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +3 -2
- package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +2 -2
- package/types/storages/inRedis/RedisAdapter.d.ts +3 -2
- package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +2 -2
- package/types/storages/inRedis/SplitsCacheInRedis.d.ts +9 -8
- package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +2 -2
- package/types/storages/inRedis/UniqueKeysCacheInRedis.d.ts +3 -2
- package/types/storages/pluggable/SplitsCachePluggable.d.ts +4 -4
- package/types/utils/inputValidation/index.d.ts +2 -2
- package/types/utils/inputValidation/splitExistence.d.ts +7 -0
- package/types/utils/inputValidation/trafficTypeExistence.d.ts +9 -0
- package/types/utils/redis/RedisMock.d.ts +0 -1
package/CHANGES.txt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
1.12.0 (December XX, 2023)
|
|
2
|
-
- Added support for Flag Sets in "consumer" and "partial consumer" modes for
|
|
2
|
+
- Added support for Flag Sets in "consumer" and "partial consumer" modes for Pluggable and Redis storages.
|
|
3
3
|
- Updated evaluation flow to log a warning when using flag sets that don't contain cached feature flags.
|
|
4
|
+
- Updated Redis adapter to wrap missing commands: 'hincrby', 'popNRaw', 'flushdb' and 'pipeline.exec'.
|
|
4
5
|
|
|
5
6
|
1.11.0 (November 3, 2023)
|
|
6
7
|
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
- Added a new impressions mode for the SDK called NONE, to be used in factory when there is no desire to capture impressions on an SDK factory to feed Split's analytics engine. Running NONE mode, the SDK will only capture unique keys evaluated for a particular feature flag instead of full blown impressions.
|
|
58
59
|
- Updated SDK telemetry to support pluggable storage, partial consumer mode, and synchronizer.
|
|
59
60
|
- Updated storage implementations to improve the performance of feature flag evaluations (i.e., `getTreatment(s)` method calls) when using the default storage in memory.
|
|
60
|
-
- Updated evaluation flow to avoid
|
|
61
|
+
- Updated evaluation flow (i.e., `getTreatment(s)` method calls) to avoid calling the storage for cached feature flags when the SDK is not ready or ready from cache. It applies to all SDK modes.
|
|
61
62
|
|
|
62
63
|
1.6.1 (July 22, 2022)
|
|
63
64
|
- Updated GoogleAnalyticsToSplit integration to validate `autoRequire` config parameter and avoid some wrong warning logs when mapping GA hit fields to Split event properties.
|
package/cjs/sdkClient/client.js
CHANGED
|
@@ -4,8 +4,8 @@ exports.clientFactory = void 0;
|
|
|
4
4
|
var evaluator_1 = require("../evaluator");
|
|
5
5
|
var thenable_1 = require("../utils/promise/thenable");
|
|
6
6
|
var key_1 = require("../utils/key");
|
|
7
|
-
var
|
|
8
|
-
var
|
|
7
|
+
var splitExistence_1 = require("../utils/inputValidation/splitExistence");
|
|
8
|
+
var trafficTypeExistence_1 = require("../utils/inputValidation/trafficTypeExistence");
|
|
9
9
|
var labels_1 = require("../utils/labels");
|
|
10
10
|
var constants_1 = require("../utils/constants");
|
|
11
11
|
var constants_2 = require("../logger/constants");
|
|
@@ -102,7 +102,7 @@ function clientFactory(params) {
|
|
|
102
102
|
var bucketingKey = (0, key_1.getBucketing)(key);
|
|
103
103
|
var treatment = evaluation.treatment, label = evaluation.label, changeNumber = evaluation.changeNumber, _a = evaluation.config, config = _a === void 0 ? null : _a;
|
|
104
104
|
log.info(constants_2.IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
105
|
-
if ((0,
|
|
105
|
+
if ((0, splitExistence_1.validateSplitExistence)(log, readinessManager, featureFlagName, label, invokingMethodName)) {
|
|
106
106
|
log.info(constants_2.IMPRESSION_QUEUEING);
|
|
107
107
|
queue.push({
|
|
108
108
|
feature: featureFlagName,
|
|
@@ -136,7 +136,7 @@ function clientFactory(params) {
|
|
|
136
136
|
properties: properties
|
|
137
137
|
};
|
|
138
138
|
// This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
|
|
139
|
-
(0,
|
|
139
|
+
(0, trafficTypeExistence_1.validateTrafficTypeExistence)(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
|
|
140
140
|
var result = eventTracker.track(eventData, size);
|
|
141
141
|
if ((0, thenable_1.thenable)(result)) {
|
|
142
142
|
return result.then(function (result) {
|
package/cjs/sdkManager/index.js
CHANGED
|
@@ -57,11 +57,11 @@ function sdkManagerFactory(log, splits, _a) {
|
|
|
57
57
|
var split = splits.getSplit(splitName);
|
|
58
58
|
if ((0, thenable_1.thenable)(split)) {
|
|
59
59
|
return split.catch(function () { return null; }).then(function (result) {
|
|
60
|
-
(0, inputValidation_1.
|
|
60
|
+
(0, inputValidation_1.validateSplitExistence)(log, readinessManager, splitName, result, SPLIT_FN_LABEL);
|
|
61
61
|
return objectToView(result);
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
(0, inputValidation_1.
|
|
64
|
+
(0, inputValidation_1.validateSplitExistence)(log, readinessManager, splitName, split, SPLIT_FN_LABEL);
|
|
65
65
|
return objectToView(split);
|
|
66
66
|
},
|
|
67
67
|
/**
|
|
@@ -246,9 +246,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
246
246
|
return;
|
|
247
247
|
var flagSetKey = _this.keys.buildFlagSetKey(featureFlagSet);
|
|
248
248
|
var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
|
|
249
|
-
|
|
250
|
-
flagSetFromLocalStorage = '[]';
|
|
251
|
-
var flagSetCache = new sets_1._Set(JSON.parse(flagSetFromLocalStorage));
|
|
249
|
+
var flagSetCache = new sets_1._Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
|
|
252
250
|
flagSetCache.add(featureFlag.name);
|
|
253
251
|
localStorage.setItem(flagSetKey, JSON.stringify((0, sets_1.setToArray)(flagSetCache)));
|
|
254
252
|
});
|
|
@@ -23,11 +23,7 @@ var ImpressionCountsCacheInRedis = /** @class */ (function (_super) {
|
|
|
23
23
|
var keys = Object.keys(counts);
|
|
24
24
|
if (!keys.length)
|
|
25
25
|
return Promise.resolve(false);
|
|
26
|
-
|
|
27
|
-
keys.forEach(function (key) {
|
|
28
|
-
pipeline.hincrby(_this.key, key, counts[key]);
|
|
29
|
-
});
|
|
30
|
-
return pipeline.exec()
|
|
26
|
+
return this.redis.pipelineExec(keys.map(function (key) { return ['hincrby', _this.key, key, counts[key]]; }))
|
|
31
27
|
.then(function (data) {
|
|
32
28
|
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
33
29
|
if (data.length && data.length === keys.length) {
|
|
@@ -9,7 +9,7 @@ var thenable_1 = require("../../utils/promise/thenable");
|
|
|
9
9
|
var timeout_1 = require("../../utils/promise/timeout");
|
|
10
10
|
var LOG_PREFIX = 'storage:redis-adapter: ';
|
|
11
11
|
// If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
|
|
12
|
-
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', '
|
|
12
|
+
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw', 'flushdb', 'pipelineExec'];
|
|
13
13
|
// Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
|
|
14
14
|
var DEFAULT_OPTIONS = {
|
|
15
15
|
connectionTimeout: 10000,
|
|
@@ -40,6 +40,9 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
40
40
|
_this._setDisconnectWrapper();
|
|
41
41
|
return _this;
|
|
42
42
|
}
|
|
43
|
+
RedisAdapter.prototype.pipelineExec = function (commands) {
|
|
44
|
+
return this.pipeline(commands).exec();
|
|
45
|
+
};
|
|
43
46
|
RedisAdapter.prototype._listenToEvents = function () {
|
|
44
47
|
var _this = this;
|
|
45
48
|
this.once('ready', function () {
|
|
@@ -58,13 +61,12 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
58
61
|
};
|
|
59
62
|
RedisAdapter.prototype._setTimeoutWrappers = function () {
|
|
60
63
|
var instance = this;
|
|
61
|
-
METHODS_TO_PROMISE_WRAP.forEach(function (
|
|
62
|
-
var originalMethod = instance[
|
|
63
|
-
instance[
|
|
64
|
+
METHODS_TO_PROMISE_WRAP.forEach(function (methodName) {
|
|
65
|
+
var originalMethod = instance[methodName];
|
|
66
|
+
instance[methodName] = function () {
|
|
64
67
|
var params = arguments;
|
|
65
68
|
function commandWrapper() {
|
|
66
|
-
instance.log.debug(LOG_PREFIX +
|
|
67
|
-
// Return original method
|
|
69
|
+
instance.log.debug(LOG_PREFIX + "Executing " + methodName + ".");
|
|
68
70
|
var result = originalMethod.apply(instance, params);
|
|
69
71
|
if ((0, thenable_1.thenable)(result)) {
|
|
70
72
|
// For handling pending commands on disconnect, add to the set and remove once finished.
|
|
@@ -76,7 +78,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
76
78
|
// Both success and error remove from queue.
|
|
77
79
|
result.then(cleanUpRunningCommandsCb, cleanUpRunningCommandsCb);
|
|
78
80
|
return (0, timeout_1.timeout)(instance._options.operationTimeout, result).catch(function (err) {
|
|
79
|
-
instance.log.error(LOG_PREFIX +
|
|
81
|
+
instance.log.error("" + LOG_PREFIX + methodName + " operation threw an error or exceeded configured timeout of " + instance._options.operationTimeout + "ms. Message: " + err);
|
|
80
82
|
// Handling is not the adapter responsibility.
|
|
81
83
|
throw err;
|
|
82
84
|
});
|
|
@@ -89,7 +91,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
89
91
|
resolve: res,
|
|
90
92
|
reject: rej,
|
|
91
93
|
command: commandWrapper,
|
|
92
|
-
name:
|
|
94
|
+
name: methodName.toUpperCase()
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
97
|
}
|
|
@@ -107,7 +109,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
107
109
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
108
110
|
params[_i] = arguments[_i];
|
|
109
111
|
}
|
|
110
|
-
setTimeout(function
|
|
112
|
+
setTimeout(function deferredDisconnect() {
|
|
111
113
|
if (instance._runningCommands.size > 0) {
|
|
112
114
|
instance.log.info(LOG_PREFIX + ("Attempting to disconnect but there are " + instance._runningCommands.size + " commands still waiting for resolution. Defering disconnection until those finish."));
|
|
113
115
|
Promise.all((0, sets_1.setToArray)(instance._runningCommands))
|
|
@@ -158,7 +160,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
158
160
|
* Parses the options into what we care about.
|
|
159
161
|
*/
|
|
160
162
|
RedisAdapter._defineOptions = function (_a) {
|
|
161
|
-
var connectionTimeout =
|
|
163
|
+
var _b = _a === void 0 ? {} : _a, connectionTimeout = _b.connectionTimeout, operationTimeout = _b.operationTimeout, url = _b.url, host = _b.host, port = _b.port, db = _b.db, pass = _b.pass, tls = _b.tls;
|
|
162
164
|
var parsedOptions = {
|
|
163
165
|
connectionTimeout: connectionTimeout,
|
|
164
166
|
operationTimeout: operationTimeout,
|
|
@@ -5,6 +5,7 @@ var tslib_1 = require("tslib");
|
|
|
5
5
|
var lang_1 = require("../../utils/lang");
|
|
6
6
|
var constants_1 = require("./constants");
|
|
7
7
|
var AbstractSplitsCacheAsync_1 = require("../AbstractSplitsCacheAsync");
|
|
8
|
+
var sets_1 = require("../../utils/lang/sets");
|
|
8
9
|
/**
|
|
9
10
|
* Discard errors for an answer of multiple operations.
|
|
10
11
|
*/
|
|
@@ -21,11 +22,12 @@ function processPipelineAnswer(results) {
|
|
|
21
22
|
*/
|
|
22
23
|
var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
23
24
|
(0, tslib_1.__extends)(SplitsCacheInRedis, _super);
|
|
24
|
-
function SplitsCacheInRedis(log, keys, redis) {
|
|
25
|
+
function SplitsCacheInRedis(log, keys, redis, splitFiltersValidation) {
|
|
25
26
|
var _this = _super.call(this) || this;
|
|
26
27
|
_this.log = log;
|
|
27
28
|
_this.redis = redis;
|
|
28
29
|
_this.keys = keys;
|
|
30
|
+
_this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
|
|
29
31
|
// There is no need to listen for redis 'error' event, because in that case ioredis calls will be rejected and handled by redis storage adapters.
|
|
30
32
|
// But it is done just to avoid getting the ioredis message `Unhandled error event`.
|
|
31
33
|
_this.redis.on('error', function (e) {
|
|
@@ -48,6 +50,18 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
48
50
|
var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
49
51
|
return this.redis.incr(ttKey);
|
|
50
52
|
};
|
|
53
|
+
SplitsCacheInRedis.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
|
|
54
|
+
var _this = this;
|
|
55
|
+
var removeFromFlagSets = (0, sets_1.returnListDifference)(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
56
|
+
var addToFlagSets = (0, sets_1.returnListDifference)(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
57
|
+
if (this.flagSetsFilter.length > 0) {
|
|
58
|
+
addToFlagSets = addToFlagSets.filter(function (flagSet) {
|
|
59
|
+
return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
var items = [featureFlagName];
|
|
63
|
+
return Promise.all((0, tslib_1.__spreadArray)((0, tslib_1.__spreadArray)([], removeFromFlagSets.map(function (flagSetName) { return _this.redis.srem(_this.keys.buildFlagSetKey(flagSetName), items); }), true), addToFlagSets.map(function (flagSetName) { return _this.redis.sadd(_this.keys.buildFlagSetKey(flagSetName), items); }), true));
|
|
64
|
+
};
|
|
51
65
|
/**
|
|
52
66
|
* Add a given split.
|
|
53
67
|
* The returned promise is resolved when the operation success
|
|
@@ -57,16 +71,16 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
57
71
|
var _this = this;
|
|
58
72
|
var splitKey = this.keys.buildSplitKey(name);
|
|
59
73
|
return this.redis.get(splitKey).then(function (splitFromStorage) {
|
|
60
|
-
// handling parsing
|
|
61
|
-
var parsedPreviousSplit,
|
|
74
|
+
// handling parsing error
|
|
75
|
+
var parsedPreviousSplit, stringifiedNewSplit;
|
|
62
76
|
try {
|
|
63
77
|
parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined;
|
|
64
|
-
|
|
78
|
+
stringifiedNewSplit = JSON.stringify(split);
|
|
65
79
|
}
|
|
66
80
|
catch (e) {
|
|
67
81
|
throw new Error('Error parsing feature flag definition: ' + e);
|
|
68
82
|
}
|
|
69
|
-
return _this.redis.set(splitKey,
|
|
83
|
+
return _this.redis.set(splitKey, stringifiedNewSplit).then(function () {
|
|
70
84
|
// avoid unnecessary increment/decrement operations
|
|
71
85
|
if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName)
|
|
72
86
|
return;
|
|
@@ -75,7 +89,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
75
89
|
if (parsedPreviousSplit)
|
|
76
90
|
return _this._decrementCounts(parsedPreviousSplit);
|
|
77
91
|
});
|
|
78
|
-
});
|
|
92
|
+
}).then(function () { return _this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets); });
|
|
79
93
|
}).then(function () { return true; });
|
|
80
94
|
};
|
|
81
95
|
/**
|
|
@@ -96,8 +110,9 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
96
110
|
var _this = this;
|
|
97
111
|
return this.getSplit(name).then(function (split) {
|
|
98
112
|
if (split) {
|
|
99
|
-
_this._decrementCounts(split);
|
|
113
|
+
return _this._decrementCounts(split).then(function () { return _this._updateFlagSets(name, split.sets); });
|
|
100
114
|
}
|
|
115
|
+
}).then(function () {
|
|
101
116
|
return _this.redis.del(_this.keys.buildSplitKey(name));
|
|
102
117
|
});
|
|
103
118
|
};
|
|
@@ -156,7 +171,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
156
171
|
SplitsCacheInRedis.prototype.getAll = function () {
|
|
157
172
|
var _this = this;
|
|
158
173
|
return this.redis.keys(this.keys.searchPatternForSplitKeys())
|
|
159
|
-
.then(function (listOfKeys) { return _this.redis.
|
|
174
|
+
.then(function (listOfKeys) { return _this.redis.pipelineExec(listOfKeys.map(function (k) { return ['get', k]; })); })
|
|
160
175
|
.then(processPipelineAnswer)
|
|
161
176
|
.then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) {
|
|
162
177
|
return JSON.parse(splitDefinition);
|
|
@@ -172,14 +187,20 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
172
187
|
return this.redis.keys(this.keys.searchPatternForSplitKeys()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
|
|
173
188
|
};
|
|
174
189
|
/**
|
|
175
|
-
* Get list of
|
|
176
|
-
* The returned promise is resolved with the list of
|
|
177
|
-
* or rejected if
|
|
178
|
-
* @todo this is a no-op method to be implemented
|
|
190
|
+
* Get list of feature flag names related to a given list of flag set names.
|
|
191
|
+
* The returned promise is resolved with the list of feature flag names per flag set,
|
|
192
|
+
* or rejected if the pipelined redis operation fails.
|
|
179
193
|
*/
|
|
180
|
-
SplitsCacheInRedis.prototype.getNamesByFlagSets = function () {
|
|
181
|
-
|
|
182
|
-
return
|
|
194
|
+
SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
|
|
195
|
+
var _this = this;
|
|
196
|
+
return this.redis.pipelineExec(flagSets.map(function (flagSet) { return ['smembers', _this.keys.buildFlagSetKey(flagSet)]; }))
|
|
197
|
+
.then(function (results) { return results.map(function (_a, index) {
|
|
198
|
+
var e = _a[0], value = _a[1];
|
|
199
|
+
if (e === null)
|
|
200
|
+
return value;
|
|
201
|
+
_this.log.error(constants_1.LOG_PREFIX + ("Could not read result from get members of flag set " + flagSets[index] + " due to an error: " + e));
|
|
202
|
+
}); })
|
|
203
|
+
.then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new sets_1._Set(namesByFlagSet); }); });
|
|
183
204
|
};
|
|
184
205
|
/**
|
|
185
206
|
* Check traffic type existence.
|
|
@@ -196,13 +217,13 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
196
217
|
return false; // if entry doesn't exist, means that TT doesn't exist
|
|
197
218
|
ttCount = parseInt(ttCount, 10);
|
|
198
219
|
if (!(0, lang_1.isFiniteNumber)(ttCount) || ttCount < 0) {
|
|
199
|
-
_this.log.info(constants_1.LOG_PREFIX + ("Could not validate traffic type
|
|
220
|
+
_this.log.info(constants_1.LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to data corruption of some sorts."));
|
|
200
221
|
return false;
|
|
201
222
|
}
|
|
202
223
|
return ttCount > 0;
|
|
203
224
|
})
|
|
204
225
|
.catch(function (e) {
|
|
205
|
-
_this.log.error(constants_1.LOG_PREFIX + ("Could not validate traffic type
|
|
226
|
+
_this.log.error(constants_1.LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to an error: " + e + "."));
|
|
206
227
|
// If there is an error, bypass the validation so the event can get tracked.
|
|
207
228
|
return true;
|
|
208
229
|
});
|
|
@@ -161,15 +161,15 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
161
161
|
return this.wrapper.getKeysByPrefix(this.keys.buildSplitKeyPrefix()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
|
|
162
162
|
};
|
|
163
163
|
/**
|
|
164
|
-
* Get list of
|
|
165
|
-
* The returned promise is resolved with the list of
|
|
166
|
-
*
|
|
167
|
-
|
|
164
|
+
* Get list of feature flag names related to a given list of flag set names.
|
|
165
|
+
* The returned promise is resolved with the list of feature flag names per flag set.
|
|
166
|
+
* It never rejects (If there is a wrapper error for some flag set, an empty set is returned for it).
|
|
167
|
+
*/
|
|
168
168
|
SplitsCachePluggable.prototype.getNamesByFlagSets = function (flagSets) {
|
|
169
169
|
var _this = this;
|
|
170
170
|
return Promise.all(flagSets.map(function (flagSet) {
|
|
171
171
|
var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
|
|
172
|
-
return _this.wrapper.getItems(flagSetKey);
|
|
172
|
+
return _this.wrapper.getItems(flagSetKey).catch(function () { return []; });
|
|
173
173
|
})).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new sets_1._Set(namesByFlagSet); }); });
|
|
174
174
|
};
|
|
175
175
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validatePreloadedData = exports.
|
|
3
|
+
exports.validatePreloadedData = exports.validateTrafficTypeExistence = exports.validateSplitExistence = exports.validateIfOperational = exports.validateIfNotDestroyed = exports.validateTrafficType = exports.validateSplits = exports.validateSplit = exports.validateKey = exports.validateEventProperties = exports.validateEventValue = exports.validateEvent = exports.validateAttributes = exports.releaseApiKey = exports.validateAndTrackApiKey = exports.validateApiKey = void 0;
|
|
4
4
|
var apiKey_1 = require("./apiKey");
|
|
5
5
|
Object.defineProperty(exports, "validateApiKey", { enumerable: true, get: function () { return apiKey_1.validateApiKey; } });
|
|
6
6
|
Object.defineProperty(exports, "validateAndTrackApiKey", { enumerable: true, get: function () { return apiKey_1.validateAndTrackApiKey; } });
|
|
@@ -24,9 +24,9 @@ Object.defineProperty(exports, "validateTrafficType", { enumerable: true, get: f
|
|
|
24
24
|
var isOperational_1 = require("./isOperational");
|
|
25
25
|
Object.defineProperty(exports, "validateIfNotDestroyed", { enumerable: true, get: function () { return isOperational_1.validateIfNotDestroyed; } });
|
|
26
26
|
Object.defineProperty(exports, "validateIfOperational", { enumerable: true, get: function () { return isOperational_1.validateIfOperational; } });
|
|
27
|
-
var
|
|
28
|
-
Object.defineProperty(exports, "
|
|
29
|
-
var
|
|
30
|
-
Object.defineProperty(exports, "
|
|
27
|
+
var splitExistence_1 = require("./splitExistence");
|
|
28
|
+
Object.defineProperty(exports, "validateSplitExistence", { enumerable: true, get: function () { return splitExistence_1.validateSplitExistence; } });
|
|
29
|
+
var trafficTypeExistence_1 = require("./trafficTypeExistence");
|
|
30
|
+
Object.defineProperty(exports, "validateTrafficTypeExistence", { enumerable: true, get: function () { return trafficTypeExistence_1.validateTrafficTypeExistence; } });
|
|
31
31
|
var preloadedData_1 = require("./preloadedData");
|
|
32
32
|
Object.defineProperty(exports, "validatePreloadedData", { enumerable: true, get: function () { return preloadedData_1.validatePreloadedData; } });
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.validateSplitExistence = void 0;
|
|
4
4
|
var labels_1 = require("../labels");
|
|
5
5
|
var constants_1 = require("../../logger/constants");
|
|
6
6
|
/**
|
|
7
7
|
* This is defined here and in this format mostly because of the logger and the fact that it's considered a validation at product level.
|
|
8
8
|
* But it's not going to run on the input validation layer. In any case, the most compeling reason to use it as we do is to avoid going to Redis and get a split twice.
|
|
9
9
|
*/
|
|
10
|
-
function
|
|
10
|
+
function validateSplitExistence(log, readinessManager, splitName, labelOrSplitObj, method) {
|
|
11
11
|
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet.
|
|
12
12
|
if (labelOrSplitObj === labels_1.SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
13
13
|
log.warn(constants_1.WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|
|
@@ -16,4 +16,4 @@ function validateSplitExistance(log, readinessManager, splitName, labelOrSplitOb
|
|
|
16
16
|
}
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
|
-
exports.
|
|
19
|
+
exports.validateSplitExistence = validateSplitExistence;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.validateTrafficTypeExistence = void 0;
|
|
4
4
|
var thenable_1 = require("../promise/thenable");
|
|
5
5
|
var constants_1 = require("../constants");
|
|
6
6
|
var constants_2 = require("../../logger/constants");
|
|
7
|
-
function
|
|
7
|
+
function logTTExistenceWarning(log, maybeTT, method) {
|
|
8
8
|
log.warn(constants_2.WARN_NOT_EXISTENT_TT, [method, maybeTT]);
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Separated from the previous method since on some cases it'll be async.
|
|
12
12
|
*/
|
|
13
|
-
function
|
|
13
|
+
function validateTrafficTypeExistence(log, readinessManager, splitsCache, mode, maybeTT, method) {
|
|
14
14
|
// If not ready or in localhost mode, we won't run the validation
|
|
15
15
|
if (!readinessManager.isReady() || mode === constants_1.LOCALHOST_MODE)
|
|
16
16
|
return true;
|
|
@@ -18,14 +18,14 @@ function validateTrafficTypeExistance(log, readinessManager, splitsCache, mode,
|
|
|
18
18
|
if ((0, thenable_1.thenable)(res)) {
|
|
19
19
|
return res.then(function (isValid) {
|
|
20
20
|
if (!isValid)
|
|
21
|
-
|
|
21
|
+
logTTExistenceWarning(log, maybeTT, method);
|
|
22
22
|
return isValid; // propagate result
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
26
|
if (!res)
|
|
27
|
-
|
|
27
|
+
logTTExistenceWarning(log, maybeTT, method);
|
|
28
28
|
return res;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
exports.
|
|
31
|
+
exports.validateTrafficTypeExistence = validateTrafficTypeExistence;
|
|
@@ -9,22 +9,16 @@ function asyncFunction(data) {
|
|
|
9
9
|
return Promise.resolve(data);
|
|
10
10
|
}
|
|
11
11
|
var IDENTITY_METHODS = [];
|
|
12
|
-
var ASYNC_METHODS = ['rpush', 'hincrby'];
|
|
13
|
-
var PIPELINE_METHODS = ['rpush', 'hincrby'];
|
|
12
|
+
var ASYNC_METHODS = ['rpush', 'hincrby', 'pipelineExec'];
|
|
14
13
|
var RedisMock = /** @class */ (function () {
|
|
15
14
|
function RedisMock() {
|
|
16
15
|
var _this = this;
|
|
17
|
-
this.pipelineMethods = { exec: jest.fn(asyncFunction) };
|
|
18
16
|
IDENTITY_METHODS.forEach(function (method) {
|
|
19
17
|
_this[method] = jest.fn(identityFunction);
|
|
20
18
|
});
|
|
21
19
|
ASYNC_METHODS.forEach(function (method) {
|
|
22
20
|
_this[method] = jest.fn(asyncFunction);
|
|
23
21
|
});
|
|
24
|
-
PIPELINE_METHODS.forEach(function (method) {
|
|
25
|
-
_this.pipelineMethods[method] = _this[method];
|
|
26
|
-
});
|
|
27
|
-
this.pipeline = jest.fn(function () { return _this.pipelineMethods; });
|
|
28
22
|
}
|
|
29
23
|
return RedisMock;
|
|
30
24
|
}());
|
package/esm/sdkClient/client.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { evaluateFeature, evaluateFeatures, evaluateFeaturesByFlagSets } from '../evaluator';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { getMatching, getBucketing } from '../utils/key';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { validateSplitExistence } from '../utils/inputValidation/splitExistence';
|
|
5
|
+
import { validateTrafficTypeExistence } from '../utils/inputValidation/trafficTypeExistence';
|
|
6
6
|
import { SDK_NOT_READY } from '../utils/labels';
|
|
7
7
|
import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET } from '../utils/constants';
|
|
8
8
|
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
|
|
@@ -99,7 +99,7 @@ export function clientFactory(params) {
|
|
|
99
99
|
var bucketingKey = getBucketing(key);
|
|
100
100
|
var treatment = evaluation.treatment, label = evaluation.label, changeNumber = evaluation.changeNumber, _a = evaluation.config, config = _a === void 0 ? null : _a;
|
|
101
101
|
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
|
|
102
|
-
if (
|
|
102
|
+
if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
|
|
103
103
|
log.info(IMPRESSION_QUEUEING);
|
|
104
104
|
queue.push({
|
|
105
105
|
feature: featureFlagName,
|
|
@@ -133,7 +133,7 @@ export function clientFactory(params) {
|
|
|
133
133
|
properties: properties
|
|
134
134
|
};
|
|
135
135
|
// This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
|
|
136
|
-
|
|
136
|
+
validateTrafficTypeExistence(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
|
|
137
137
|
var result = eventTracker.track(eventData, size);
|
|
138
138
|
if (thenable(result)) {
|
|
139
139
|
return result.then(function (result) {
|
package/esm/sdkManager/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { find } from '../utils/lang';
|
|
4
|
-
import { validateSplit,
|
|
4
|
+
import { validateSplit, validateSplitExistence, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
|
|
5
5
|
var SPLIT_FN_LABEL = 'split';
|
|
6
6
|
var SPLITS_FN_LABEL = 'splits';
|
|
7
7
|
var NAMES_FN_LABEL = 'names';
|
|
@@ -54,11 +54,11 @@ export function sdkManagerFactory(log, splits, _a) {
|
|
|
54
54
|
var split = splits.getSplit(splitName);
|
|
55
55
|
if (thenable(split)) {
|
|
56
56
|
return split.catch(function () { return null; }).then(function (result) {
|
|
57
|
-
|
|
57
|
+
validateSplitExistence(log, readinessManager, splitName, result, SPLIT_FN_LABEL);
|
|
58
58
|
return objectToView(result);
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
validateSplitExistence(log, readinessManager, splitName, split, SPLIT_FN_LABEL);
|
|
62
62
|
return objectToView(split);
|
|
63
63
|
},
|
|
64
64
|
/**
|
|
@@ -243,9 +243,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
243
243
|
return;
|
|
244
244
|
var flagSetKey = _this.keys.buildFlagSetKey(featureFlagSet);
|
|
245
245
|
var flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
|
|
246
|
-
|
|
247
|
-
flagSetFromLocalStorage = '[]';
|
|
248
|
-
var flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
|
|
246
|
+
var flagSetCache = new _Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
|
|
249
247
|
flagSetCache.add(featureFlag.name);
|
|
250
248
|
localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
251
249
|
});
|
|
@@ -20,11 +20,7 @@ var ImpressionCountsCacheInRedis = /** @class */ (function (_super) {
|
|
|
20
20
|
var keys = Object.keys(counts);
|
|
21
21
|
if (!keys.length)
|
|
22
22
|
return Promise.resolve(false);
|
|
23
|
-
|
|
24
|
-
keys.forEach(function (key) {
|
|
25
|
-
pipeline.hincrby(_this.key, key, counts[key]);
|
|
26
|
-
});
|
|
27
|
-
return pipeline.exec()
|
|
23
|
+
return this.redis.pipelineExec(keys.map(function (key) { return ['hincrby', _this.key, key, counts[key]]; }))
|
|
28
24
|
.then(function (data) {
|
|
29
25
|
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
30
26
|
if (data.length && data.length === keys.length) {
|
|
@@ -6,7 +6,7 @@ import { thenable } from '../../utils/promise/thenable';
|
|
|
6
6
|
import { timeout } from '../../utils/promise/timeout';
|
|
7
7
|
var LOG_PREFIX = 'storage:redis-adapter: ';
|
|
8
8
|
// If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
|
|
9
|
-
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', '
|
|
9
|
+
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw', 'flushdb', 'pipelineExec'];
|
|
10
10
|
// Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
|
|
11
11
|
var DEFAULT_OPTIONS = {
|
|
12
12
|
connectionTimeout: 10000,
|
|
@@ -37,6 +37,9 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
37
37
|
_this._setDisconnectWrapper();
|
|
38
38
|
return _this;
|
|
39
39
|
}
|
|
40
|
+
RedisAdapter.prototype.pipelineExec = function (commands) {
|
|
41
|
+
return this.pipeline(commands).exec();
|
|
42
|
+
};
|
|
40
43
|
RedisAdapter.prototype._listenToEvents = function () {
|
|
41
44
|
var _this = this;
|
|
42
45
|
this.once('ready', function () {
|
|
@@ -55,13 +58,12 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
55
58
|
};
|
|
56
59
|
RedisAdapter.prototype._setTimeoutWrappers = function () {
|
|
57
60
|
var instance = this;
|
|
58
|
-
METHODS_TO_PROMISE_WRAP.forEach(function (
|
|
59
|
-
var originalMethod = instance[
|
|
60
|
-
instance[
|
|
61
|
+
METHODS_TO_PROMISE_WRAP.forEach(function (methodName) {
|
|
62
|
+
var originalMethod = instance[methodName];
|
|
63
|
+
instance[methodName] = function () {
|
|
61
64
|
var params = arguments;
|
|
62
65
|
function commandWrapper() {
|
|
63
|
-
instance.log.debug(LOG_PREFIX +
|
|
64
|
-
// Return original method
|
|
66
|
+
instance.log.debug(LOG_PREFIX + "Executing " + methodName + ".");
|
|
65
67
|
var result = originalMethod.apply(instance, params);
|
|
66
68
|
if (thenable(result)) {
|
|
67
69
|
// For handling pending commands on disconnect, add to the set and remove once finished.
|
|
@@ -73,7 +75,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
73
75
|
// Both success and error remove from queue.
|
|
74
76
|
result.then(cleanUpRunningCommandsCb, cleanUpRunningCommandsCb);
|
|
75
77
|
return timeout(instance._options.operationTimeout, result).catch(function (err) {
|
|
76
|
-
instance.log.error(LOG_PREFIX +
|
|
78
|
+
instance.log.error("" + LOG_PREFIX + methodName + " operation threw an error or exceeded configured timeout of " + instance._options.operationTimeout + "ms. Message: " + err);
|
|
77
79
|
// Handling is not the adapter responsibility.
|
|
78
80
|
throw err;
|
|
79
81
|
});
|
|
@@ -86,7 +88,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
86
88
|
resolve: res,
|
|
87
89
|
reject: rej,
|
|
88
90
|
command: commandWrapper,
|
|
89
|
-
name:
|
|
91
|
+
name: methodName.toUpperCase()
|
|
90
92
|
});
|
|
91
93
|
});
|
|
92
94
|
}
|
|
@@ -104,7 +106,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
104
106
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
105
107
|
params[_i] = arguments[_i];
|
|
106
108
|
}
|
|
107
|
-
setTimeout(function
|
|
109
|
+
setTimeout(function deferredDisconnect() {
|
|
108
110
|
if (instance._runningCommands.size > 0) {
|
|
109
111
|
instance.log.info(LOG_PREFIX + ("Attempting to disconnect but there are " + instance._runningCommands.size + " commands still waiting for resolution. Defering disconnection until those finish."));
|
|
110
112
|
Promise.all(setToArray(instance._runningCommands))
|
|
@@ -155,7 +157,7 @@ var RedisAdapter = /** @class */ (function (_super) {
|
|
|
155
157
|
* Parses the options into what we care about.
|
|
156
158
|
*/
|
|
157
159
|
RedisAdapter._defineOptions = function (_a) {
|
|
158
|
-
var connectionTimeout =
|
|
160
|
+
var _b = _a === void 0 ? {} : _a, connectionTimeout = _b.connectionTimeout, operationTimeout = _b.operationTimeout, url = _b.url, host = _b.host, port = _b.port, db = _b.db, pass = _b.pass, tls = _b.tls;
|
|
159
161
|
var parsedOptions = {
|
|
160
162
|
connectionTimeout: connectionTimeout,
|
|
161
163
|
operationTimeout: operationTimeout,
|