@splitsoftware/splitio-commons 1.11.0 → 1.12.1-rc.1

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 (81) hide show
  1. package/CHANGES.txt +4 -0
  2. package/cjs/evaluator/index.js +19 -3
  3. package/cjs/logger/constants.js +4 -2
  4. package/cjs/logger/messages/warn.js +3 -1
  5. package/cjs/sdkClient/client.js +1 -1
  6. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +2 -7
  7. package/cjs/storages/inMemory/SplitsCacheInMemory.js +2 -10
  8. package/cjs/storages/inRedis/SplitsCacheInRedis.js +29 -12
  9. package/cjs/storages/pluggable/SplitsCachePluggable.js +25 -8
  10. package/cjs/storages/pluggable/index.js +1 -1
  11. package/cjs/utils/lang/sets.js +11 -1
  12. package/cjs/utils/settingsValidation/index.js +1 -1
  13. package/cjs/utils/settingsValidation/splitFilters.js +17 -10
  14. package/esm/evaluator/index.js +20 -4
  15. package/esm/logger/constants.js +2 -0
  16. package/esm/logger/messages/warn.js +3 -1
  17. package/esm/sdkClient/client.js +1 -1
  18. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +3 -8
  19. package/esm/storages/inMemory/SplitsCacheInMemory.js +3 -11
  20. package/esm/storages/inRedis/SplitsCacheInRedis.js +30 -13
  21. package/esm/storages/pluggable/SplitsCachePluggable.js +26 -9
  22. package/esm/storages/pluggable/index.js +1 -1
  23. package/esm/utils/lang/sets.js +9 -0
  24. package/esm/utils/settingsValidation/index.js +1 -1
  25. package/esm/utils/settingsValidation/splitFilters.js +9 -2
  26. package/package.json +1 -1
  27. package/src/evaluator/index.ts +24 -4
  28. package/src/logger/constants.ts +2 -0
  29. package/src/logger/messages/warn.ts +9 -7
  30. package/src/sdkClient/client.ts +1 -1
  31. package/src/storages/AbstractSplitsCacheAsync.ts +1 -1
  32. package/src/storages/AbstractSplitsCacheSync.ts +1 -1
  33. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +4 -10
  34. package/src/storages/inMemory/SplitsCacheInMemory.ts +6 -14
  35. package/src/storages/inRedis/SplitsCacheInRedis.ts +37 -15
  36. package/src/storages/pluggable/SplitsCachePluggable.ts +32 -10
  37. package/src/storages/pluggable/index.ts +1 -1
  38. package/src/storages/types.ts +5 -5
  39. package/src/types.ts +0 -2
  40. package/src/utils/lang/sets.ts +9 -1
  41. package/src/utils/settingsValidation/index.ts +1 -1
  42. package/src/utils/settingsValidation/splitFilters.ts +13 -6
  43. package/types/evaluator/index.d.ts +1 -1
  44. package/types/logger/constants.d.ts +2 -0
  45. package/types/sdkClient/identity.d.ts +6 -0
  46. package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
  47. package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
  48. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +1 -1
  49. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +1 -1
  50. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +6 -5
  51. package/types/storages/pluggable/SplitsCachePluggable.d.ts +7 -6
  52. package/types/storages/types.d.ts +5 -5
  53. package/types/types.d.ts +0 -2
  54. package/types/utils/inputValidation/sdkKey.d.ts +7 -0
  55. package/types/utils/lang/sets.d.ts +1 -0
  56. package/types/utils/settingsValidation/splitFilters.d.ts +2 -1
  57. package/types/myLogger.d.ts +0 -5
  58. package/types/sdkClient/types.d.ts +0 -18
  59. package/types/storages/inMemory/CountsCacheInMemory.d.ts +0 -20
  60. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +0 -20
  61. package/types/storages/inRedis/CountsCacheInRedis.d.ts +0 -9
  62. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +0 -9
  63. package/types/storages/metadataBuilder.d.ts +0 -3
  64. package/types/sync/offline/LocalhostFromFile.d.ts +0 -2
  65. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +0 -2
  66. package/types/sync/offline/updaters/splitChangesUpdater.d.ts +0 -0
  67. package/types/sync/submitters/eventsSyncTask.d.ts +0 -8
  68. package/types/sync/submitters/impressionCountsSubmitterInRedis.d.ts +0 -5
  69. package/types/sync/submitters/impressionCountsSyncTask.d.ts +0 -13
  70. package/types/sync/submitters/impressionsSyncTask.d.ts +0 -14
  71. package/types/sync/submitters/metricsSyncTask.d.ts +0 -12
  72. package/types/sync/submitters/submitterSyncTask.d.ts +0 -10
  73. package/types/sync/submitters/uniqueKeysSubmitterInRedis.d.ts +0 -5
  74. package/types/sync/syncTaskComposite.d.ts +0 -5
  75. package/types/trackers/filter/bloomFilter.d.ts +0 -10
  76. package/types/trackers/filter/dictionaryFilter.d.ts +0 -8
  77. package/types/trackers/filter/types.d.ts +0 -5
  78. package/types/utils/timeTracker/index.d.ts +0 -70
  79. /package/types/storages/inMemory/{uniqueKeysCacheInMemory.d.ts → UniqueKeysCacheInMemory.d.ts} +0 -0
  80. /package/types/storages/inMemory/{uniqueKeysCacheInMemoryCS.d.ts → UniqueKeysCacheInMemoryCS.d.ts} +0 -0
  81. /package/types/storages/inRedis/{uniqueKeysCacheInRedis.d.ts → UniqueKeysCacheInRedis.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { __extends } from "tslib";
2
2
  import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
3
3
  import { isFiniteNumber } from '../../utils/lang';
4
- import { _Set, returnSetsUnion } from '../../utils/lang/sets';
4
+ import { _Set } from '../../utils/lang/sets';
5
5
  /**
6
6
  * Default ISplitsCacheSync implementation that stores split definitions in memory.
7
7
  * Supported by all JS runtimes.
@@ -9,14 +9,13 @@ import { _Set, returnSetsUnion } from '../../utils/lang/sets';
9
9
  var SplitsCacheInMemory = /** @class */ (function (_super) {
10
10
  __extends(SplitsCacheInMemory, _super);
11
11
  function SplitsCacheInMemory(splitFiltersValidation) {
12
- if (splitFiltersValidation === void 0) { splitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }; }
13
12
  var _this = _super.call(this) || this;
14
13
  _this.splitsCache = {};
15
14
  _this.ttCache = {};
16
15
  _this.changeNumber = -1;
17
16
  _this.splitsWithSegmentsCount = 0;
18
17
  _this.flagSetsCache = {};
19
- _this.flagSetsFilter = splitFiltersValidation.groupedFilters.bySet;
18
+ _this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
20
19
  return _this;
21
20
  }
22
21
  SplitsCacheInMemory.prototype.clear = function () {
@@ -93,14 +92,7 @@ var SplitsCacheInMemory = /** @class */ (function (_super) {
93
92
  };
94
93
  SplitsCacheInMemory.prototype.getNamesByFlagSets = function (flagSets) {
95
94
  var _this = this;
96
- var toReturn = new _Set([]);
97
- flagSets.forEach(function (flagSet) {
98
- var featureFlagNames = _this.flagSetsCache[flagSet];
99
- if (featureFlagNames) {
100
- toReturn = returnSetsUnion(toReturn, featureFlagNames);
101
- }
102
- });
103
- return toReturn;
95
+ return flagSets.map(function (flagSet) { return _this.flagSetsCache[flagSet] || new _Set(); });
104
96
  };
105
97
  SplitsCacheInMemory.prototype.addToFlagSets = function (featureFlag) {
106
98
  var _this = this;
@@ -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
  };
@@ -171,12 +186,14 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
171
186
  /**
172
187
  * Get list of split names related to a given flag set names list.
173
188
  * 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
189
+ * or rejected if any wrapper 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 Promise.all(flagSets.map(function (flagSet) {
194
+ var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
195
+ return _this.redis.smembers(flagSetKey);
196
+ })).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new _Set(namesByFlagSet); }); });
180
197
  };
181
198
  /**
182
199
  * Check traffic type existence.
@@ -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
  * ISplitsCacheAsync implementation for pluggable storages.
7
8
  */
@@ -13,11 +14,12 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
13
14
  * @param keys Key builder.
14
15
  * @param wrapper Adapted wrapper storage.
15
16
  */
16
- function SplitsCachePluggable(log, keys, wrapper) {
17
+ function SplitsCachePluggable(log, keys, wrapper, splitFiltersValidation) {
17
18
  var _this = _super.call(this) || this;
18
19
  _this.log = log;
19
20
  _this.keys = keys;
20
21
  _this.wrapper = wrapper;
22
+ _this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
21
23
  return _this;
22
24
  }
23
25
  SplitsCachePluggable.prototype._decrementCounts = function (split) {
@@ -32,6 +34,18 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
32
34
  var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
33
35
  return this.wrapper.incr(ttKey);
34
36
  };
37
+ SplitsCachePluggable.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
38
+ var _this = this;
39
+ var removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
40
+ var addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
41
+ if (this.flagSetsFilter.length > 0) {
42
+ addToFlagSets = addToFlagSets.filter(function (flagSet) {
43
+ return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
44
+ });
45
+ }
46
+ var items = [featureFlagName];
47
+ return Promise.all(__spreadArray(__spreadArray([], removeFromFlagSets.map(function (flagSetName) { return _this.wrapper.removeItems(_this.keys.buildFlagSetKey(flagSetName), items); }), true), addToFlagSets.map(function (flagSetName) { return _this.wrapper.addItems(_this.keys.buildFlagSetKey(flagSetName), items); }), true));
48
+ };
35
49
  /**
36
50
  * Add a given split.
37
51
  * The returned promise is resolved when the operation success
@@ -59,7 +73,7 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
59
73
  if (parsedPreviousSplit)
60
74
  return _this._decrementCounts(parsedPreviousSplit);
61
75
  });
62
- });
76
+ }).then(function () { return _this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets); });
63
77
  }).then(function () { return true; });
64
78
  };
65
79
  /**
@@ -80,8 +94,9 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
80
94
  var _this = this;
81
95
  return this.getSplit(name).then(function (split) {
82
96
  if (split) {
83
- _this._decrementCounts(split);
97
+ return _this._decrementCounts(split).then(function () { return _this._updateFlagSets(name, split.sets); });
84
98
  }
99
+ }).then(function () {
85
100
  return _this.wrapper.del(_this.keys.buildSplitKey(name));
86
101
  });
87
102
  };
@@ -145,12 +160,14 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
145
160
  /**
146
161
  * Get list of split names related to a given flag set names list.
147
162
  * The returned promise is resolved with the list of split names,
148
- * or rejected if wrapper operation fails.
149
- * @todo this is a no-op method to be implemented
163
+ * or rejected if any wrapper operation fails.
150
164
  */
151
- SplitsCachePluggable.prototype.getNamesByFlagSets = function () {
152
- this.log.error(LOG_PREFIX + 'ByFlagSet/s evaluations are not supported with pluggable storage yet.');
153
- return Promise.reject();
165
+ SplitsCachePluggable.prototype.getNamesByFlagSets = function (flagSets) {
166
+ var _this = this;
167
+ return Promise.all(flagSets.map(function (flagSet) {
168
+ var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
169
+ return _this.wrapper.getItems(flagSetKey);
170
+ })).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new _Set(namesByFlagSet); }); });
154
171
  };
155
172
  /**
156
173
  * Check traffic type existence.
@@ -89,7 +89,7 @@ export function PluggableStorage(options) {
89
89
  return e; // Propagate error for shared clients
90
90
  });
91
91
  return {
92
- splits: new SplitsCachePluggable(log, keys, wrapper),
92
+ splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
93
93
  segments: new SegmentsCachePluggable(log, keys, wrapper),
94
94
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
95
95
  impressionCounts: impressionCountsCache,
@@ -102,3 +102,12 @@ export function returnSetsUnion(set, set2) {
102
102
  });
103
103
  return result;
104
104
  }
105
+ export function returnListDifference(list, list2) {
106
+ if (list === void 0) { list = []; }
107
+ if (list2 === void 0) { list2 = []; }
108
+ var result = new _Set(list);
109
+ list2.forEach(function (item) {
110
+ result.delete(item);
111
+ });
112
+ return setToArray(result);
113
+ }
@@ -176,7 +176,7 @@ export function settingsValidation(config, validationParams) {
176
176
  withDefaults.sync.enabled = true;
177
177
  }
178
178
  // validate the `splitFilters` settings and parse splits query
179
- var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters);
179
+ var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
180
180
  withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
181
181
  withDefaults.sync.__splitFiltersValidation = splitFiltersValidation;
182
182
  // ensure a valid user consent value
@@ -1,5 +1,6 @@
1
+ import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
1
2
  import { validateSplits } from '../inputValidation/splits';
2
- import { WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
3
+ import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
3
4
  import { objectAssign } from '../lang/objectAssign';
4
5
  import { find, uniq } from '../lang';
5
6
  // Split filters metadata.
@@ -117,6 +118,7 @@ function configuredFilter(validFilters, filterType) {
117
118
  *
118
119
  * @param {ILogger} log logger
119
120
  * @param {any} maybeSplitFilters split filters configuration param provided by the user
121
+ * @param {string} mode settings mode
120
122
  * @returns it returns an object with the following properties:
121
123
  * - `validFilters`: the validated `splitFilters` configuration object defined by the user.
122
124
  * - `queryString`: the parsed split filter query. it is null if all filters are invalid or all values in filters are invalid.
@@ -124,7 +126,7 @@ function configuredFilter(validFilters, filterType) {
124
126
  *
125
127
  * @throws Error if the some of the grouped list of values per filter exceeds the max allowed length
126
128
  */
127
- export function validateSplitFilters(log, maybeSplitFilters) {
129
+ export function validateSplitFilters(log, maybeSplitFilters, mode) {
128
130
  // Validation result schema
129
131
  var res = {
130
132
  validFilters: [],
@@ -134,6 +136,11 @@ export function validateSplitFilters(log, maybeSplitFilters) {
134
136
  // do nothing if `splitFilters` param is not a non-empty array or mode is not STANDALONE
135
137
  if (!maybeSplitFilters)
136
138
  return res;
139
+ // Warn depending on the mode
140
+ if (mode === CONSUMER_MODE || mode === CONSUMER_PARTIAL_MODE) {
141
+ log.warn(WARN_SPLITS_FILTER_IGNORED);
142
+ return res;
143
+ }
137
144
  // Check collection type
138
145
  if (!Array.isArray(maybeSplitFilters) || maybeSplitFilters.length === 0) {
139
146
  log.warn(WARN_SPLITS_FILTER_EMPTY);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.11.0",
3
+ "version": "1.12.1-rc.1",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -7,7 +7,8 @@ import { IStorageAsync, IStorageSync } from '../storages/types';
7
7
  import { IEvaluationResult } from './types';
8
8
  import { SplitIO } from '../types';
9
9
  import { ILogger } from '../logger/types';
10
- import { ISet, setToArray } from '../utils/lang/sets';
10
+ import { ISet, setToArray, returnSetsUnion, _Set } from '../utils/lang/sets';
11
+ import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
11
12
 
12
13
  const treatmentException = {
13
14
  treatment: CONTROL,
@@ -94,8 +95,27 @@ export function evaluateFeaturesByFlagSets(
94
95
  flagSets: string[],
95
96
  attributes: SplitIO.Attributes | undefined,
96
97
  storage: IStorageSync | IStorageAsync,
98
+ method: string,
97
99
  ): MaybeThenable<Record<string, IEvaluationResult>> {
98
- let storedFlagNames: MaybeThenable<ISet<string>>;
100
+ let storedFlagNames: MaybeThenable<ISet<string>[]>;
101
+
102
+ function evaluate(
103
+ featureFlagsByFlagSets: ISet<string>[],
104
+ ) {
105
+ let featureFlags = new _Set();
106
+ for (let i = 0; i < flagSets.length; i++) {
107
+ const featureFlagByFlagSet = featureFlagsByFlagSets[i];
108
+ if (featureFlagByFlagSet.size) {
109
+ featureFlags = returnSetsUnion(featureFlags, featureFlagByFlagSet);
110
+ } else {
111
+ log.warn(WARN_FLAGSET_WITHOUT_FLAGS, [method, flagSets[i]]);
112
+ }
113
+ }
114
+
115
+ return featureFlags.size ?
116
+ evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
117
+ {};
118
+ }
99
119
 
100
120
  // get features by flag sets
101
121
  try {
@@ -107,11 +127,11 @@ export function evaluateFeaturesByFlagSets(
107
127
 
108
128
  // evaluate related features
109
129
  return thenable(storedFlagNames) ?
110
- storedFlagNames.then((splitNames) => evaluateFeatures(log, key, setToArray(splitNames), attributes, storage))
130
+ storedFlagNames.then((storedFlagNames) => evaluate(storedFlagNames))
111
131
  .catch(() => {
112
132
  return {};
113
133
  }) :
114
- evaluateFeatures(log, key, setToArray(storedFlagNames), attributes, storage);
134
+ evaluate(storedFlagNames);
115
135
  }
116
136
 
117
137
  function getEvaluation(
@@ -91,6 +91,7 @@ export const WARN_NOT_EXISTENT_SPLIT = 215;
91
91
  export const WARN_LOWERCASE_TRAFFIC_TYPE = 216;
92
92
  export const WARN_NOT_EXISTENT_TT = 217;
93
93
  export const WARN_INTEGRATION_INVALID = 218;
94
+ export const WARN_SPLITS_FILTER_IGNORED = 219;
94
95
  export const WARN_SPLITS_FILTER_INVALID = 220;
95
96
  export const WARN_SPLITS_FILTER_EMPTY = 221;
96
97
  export const WARN_SDK_KEY = 222;
@@ -99,6 +100,7 @@ export const STREAMING_PARSING_SPLIT_UPDATE = 224;
99
100
  export const WARN_SPLITS_FILTER_INVALID_SET = 225;
100
101
  export const WARN_SPLITS_FILTER_LOWERCASE_SET = 226;
101
102
  export const WARN_FLAGSET_NOT_CONFIGURED = 227;
103
+ export const WARN_FLAGSET_WITHOUT_FLAGS = 228;
102
104
 
103
105
  export const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
104
106
  export const ERROR_LOGLEVEL_INVALID = 301;
@@ -24,15 +24,17 @@ export const codesWarn: [number, string][] = codesError.concat([
24
24
  [c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in the Split user interface.'],
25
25
  [c.WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'],
26
26
  [c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding feature flag in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split user interface.'],
27
- [c.WARN_FLAGSET_NOT_CONFIGURED, '%s: : you passed %s wich is not part of the configured FlagSetsFilter, ignoring Flag Set.'],
27
+ [c.WARN_FLAGSET_NOT_CONFIGURED, '%s: you passed %s which is not part of the configured FlagSetsFilter, ignoring Flag Set.'],
28
28
  // initialization / settings validation
29
- [c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS+': %s integration item(s) at settings is invalid. %s'],
30
- [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("bySet", "byName" or "byPrefix") and a list of "values".'],
31
- [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': feature flag filter configuration must be a non-empty array of filter objects.'],
32
- [c.WARN_SDK_KEY, c.LOG_PREFIX_SETTINGS+': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
29
+ [c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS + ': %s integration item(s) at settings is invalid. %s'],
30
+ [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS + ': feature flag filters are not applicable for Consumer modes where the SDK does not keep rollout data in sync. Filters were discarded'],
31
+ [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS + ': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("bySet", "byName" or "byPrefix") and a list of "values".'],
32
+ [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS + ': feature flag filter configuration must be a non-empty array of filter objects.'],
33
+ [c.WARN_SDK_KEY, c.LOG_PREFIX_SETTINGS + ': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
33
34
 
34
35
  [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
35
36
  [c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'],
36
- [c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS+': you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
37
- [c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS+': flag set %s should be all lowercase - converting string to lowercase.'],
37
+ [c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS + ': you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
38
+ [c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS + ': flag set %s should be all lowercase - converting string to lowercase.'],
39
+ [c.WARN_FLAGSET_WITHOUT_FLAGS, '%s: you passed %s flag set that does not contain cached feature flag names. Please double check what flag sets are in use in the Split user interface.'],
38
40
  ]);
@@ -99,7 +99,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
99
99
  };
100
100
 
101
101
  const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
102
- evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage) :
102
+ evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, method) :
103
103
  isStorageSync(settings) ? {} : Promise.resolve({}); // Promisify if async
104
104
 
105
105
  return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
@@ -18,7 +18,7 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
18
18
  abstract getChangeNumber(): Promise<number>
19
19
  abstract getAll(): Promise<ISplit[]>
20
20
  abstract getSplitNames(): Promise<string[]>
21
- abstract getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>>
21
+ abstract getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
22
22
  abstract trafficTypeExists(trafficType: string): Promise<boolean>
23
23
  abstract clear(): Promise<boolean | void>
24
24
 
@@ -79,7 +79,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
79
79
  return false;
80
80
  }
81
81
 
82
- abstract getNamesByFlagSets(flagSets: string[]): ISet<string>
82
+ abstract getNamesByFlagSets(flagSets: string[]): ISet<string>[]
83
83
 
84
84
  }
85
85
 
@@ -4,7 +4,7 @@ import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
4
4
  import { KeyBuilderCS } from '../KeyBuilderCS';
5
5
  import { ILogger } from '../../logger/types';
6
6
  import { LOG_PREFIX } from './constants';
7
- import { ISet, _Set, returnSetsUnion, setToArray } from '../../utils/lang/sets';
7
+ import { ISet, _Set, setToArray } from '../../utils/lang/sets';
8
8
 
9
9
  /**
10
10
  * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
@@ -257,19 +257,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
257
257
  // if the filter didn't change, nothing is done
258
258
  }
259
259
 
260
- getNamesByFlagSets(flagSets: string[]): ISet<string>{
261
- let toReturn: ISet<string> = new _Set([]);
262
- flagSets.forEach(flagSet => {
260
+ getNamesByFlagSets(flagSets: string[]): ISet<string>[] {
261
+ return flagSets.map(flagSet => {
263
262
  const flagSetKey = this.keys.buildFlagSetKey(flagSet);
264
263
  let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
265
264
 
266
- if (flagSetFromLocalStorage) {
267
- const flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
268
- toReturn = returnSetsUnion(toReturn, flagSetCache);
269
- }
265
+ return new _Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
270
266
  });
271
- return toReturn;
272
-
273
267
  }
274
268
 
275
269
  private addToFlagSets(featureFlag: ISplit) {
@@ -1,7 +1,7 @@
1
1
  import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
2
2
  import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
3
3
  import { isFiniteNumber } from '../../utils/lang';
4
- import { ISet, _Set, returnSetsUnion } from '../../utils/lang/sets';
4
+ import { ISet, _Set } from '../../utils/lang/sets';
5
5
 
6
6
  /**
7
7
  * Default ISplitsCacheSync implementation that stores split definitions in memory.
@@ -16,9 +16,9 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
16
16
  private splitsWithSegmentsCount: number = 0;
17
17
  private flagSetsCache: Record<string, ISet<string>> = {};
18
18
 
19
- constructor(splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) {
19
+ constructor(splitFiltersValidation?: ISplitFiltersValidation) {
20
20
  super();
21
- this.flagSetsFilter = splitFiltersValidation.groupedFilters.bySet;
21
+ this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
22
22
  }
23
23
 
24
24
  clear() {
@@ -105,16 +105,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
105
105
  return this.getChangeNumber() === -1 || this.splitsWithSegmentsCount > 0;
106
106
  }
107
107
 
108
- getNamesByFlagSets(flagSets: string[]): ISet<string>{
109
- let toReturn: ISet<string> = new _Set([]);
110
- flagSets.forEach(flagSet => {
111
- const featureFlagNames = this.flagSetsCache[flagSet];
112
- if (featureFlagNames) {
113
- toReturn = returnSetsUnion(toReturn, featureFlagNames);
114
- }
115
- });
116
- return toReturn;
117
-
108
+ getNamesByFlagSets(flagSets: string[]): ISet<string>[] {
109
+ return flagSets.map(flagSet => this.flagSetsCache[flagSet] || new _Set());
118
110
  }
119
111
 
120
112
  private addToFlagSets(featureFlag: ISplit) {
@@ -129,7 +121,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
129
121
  });
130
122
  }
131
123
 
132
- private removeFromFlagSets(featureFlagName :string, flagSets: string[] | undefined) {
124
+ private removeFromFlagSets(featureFlagName: string, flagSets: string[] | undefined) {
133
125
  if (!flagSets) return;
134
126
  flagSets.forEach(flagSet => {
135
127
  this.removeNames(flagSet, featureFlagName);
@@ -3,9 +3,9 @@ import { KeyBuilderSS } from '../KeyBuilderSS';
3
3
  import { Redis } from 'ioredis';
4
4
  import { ILogger } from '../../logger/types';
5
5
  import { LOG_PREFIX } from './constants';
6
- import { ISplit } from '../../dtos/types';
6
+ import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
7
7
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
8
- import { ISet } from '../../utils/lang/sets';
8
+ import { ISet, _Set, returnListDifference } from '../../utils/lang/sets';
9
9
 
10
10
  /**
11
11
  * Discard errors for an answer of multiple operations.
@@ -27,12 +27,14 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
27
27
  private readonly redis: Redis;
28
28
  private readonly keys: KeyBuilderSS;
29
29
  private redisError?: string;
30
+ private readonly flagSetsFilter: string[];
30
31
 
31
- constructor(log: ILogger, keys: KeyBuilderSS, redis: Redis) {
32
+ constructor(log: ILogger, keys: KeyBuilderSS, redis: Redis, splitFiltersValidation?: ISplitFiltersValidation) {
32
33
  super();
33
34
  this.log = log;
34
35
  this.redis = redis;
35
36
  this.keys = keys;
37
+ this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
36
38
 
37
39
  // 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.
38
40
  // But it is done just to avoid getting the ioredis message `Unhandled error event`.
@@ -57,6 +59,24 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
57
59
  return this.redis.incr(ttKey);
58
60
  }
59
61
 
62
+ private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
63
+ const removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
64
+
65
+ let addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
66
+ if (this.flagSetsFilter.length > 0) {
67
+ addToFlagSets = addToFlagSets.filter(flagSet => {
68
+ return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
69
+ });
70
+ }
71
+
72
+ const items = [featureFlagName];
73
+
74
+ return Promise.all([
75
+ ...removeFromFlagSets.map(flagSetName => this.redis.srem(this.keys.buildFlagSetKey(flagSetName), items)),
76
+ ...addToFlagSets.map(flagSetName => this.redis.sadd(this.keys.buildFlagSetKey(flagSetName), items))
77
+ ]);
78
+ }
79
+
60
80
  /**
61
81
  * Add a given split.
62
82
  * The returned promise is resolved when the operation success
@@ -66,16 +86,16 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
66
86
  const splitKey = this.keys.buildSplitKey(name);
67
87
  return this.redis.get(splitKey).then(splitFromStorage => {
68
88
 
69
- // handling parsing errors
70
- let parsedPreviousSplit: ISplit, newStringifiedSplit;
89
+ // handling parsing error
90
+ let parsedPreviousSplit: ISplit, stringifiedNewSplit;
71
91
  try {
72
92
  parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined;
73
- newStringifiedSplit = JSON.stringify(split);
93
+ stringifiedNewSplit = JSON.stringify(split);
74
94
  } catch (e) {
75
95
  throw new Error('Error parsing feature flag definition: ' + e);
76
96
  }
77
97
 
78
- return this.redis.set(splitKey, newStringifiedSplit).then(() => {
98
+ return this.redis.set(splitKey, stringifiedNewSplit).then(() => {
79
99
  // avoid unnecessary increment/decrement operations
80
100
  if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName) return;
81
101
 
@@ -83,7 +103,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
83
103
  return this._incrementCounts(split).then(() => {
84
104
  if (parsedPreviousSplit) return this._decrementCounts(parsedPreviousSplit);
85
105
  });
86
- });
106
+ }).then(() => this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets));
87
107
  }).then(() => true);
88
108
  }
89
109
 
@@ -101,11 +121,12 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
101
121
  * The returned promise is resolved when the operation success, with 1 or 0 indicating if the split existed or not.
102
122
  * or rejected if it fails (e.g., redis operation fails).
103
123
  */
104
- removeSplit(name: string): Promise<number> {
124
+ removeSplit(name: string) {
105
125
  return this.getSplit(name).then((split) => {
106
126
  if (split) {
107
- this._decrementCounts(split);
127
+ return this._decrementCounts(split).then(() => this._updateFlagSets(name, split.sets));
108
128
  }
129
+ }).then(() => {
109
130
  return this.redis.del(this.keys.buildSplitKey(name));
110
131
  });
111
132
  }
@@ -192,12 +213,13 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
192
213
  /**
193
214
  * Get list of split names related to a given flag set names list.
194
215
  * The returned promise is resolved with the list of split names,
195
- * or rejected if wrapper operation fails.
196
- * @todo this is a no-op method to be implemented
216
+ * or rejected if any wrapper operation fails.
197
217
  */
198
- getNamesByFlagSets(): Promise<ISet<string>> {
199
- this.log.error(LOG_PREFIX + 'ByFlagSet/s evaluations are not supported with Redis storage yet.');
200
- return Promise.reject();
218
+ getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]> {
219
+ return Promise.all(flagSets.map(flagSet => {
220
+ const flagSetKey = this.keys.buildFlagSetKey(flagSet);
221
+ return this.redis.smembers(flagSetKey);
222
+ })).then(namesByFlagSets => namesByFlagSets.map(namesByFlagSet => new _Set(namesByFlagSet)));
201
223
  }
202
224
 
203
225
  /**