@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.
Files changed (53) hide show
  1. package/CHANGES.txt +3 -2
  2. package/cjs/sdkClient/client.js +4 -4
  3. package/cjs/sdkManager/index.js +2 -2
  4. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -3
  5. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +1 -5
  6. package/cjs/storages/inRedis/RedisAdapter.js +12 -10
  7. package/cjs/storages/inRedis/SplitsCacheInRedis.js +38 -17
  8. package/cjs/storages/pluggable/SplitsCachePluggable.js +5 -5
  9. package/cjs/utils/inputValidation/index.js +5 -5
  10. package/cjs/utils/inputValidation/{splitExistance.js → splitExistence.js} +3 -3
  11. package/cjs/utils/inputValidation/{trafficTypeExistance.js → trafficTypeExistence.js} +6 -6
  12. package/cjs/utils/redis/RedisMock.js +1 -7
  13. package/esm/sdkClient/client.js +4 -4
  14. package/esm/sdkManager/index.js +3 -3
  15. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -3
  16. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +1 -5
  17. package/esm/storages/inRedis/RedisAdapter.js +12 -10
  18. package/esm/storages/inRedis/SplitsCacheInRedis.js +39 -18
  19. package/esm/storages/pluggable/SplitsCachePluggable.js +5 -5
  20. package/esm/utils/inputValidation/index.js +2 -2
  21. package/esm/utils/inputValidation/{splitExistance.js → splitExistence.js} +1 -1
  22. package/esm/utils/inputValidation/{trafficTypeExistance.js → trafficTypeExistence.js} +4 -4
  23. package/esm/utils/redis/RedisMock.js +1 -7
  24. package/package.json +2 -2
  25. package/src/sdkClient/client.ts +4 -4
  26. package/src/sdkManager/index.ts +3 -3
  27. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +4 -5
  28. package/src/storages/inRedis/EventsCacheInRedis.ts +3 -3
  29. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +4 -8
  30. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +3 -3
  31. package/src/storages/inRedis/RedisAdapter.ts +16 -12
  32. package/src/storages/inRedis/SegmentsCacheInRedis.ts +3 -3
  33. package/src/storages/inRedis/SplitsCacheInRedis.ts +48 -23
  34. package/src/storages/inRedis/TelemetryCacheInRedis.ts +2 -2
  35. package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +3 -3
  36. package/src/storages/pluggable/SplitsCachePluggable.ts +5 -5
  37. package/src/utils/inputValidation/index.ts +2 -2
  38. package/src/utils/inputValidation/{splitExistance.ts → splitExistence.ts} +1 -1
  39. package/src/utils/inputValidation/{trafficTypeExistance.ts → trafficTypeExistence.ts} +4 -4
  40. package/src/utils/redis/RedisMock.ts +1 -11
  41. package/types/storages/inRedis/EventsCacheInRedis.d.ts +2 -2
  42. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +3 -2
  43. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +2 -2
  44. package/types/storages/inRedis/RedisAdapter.d.ts +3 -2
  45. package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +2 -2
  46. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +9 -8
  47. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +2 -2
  48. package/types/storages/inRedis/UniqueKeysCacheInRedis.d.ts +3 -2
  49. package/types/storages/pluggable/SplitsCachePluggable.d.ts +4 -4
  50. package/types/utils/inputValidation/index.d.ts +2 -2
  51. package/types/utils/inputValidation/splitExistence.d.ts +7 -0
  52. package/types/utils/inputValidation/trafficTypeExistence.d.ts +9 -0
  53. package/types/utils/redis/RedisMock.d.ts +0 -1
@@ -1,7 +1,8 @@
1
- import { __extends } from "tslib";
1
+ import { __extends, __spreadArray } from "tslib";
2
2
  import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
3
3
  import { LOG_PREFIX } from './constants';
4
4
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
5
+ import { _Set, returnListDifference } from '../../utils/lang/sets';
5
6
  /**
6
7
  * Discard errors for an answer of multiple operations.
7
8
  */
@@ -18,11 +19,12 @@ function processPipelineAnswer(results) {
18
19
  */
19
20
  var SplitsCacheInRedis = /** @class */ (function (_super) {
20
21
  __extends(SplitsCacheInRedis, _super);
21
- function SplitsCacheInRedis(log, keys, redis) {
22
+ function SplitsCacheInRedis(log, keys, redis, splitFiltersValidation) {
22
23
  var _this = _super.call(this) || this;
23
24
  _this.log = log;
24
25
  _this.redis = redis;
25
26
  _this.keys = keys;
27
+ _this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
26
28
  // 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.
27
29
  // But it is done just to avoid getting the ioredis message `Unhandled error event`.
28
30
  _this.redis.on('error', function (e) {
@@ -45,6 +47,18 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
45
47
  var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
46
48
  return this.redis.incr(ttKey);
47
49
  };
50
+ SplitsCacheInRedis.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
51
+ var _this = this;
52
+ var removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
53
+ var addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
54
+ if (this.flagSetsFilter.length > 0) {
55
+ addToFlagSets = addToFlagSets.filter(function (flagSet) {
56
+ return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
57
+ });
58
+ }
59
+ var items = [featureFlagName];
60
+ return Promise.all(__spreadArray(__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));
61
+ };
48
62
  /**
49
63
  * Add a given split.
50
64
  * The returned promise is resolved when the operation success
@@ -54,16 +68,16 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
54
68
  var _this = this;
55
69
  var splitKey = this.keys.buildSplitKey(name);
56
70
  return this.redis.get(splitKey).then(function (splitFromStorage) {
57
- // handling parsing errors
58
- var parsedPreviousSplit, newStringifiedSplit;
71
+ // handling parsing error
72
+ var parsedPreviousSplit, stringifiedNewSplit;
59
73
  try {
60
74
  parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined;
61
- newStringifiedSplit = JSON.stringify(split);
75
+ stringifiedNewSplit = JSON.stringify(split);
62
76
  }
63
77
  catch (e) {
64
78
  throw new Error('Error parsing feature flag definition: ' + e);
65
79
  }
66
- return _this.redis.set(splitKey, newStringifiedSplit).then(function () {
80
+ return _this.redis.set(splitKey, stringifiedNewSplit).then(function () {
67
81
  // avoid unnecessary increment/decrement operations
68
82
  if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName)
69
83
  return;
@@ -72,7 +86,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
72
86
  if (parsedPreviousSplit)
73
87
  return _this._decrementCounts(parsedPreviousSplit);
74
88
  });
75
- });
89
+ }).then(function () { return _this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets); });
76
90
  }).then(function () { return true; });
77
91
  };
78
92
  /**
@@ -93,8 +107,9 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
93
107
  var _this = this;
94
108
  return this.getSplit(name).then(function (split) {
95
109
  if (split) {
96
- _this._decrementCounts(split);
110
+ return _this._decrementCounts(split).then(function () { return _this._updateFlagSets(name, split.sets); });
97
111
  }
112
+ }).then(function () {
98
113
  return _this.redis.del(_this.keys.buildSplitKey(name));
99
114
  });
100
115
  };
@@ -153,7 +168,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
153
168
  SplitsCacheInRedis.prototype.getAll = function () {
154
169
  var _this = this;
155
170
  return this.redis.keys(this.keys.searchPatternForSplitKeys())
156
- .then(function (listOfKeys) { return _this.redis.pipeline(listOfKeys.map(function (k) { return ['get', k]; })).exec(); })
171
+ .then(function (listOfKeys) { return _this.redis.pipelineExec(listOfKeys.map(function (k) { return ['get', k]; })); })
157
172
  .then(processPipelineAnswer)
158
173
  .then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) {
159
174
  return JSON.parse(splitDefinition);
@@ -169,14 +184,20 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
169
184
  return this.redis.keys(this.keys.searchPatternForSplitKeys()).then(function (listOfKeys) { return listOfKeys.map(_this.keys.extractKey); });
170
185
  };
171
186
  /**
172
- * Get list of split names related to a given flag set names list.
173
- * The returned promise is resolved with the list of split names,
174
- * or rejected if wrapper operation fails.
175
- * @todo this is a no-op method to be implemented
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.
176
190
  */
177
- SplitsCacheInRedis.prototype.getNamesByFlagSets = function () {
178
- this.log.error(LOG_PREFIX + 'ByFlagSet/s evaluations are not supported with Redis storage yet.');
179
- return Promise.reject();
191
+ SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
192
+ var _this = this;
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); }); });
180
201
  };
181
202
  /**
182
203
  * Check traffic type existence.
@@ -193,13 +214,13 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
193
214
  return false; // if entry doesn't exist, means that TT doesn't exist
194
215
  ttCount = parseInt(ttCount, 10);
195
216
  if (!isFiniteNumber(ttCount) || ttCount < 0) {
196
- _this.log.info(LOG_PREFIX + ("Could not validate traffic type existance of " + trafficType + " due to data corruption of some sorts."));
217
+ _this.log.info(LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to data corruption of some sorts."));
197
218
  return false;
198
219
  }
199
220
  return ttCount > 0;
200
221
  })
201
222
  .catch(function (e) {
202
- _this.log.error(LOG_PREFIX + ("Could not validate traffic type existance of " + trafficType + " due to an error: " + e + "."));
223
+ _this.log.error(LOG_PREFIX + ("Could not validate traffic type existence of " + trafficType + " due to an error: " + e + "."));
203
224
  // If there is an error, bypass the validation so the event can get tracked.
204
225
  return true;
205
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 split names related to a given flag set names list.
162
- * The returned promise is resolved with the list of split names,
163
- * or rejected if any wrapper operation fails.
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 { validateSplitExistance } from './splitExistance';
12
- export { validateTrafficTypeExistance } from './trafficTypeExistance';
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 validateSplitExistance(log, readinessManager, splitName, labelOrSplitObj, method) {
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]);
@@ -1,13 +1,13 @@
1
1
  import { thenable } from '../promise/thenable';
2
2
  import { LOCALHOST_MODE } from '../constants';
3
3
  import { WARN_NOT_EXISTENT_TT } from '../../logger/constants';
4
- function logTTExistanceWarning(log, maybeTT, method) {
4
+ function logTTExistenceWarning(log, maybeTT, method) {
5
5
  log.warn(WARN_NOT_EXISTENT_TT, [method, maybeTT]);
6
6
  }
7
7
  /**
8
8
  * Separated from the previous method since on some cases it'll be async.
9
9
  */
10
- export function validateTrafficTypeExistance(log, readinessManager, splitsCache, mode, maybeTT, method) {
10
+ export function validateTrafficTypeExistence(log, readinessManager, splitsCache, mode, maybeTT, method) {
11
11
  // If not ready or in localhost mode, we won't run the validation
12
12
  if (!readinessManager.isReady() || mode === LOCALHOST_MODE)
13
13
  return true;
@@ -15,13 +15,13 @@ export function validateTrafficTypeExistance(log, readinessManager, splitsCache,
15
15
  if (thenable(res)) {
16
16
  return res.then(function (isValid) {
17
17
  if (!isValid)
18
- logTTExistanceWarning(log, maybeTT, method);
18
+ logTTExistenceWarning(log, maybeTT, method);
19
19
  return isValid; // propagate result
20
20
  });
21
21
  }
22
22
  else {
23
23
  if (!res)
24
- logTTExistanceWarning(log, maybeTT, method);
24
+ logTTExistenceWarning(log, maybeTT, method);
25
25
  return res;
26
26
  }
27
27
  }
@@ -6,22 +6,16 @@ function asyncFunction(data) {
6
6
  return Promise.resolve(data);
7
7
  }
8
8
  var IDENTITY_METHODS = [];
9
- var ASYNC_METHODS = ['rpush', 'hincrby'];
10
- var PIPELINE_METHODS = ['rpush', 'hincrby'];
9
+ var ASYNC_METHODS = ['rpush', 'hincrby', 'pipelineExec'];
11
10
  var RedisMock = /** @class */ (function () {
12
11
  function RedisMock() {
13
12
  var _this = this;
14
- this.pipelineMethods = { exec: jest.fn(asyncFunction) };
15
13
  IDENTITY_METHODS.forEach(function (method) {
16
14
  _this[method] = jest.fn(identityFunction);
17
15
  });
18
16
  ASYNC_METHODS.forEach(function (method) {
19
17
  _this[method] = jest.fn(asyncFunction);
20
18
  });
21
- PIPELINE_METHODS.forEach(function (method) {
22
- _this.pipelineMethods[method] = _this[method];
23
- });
24
- this.pipeline = jest.fn(function () { return _this.pipelineMethods; });
25
19
  }
26
20
  return RedisMock;
27
21
  }());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.12.1-rc.0",
3
+ "version": "1.12.1-rc.2",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -22,7 +22,7 @@
22
22
  "build": "npm run build:cjs && npm run build:esm",
23
23
  "build:esm": "rimraf esm && tsc -m es2015 --outDir esm -d true --declarationDir types",
24
24
  "build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs",
25
- "test": "jest --runInBand",
25
+ "test": "jest",
26
26
  "test:coverage": "jest --coverage",
27
27
  "all": "npm run check && npm run build && npm run test",
28
28
  "publish:rc": "npm run check && npm run test && npm run build && npm publish --tag rc",
@@ -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 { validateSplitExistance } from '../utils/inputValidation/splitExistance';
5
- import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
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 { IEvaluationResult } from '../evaluator/types';
@@ -133,7 +133,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
133
133
  const { treatment, label, changeNumber, config = null } = evaluation;
134
134
  log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);
135
135
 
136
- if (validateSplitExistance(log, readinessManager, featureFlagName, label, invokingMethodName)) {
136
+ if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
137
137
  log.info(IMPRESSION_QUEUEING);
138
138
  queue.push({
139
139
  feature: featureFlagName,
@@ -171,7 +171,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
171
171
  };
172
172
 
173
173
  // 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.
174
- validateTrafficTypeExistance(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
174
+ validateTrafficTypeExistence(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
175
175
 
176
176
  const result = eventTracker.track(eventData, size);
177
177
 
@@ -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, validateSplitExistance, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
4
+ import { validateSplit, validateSplitExistence, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
5
5
  import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
6
6
  import { ISdkReadinessManager } from '../readiness/types';
7
7
  import { ISplit } from '../dtos/types';
@@ -71,12 +71,12 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
71
71
 
72
72
  if (thenable(split)) {
73
73
  return split.catch(() => null).then(result => { // handle possible rejections when using pluggable storage
74
- validateSplitExistance(log, readinessManager, splitName, result, SPLIT_FN_LABEL);
74
+ validateSplitExistence(log, readinessManager, splitName, result, SPLIT_FN_LABEL);
75
75
  return objectToView(result);
76
76
  });
77
77
  }
78
78
 
79
- validateSplitExistance(log, readinessManager, splitName, split, SPLIT_FN_LABEL);
79
+ validateSplitExistence(log, readinessManager, splitName, split, SPLIT_FN_LABEL);
80
80
 
81
81
  return objectToView(split);
82
82
  },
@@ -260,7 +260,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
260
260
  getNamesByFlagSets(flagSets: string[]): ISet<string>[] {
261
261
  return flagSets.map(flagSet => {
262
262
  const flagSetKey = this.keys.buildFlagSetKey(flagSet);
263
- let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
263
+ const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
264
264
 
265
265
  return new _Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
266
266
  });
@@ -275,10 +275,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
275
275
 
276
276
  const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
277
277
 
278
- let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
279
- if (!flagSetFromLocalStorage) flagSetFromLocalStorage = '[]';
278
+ const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
280
279
 
281
- const flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
280
+ const flagSetCache = new _Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
282
281
  flagSetCache.add(featureFlag.name);
283
282
 
284
283
  localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
@@ -296,7 +295,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
296
295
  private removeNames(flagSetName: string, featureFlagName: string) {
297
296
  const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
298
297
 
299
- let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
298
+ const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
300
299
 
301
300
  if (!flagSetFromLocalStorage) return;
302
301
 
@@ -1,19 +1,19 @@
1
1
  import { IEventsCacheAsync } from '../types';
2
2
  import { IMetadata } from '../../dtos/types';
3
- import { Redis } from 'ioredis';
4
3
  import { SplitIO } from '../../types';
5
4
  import { ILogger } from '../../logger/types';
6
5
  import { LOG_PREFIX } from './constants';
7
6
  import { StoredEventWithMetadata } from '../../sync/submitters/types';
7
+ import type { RedisAdapter } from './RedisAdapter';
8
8
 
9
9
  export class EventsCacheInRedis implements IEventsCacheAsync {
10
10
 
11
11
  private readonly log: ILogger;
12
12
  private readonly key: string;
13
- private readonly redis: Redis;
13
+ private readonly redis: RedisAdapter;
14
14
  private readonly metadata: IMetadata;
15
15
 
16
- constructor(log: ILogger, key: string, redis: Redis, metadata: IMetadata) {
16
+ constructor(log: ILogger, key: string, redis: RedisAdapter, metadata: IMetadata) {
17
17
  this.log = log;
18
18
  this.key = key;
19
19
  this.redis = redis;
@@ -1,19 +1,19 @@
1
- import { Redis } from 'ioredis';
2
1
  import { ILogger } from '../../logger/types';
3
2
  import { ImpressionCountsPayload } from '../../sync/submitters/types';
4
3
  import { forOwn } from '../../utils/lang';
5
4
  import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
6
5
  import { LOG_PREFIX, REFRESH_RATE, TTL_REFRESH } from './constants';
6
+ import type { RedisAdapter } from './RedisAdapter';
7
7
 
8
8
  export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory {
9
9
 
10
10
  private readonly log: ILogger;
11
11
  private readonly key: string;
12
- private readonly redis: Redis;
12
+ private readonly redis: RedisAdapter;
13
13
  private readonly refreshRate: number;
14
14
  private intervalId: any;
15
15
 
16
- constructor(log: ILogger, key: string, redis: Redis, impressionCountsCacheSize?: number, refreshRate = REFRESH_RATE) {
16
+ constructor(log: ILogger, key: string, redis: RedisAdapter, impressionCountsCacheSize?: number, refreshRate = REFRESH_RATE) {
17
17
  super(impressionCountsCacheSize);
18
18
  this.log = log;
19
19
  this.key = key;
@@ -27,11 +27,7 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
27
27
  const keys = Object.keys(counts);
28
28
  if (!keys.length) return Promise.resolve(false);
29
29
 
30
- const pipeline = this.redis.pipeline();
31
- keys.forEach(key => {
32
- pipeline.hincrby(this.key, key, counts[key]);
33
- });
34
- return pipeline.exec()
30
+ return this.redis.pipelineExec(keys.map(key => ['hincrby', this.key, key, counts[key]]))
35
31
  .then(data => {
36
32
  // If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
37
33
  if (data.length && data.length === keys.length) {
@@ -1,10 +1,10 @@
1
1
  import { IImpressionsCacheAsync } from '../types';
2
2
  import { IMetadata } from '../../dtos/types';
3
3
  import { ImpressionDTO } from '../../types';
4
- import { Redis } from 'ioredis';
5
4
  import { StoredImpressionWithMetadata } from '../../sync/submitters/types';
6
5
  import { ILogger } from '../../logger/types';
7
6
  import { impressionsToJSON } from '../utils';
7
+ import type { RedisAdapter } from './RedisAdapter';
8
8
 
9
9
  const IMPRESSIONS_TTL_REFRESH = 3600; // 1 hr
10
10
 
@@ -12,10 +12,10 @@ export class ImpressionsCacheInRedis implements IImpressionsCacheAsync {
12
12
 
13
13
  private readonly log: ILogger;
14
14
  private readonly key: string;
15
- private readonly redis: Redis;
15
+ private readonly redis: RedisAdapter;
16
16
  private readonly metadata: IMetadata;
17
17
 
18
- constructor(log: ILogger, key: string, redis: Redis, metadata: IMetadata) {
18
+ constructor(log: ILogger, key: string, redis: RedisAdapter, metadata: IMetadata) {
19
19
  this.log = log;
20
20
  this.key = key;
21
21
  this.redis = redis;
@@ -8,7 +8,7 @@ import { timeout } from '../../utils/promise/timeout';
8
8
  const LOG_PREFIX = 'storage:redis-adapter: ';
9
9
 
10
10
  // If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
11
- const METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim', 'hset'];
11
+ const METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw', 'flushdb', 'pipelineExec'];
12
12
 
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
  const DEFAULT_OPTIONS = {
@@ -38,7 +38,7 @@ export class RedisAdapter extends ioredis {
38
38
  private _notReadyCommandsQueue?: IRedisCommand[];
39
39
  private _runningCommands: ISet<Promise<any>>;
40
40
 
41
- constructor(log: ILogger, storageSettings: Record<string, any>) {
41
+ constructor(log: ILogger, storageSettings?: Record<string, any>) {
42
42
  const options = RedisAdapter._defineOptions(storageSettings);
43
43
  // Call the ioredis constructor
44
44
  super(...RedisAdapter._defineLibrarySettings(options));
@@ -52,10 +52,15 @@ export class RedisAdapter extends ioredis {
52
52
  this._setDisconnectWrapper();
53
53
  }
54
54
 
55
+ pipelineExec(commands?: (string | number)[][]): Promise<Array<[Error | null, any]>> {
56
+ return this.pipeline(commands as string[][]).exec();
57
+ }
58
+
55
59
  _listenToEvents() {
56
60
  this.once('ready', () => {
57
61
  const commandsCount = this._notReadyCommandsQueue ? this._notReadyCommandsQueue.length : 0;
58
62
  this.log.info(LOG_PREFIX + `Redis connection established. Queued commands: ${commandsCount}.`);
63
+
59
64
  this._notReadyCommandsQueue && this._notReadyCommandsQueue.forEach(queued => {
60
65
  this.log.info(LOG_PREFIX + `Executing queued ${queued.name} command.`);
61
66
  queued.command().then(queued.resolve).catch(queued.reject);
@@ -71,29 +76,28 @@ export class RedisAdapter extends ioredis {
71
76
  _setTimeoutWrappers() {
72
77
  const instance: Record<string, any> = this;
73
78
 
74
- METHODS_TO_PROMISE_WRAP.forEach(method => {
75
- const originalMethod = instance[method];
79
+ METHODS_TO_PROMISE_WRAP.forEach(methodName => {
80
+ const originalMethod = instance[methodName];
76
81
 
77
- instance[method] = function () {
82
+ instance[methodName] = function () {
78
83
  const params = arguments;
79
84
 
80
85
  function commandWrapper() {
81
- instance.log.debug(LOG_PREFIX + `Executing ${method}.`);
82
- // Return original method
86
+ instance.log.debug(`${LOG_PREFIX}Executing ${methodName}.`);
83
87
  const result = originalMethod.apply(instance, params);
84
88
 
85
89
  if (thenable(result)) {
86
90
  // For handling pending commands on disconnect, add to the set and remove once finished.
87
91
  // On sync commands there's no need, only thenables.
88
92
  instance._runningCommands.add(result);
89
- const cleanUpRunningCommandsCb = function () {
93
+ const cleanUpRunningCommandsCb = () => {
90
94
  instance._runningCommands.delete(result);
91
95
  };
92
96
  // Both success and error remove from queue.
93
97
  result.then(cleanUpRunningCommandsCb, cleanUpRunningCommandsCb);
94
98
 
95
99
  return timeout(instance._options.operationTimeout, result).catch(err => {
96
- instance.log.error(LOG_PREFIX + `${method} operation threw an error or exceeded configured timeout of ${instance._options.operationTimeout}ms. Message: ${err}`);
100
+ instance.log.error(`${LOG_PREFIX}${methodName} operation threw an error or exceeded configured timeout of ${instance._options.operationTimeout}ms. Message: ${err}`);
97
101
  // Handling is not the adapter responsibility.
98
102
  throw err;
99
103
  });
@@ -108,7 +112,7 @@ export class RedisAdapter extends ioredis {
108
112
  resolve: res,
109
113
  reject: rej,
110
114
  command: commandWrapper,
111
- name: method.toUpperCase()
115
+ name: methodName.toUpperCase()
112
116
  });
113
117
  });
114
118
  } else {
@@ -124,7 +128,7 @@ export class RedisAdapter extends ioredis {
124
128
 
125
129
  instance.disconnect = function disconnect(...params: []) {
126
130
 
127
- setTimeout(function deferedDisconnect() {
131
+ setTimeout(function deferredDisconnect() {
128
132
  if (instance._runningCommands.size > 0) {
129
133
  instance.log.info(LOG_PREFIX + `Attempting to disconnect but there are ${instance._runningCommands.size} commands still waiting for resolution. Defering disconnection until those finish.`);
130
134
 
@@ -177,7 +181,7 @@ export class RedisAdapter extends ioredis {
177
181
  /**
178
182
  * Parses the options into what we care about.
179
183
  */
180
- static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any>) {
184
+ static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any> = {}) {
181
185
  const parsedOptions = {
182
186
  connectionTimeout, operationTimeout, url, host, port, db, pass, tls
183
187
  };
@@ -1,17 +1,17 @@
1
- import { Redis } from 'ioredis';
2
1
  import { ILogger } from '../../logger/types';
3
2
  import { isNaNNumber } from '../../utils/lang';
4
3
  import { LOG_PREFIX } from '../inLocalStorage/constants';
5
4
  import { KeyBuilderSS } from '../KeyBuilderSS';
6
5
  import { ISegmentsCacheAsync } from '../types';
6
+ import type { RedisAdapter } from './RedisAdapter';
7
7
 
8
8
  export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
9
9
 
10
10
  private readonly log: ILogger;
11
- private readonly redis: Redis;
11
+ private readonly redis: RedisAdapter;
12
12
  private readonly keys: KeyBuilderSS;
13
13
 
14
- constructor(log: ILogger, keys: KeyBuilderSS, redis: Redis) {
14
+ constructor(log: ILogger, keys: KeyBuilderSS, redis: RedisAdapter) {
15
15
  this.log = log;
16
16
  this.redis = redis;
17
17
  this.keys = keys;