@splitsoftware/splitio-commons 1.12.1-rc.1 → 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 +2 -1
- 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 +14 -10
- 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 +14 -10
- 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 +1 -1
- 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 +17 -14
- 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 +5 -5
- 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
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,
|
|
@@ -171,7 +171,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
171
171
|
SplitsCacheInRedis.prototype.getAll = function () {
|
|
172
172
|
var _this = this;
|
|
173
173
|
return this.redis.keys(this.keys.searchPatternForSplitKeys())
|
|
174
|
-
.then(function (listOfKeys) { return _this.redis.
|
|
174
|
+
.then(function (listOfKeys) { return _this.redis.pipelineExec(listOfKeys.map(function (k) { return ['get', k]; })); })
|
|
175
175
|
.then(processPipelineAnswer)
|
|
176
176
|
.then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) {
|
|
177
177
|
return JSON.parse(splitDefinition);
|
|
@@ -187,16 +187,20 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
187
187
|
return this.redis.keys(this.keys.searchPatternForSplitKeys()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
|
|
188
188
|
};
|
|
189
189
|
/**
|
|
190
|
-
* Get list of
|
|
191
|
-
* The returned promise is resolved with the list of
|
|
192
|
-
* or rejected if
|
|
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.
|
|
193
193
|
*/
|
|
194
194
|
SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
|
|
195
195
|
var _this = this;
|
|
196
|
-
return
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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); }); });
|
|
200
204
|
};
|
|
201
205
|
/**
|
|
202
206
|
* Check traffic type existence.
|
|
@@ -213,13 +217,13 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
213
217
|
return false; // if entry doesn't exist, means that TT doesn't exist
|
|
214
218
|
ttCount = parseInt(ttCount, 10);
|
|
215
219
|
if (!(0, lang_1.isFiniteNumber)(ttCount) || ttCount < 0) {
|
|
216
|
-
_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."));
|
|
217
221
|
return false;
|
|
218
222
|
}
|
|
219
223
|
return ttCount > 0;
|
|
220
224
|
})
|
|
221
225
|
.catch(function (e) {
|
|
222
|
-
_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 + "."));
|
|
223
227
|
// If there is an error, bypass the validation so the event can get tracked.
|
|
224
228
|
return true;
|
|
225
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,
|
|
@@ -168,7 +168,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
168
168
|
SplitsCacheInRedis.prototype.getAll = function () {
|
|
169
169
|
var _this = this;
|
|
170
170
|
return this.redis.keys(this.keys.searchPatternForSplitKeys())
|
|
171
|
-
.then(function (listOfKeys) { return _this.redis.
|
|
171
|
+
.then(function (listOfKeys) { return _this.redis.pipelineExec(listOfKeys.map(function (k) { return ['get', k]; })); })
|
|
172
172
|
.then(processPipelineAnswer)
|
|
173
173
|
.then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) {
|
|
174
174
|
return JSON.parse(splitDefinition);
|
|
@@ -184,16 +184,20 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
184
184
|
return this.redis.keys(this.keys.searchPatternForSplitKeys()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
|
|
185
185
|
};
|
|
186
186
|
/**
|
|
187
|
-
* Get list of
|
|
188
|
-
* The returned promise is resolved with the list of
|
|
189
|
-
* or rejected if
|
|
187
|
+
* Get list of feature flag names related to a given list of flag set names.
|
|
188
|
+
* The returned promise is resolved with the list of feature flag names per flag set,
|
|
189
|
+
* or rejected if the pipelined redis operation fails.
|
|
190
190
|
*/
|
|
191
191
|
SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
|
|
192
192
|
var _this = this;
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
return this.redis.pipelineExec(flagSets.map(function (flagSet) { return ['smembers', _this.keys.buildFlagSetKey(flagSet)]; }))
|
|
194
|
+
.then(function (results) { return results.map(function (_a, index) {
|
|
195
|
+
var e = _a[0], value = _a[1];
|
|
196
|
+
if (e === null)
|
|
197
|
+
return value;
|
|
198
|
+
_this.log.error(LOG_PREFIX + ("Could not read result from get members of flag set " + flagSets[index] + " due to an error: " + e));
|
|
199
|
+
}); })
|
|
200
|
+
.then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new _Set(namesByFlagSet); }); });
|
|
197
201
|
};
|
|
198
202
|
/**
|
|
199
203
|
* Check traffic type existence.
|
|
@@ -210,13 +214,13 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
|
|
|
210
214
|
return false; // if entry doesn't exist, means that TT doesn't exist
|
|
211
215
|
ttCount = parseInt(ttCount, 10);
|
|
212
216
|
if (!isFiniteNumber(ttCount) || ttCount < 0) {
|
|
213
|
-
_this.log.info(LOG_PREFIX + ("Could not validate traffic type
|
|
217
|
+
_this.log.info(LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to data corruption of some sorts."));
|
|
214
218
|
return false;
|
|
215
219
|
}
|
|
216
220
|
return ttCount > 0;
|
|
217
221
|
})
|
|
218
222
|
.catch(function (e) {
|
|
219
|
-
_this.log.error(LOG_PREFIX + ("Could not validate traffic type
|
|
223
|
+
_this.log.error(LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to an error: " + e + "."));
|
|
220
224
|
// If there is an error, bypass the validation so the event can get tracked.
|
|
221
225
|
return true;
|
|
222
226
|
});
|
|
@@ -158,15 +158,15 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
158
158
|
return this.wrapper.getKeysByPrefix(this.keys.buildSplitKeyPrefix()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
|
|
159
159
|
};
|
|
160
160
|
/**
|
|
161
|
-
* Get list of
|
|
162
|
-
* The returned promise is resolved with the list of
|
|
163
|
-
*
|
|
164
|
-
|
|
161
|
+
* Get list of feature flag names related to a given list of flag set names.
|
|
162
|
+
* The returned promise is resolved with the list of feature flag names per flag set.
|
|
163
|
+
* It never rejects (If there is a wrapper error for some flag set, an empty set is returned for it).
|
|
164
|
+
*/
|
|
165
165
|
SplitsCachePluggable.prototype.getNamesByFlagSets = function (flagSets) {
|
|
166
166
|
var _this = this;
|
|
167
167
|
return Promise.all(flagSets.map(function (flagSet) {
|
|
168
168
|
var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
|
|
169
|
-
return _this.wrapper.getItems(flagSetKey);
|
|
169
|
+
return _this.wrapper.getItems(flagSetKey).catch(function () { return []; });
|
|
170
170
|
})).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new _Set(namesByFlagSet); }); });
|
|
171
171
|
};
|
|
172
172
|
/**
|
|
@@ -8,6 +8,6 @@ export { validateSplit } from './split';
|
|
|
8
8
|
export { validateSplits } from './splits';
|
|
9
9
|
export { validateTrafficType } from './trafficType';
|
|
10
10
|
export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
11
|
+
export { validateSplitExistence } from './splitExistence';
|
|
12
|
+
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
13
|
export { validatePreloadedData } from './preloadedData';
|
|
@@ -4,7 +4,7 @@ import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants';
|
|
|
4
4
|
* 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.
|
|
5
5
|
* 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.
|
|
6
6
|
*/
|
|
7
|
-
export function
|
|
7
|
+
export function validateSplitExistence(log, readinessManager, splitName, labelOrSplitObj, method) {
|
|
8
8
|
if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet.
|
|
9
9
|
if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null) {
|
|
10
10
|
log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]);
|