@splitsoftware/splitio-commons 1.12.1-rc.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.
package/CHANGES.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  1.12.0 (December XX, 2023)
2
- - Added support for Flag Sets in "consumer" and "partial consumer" modes for pluggable storage.
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
4
 
5
5
  1.11.0 (November 3, 2023)
@@ -5,6 +5,7 @@ var tslib_1 = require("tslib");
5
5
  var lang_1 = require("../../utils/lang");
6
6
  var constants_1 = require("./constants");
7
7
  var AbstractSplitsCacheAsync_1 = require("../AbstractSplitsCacheAsync");
8
+ var sets_1 = require("../../utils/lang/sets");
8
9
  /**
9
10
  * Discard errors for an answer of multiple operations.
10
11
  */
@@ -21,11 +22,12 @@ function processPipelineAnswer(results) {
21
22
  */
22
23
  var SplitsCacheInRedis = /** @class */ (function (_super) {
23
24
  (0, tslib_1.__extends)(SplitsCacheInRedis, _super);
24
- function SplitsCacheInRedis(log, keys, redis) {
25
+ function SplitsCacheInRedis(log, keys, redis, splitFiltersValidation) {
25
26
  var _this = _super.call(this) || this;
26
27
  _this.log = log;
27
28
  _this.redis = redis;
28
29
  _this.keys = keys;
30
+ _this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
29
31
  // There is no need to listen for redis 'error' event, because in that case ioredis calls will be rejected and handled by redis storage adapters.
30
32
  // But it is done just to avoid getting the ioredis message `Unhandled error event`.
31
33
  _this.redis.on('error', function (e) {
@@ -48,6 +50,18 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
48
50
  var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
49
51
  return this.redis.incr(ttKey);
50
52
  };
53
+ SplitsCacheInRedis.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
54
+ var _this = this;
55
+ var removeFromFlagSets = (0, sets_1.returnListDifference)(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
56
+ var addToFlagSets = (0, sets_1.returnListDifference)(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
57
+ if (this.flagSetsFilter.length > 0) {
58
+ addToFlagSets = addToFlagSets.filter(function (flagSet) {
59
+ return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
60
+ });
61
+ }
62
+ var items = [featureFlagName];
63
+ return Promise.all((0, tslib_1.__spreadArray)((0, tslib_1.__spreadArray)([], removeFromFlagSets.map(function (flagSetName) { return _this.redis.srem(_this.keys.buildFlagSetKey(flagSetName), items); }), true), addToFlagSets.map(function (flagSetName) { return _this.redis.sadd(_this.keys.buildFlagSetKey(flagSetName), items); }), true));
64
+ };
51
65
  /**
52
66
  * Add a given split.
53
67
  * The returned promise is resolved when the operation success
@@ -57,16 +71,16 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
57
71
  var _this = this;
58
72
  var splitKey = this.keys.buildSplitKey(name);
59
73
  return this.redis.get(splitKey).then(function (splitFromStorage) {
60
- // handling parsing errors
61
- var parsedPreviousSplit, newStringifiedSplit;
74
+ // handling parsing error
75
+ var parsedPreviousSplit, stringifiedNewSplit;
62
76
  try {
63
77
  parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined;
64
- newStringifiedSplit = JSON.stringify(split);
78
+ stringifiedNewSplit = JSON.stringify(split);
65
79
  }
66
80
  catch (e) {
67
81
  throw new Error('Error parsing feature flag definition: ' + e);
68
82
  }
69
- return _this.redis.set(splitKey, newStringifiedSplit).then(function () {
83
+ return _this.redis.set(splitKey, stringifiedNewSplit).then(function () {
70
84
  // avoid unnecessary increment/decrement operations
71
85
  if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName)
72
86
  return;
@@ -75,7 +89,7 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
75
89
  if (parsedPreviousSplit)
76
90
  return _this._decrementCounts(parsedPreviousSplit);
77
91
  });
78
- });
92
+ }).then(function () { return _this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets); });
79
93
  }).then(function () { return true; });
80
94
  };
81
95
  /**
@@ -96,8 +110,9 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
96
110
  var _this = this;
97
111
  return this.getSplit(name).then(function (split) {
98
112
  if (split) {
99
- _this._decrementCounts(split);
113
+ return _this._decrementCounts(split).then(function () { return _this._updateFlagSets(name, split.sets); });
100
114
  }
115
+ }).then(function () {
101
116
  return _this.redis.del(_this.keys.buildSplitKey(name));
102
117
  });
103
118
  };
@@ -174,12 +189,14 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
174
189
  /**
175
190
  * Get list of split names related to a given flag set names list.
176
191
  * The returned promise is resolved with the list of split names,
177
- * or rejected if wrapper operation fails.
178
- * @todo this is a no-op method to be implemented
192
+ * or rejected if any wrapper operation fails.
179
193
  */
180
- SplitsCacheInRedis.prototype.getNamesByFlagSets = function () {
181
- this.log.error(constants_1.LOG_PREFIX + 'ByFlagSet/s evaluations are not supported with Redis storage yet.');
182
- return Promise.reject();
194
+ SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
195
+ var _this = this;
196
+ return Promise.all(flagSets.map(function (flagSet) {
197
+ var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
198
+ return _this.redis.smembers(flagSetKey);
199
+ })).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new sets_1._Set(namesByFlagSet); }); });
183
200
  };
184
201
  /**
185
202
  * 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
  * 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.
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.1",
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",
@@ -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
  /**
@@ -1,7 +1,7 @@
1
1
  import { KeyBuilderSS } from '../KeyBuilderSS';
2
2
  import { Redis } from 'ioredis';
3
3
  import { ILogger } from '../../logger/types';
4
- import { ISplit } from '../../dtos/types';
4
+ import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
5
5
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
6
6
  import { ISet } from '../../utils/lang/sets';
7
7
  /**
@@ -13,9 +13,11 @@ export declare class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
13
13
  private readonly redis;
14
14
  private readonly keys;
15
15
  private redisError?;
16
- constructor(log: ILogger, keys: KeyBuilderSS, redis: Redis);
16
+ private readonly flagSetsFilter;
17
+ constructor(log: ILogger, keys: KeyBuilderSS, redis: Redis, splitFiltersValidation?: ISplitFiltersValidation);
17
18
  private _decrementCounts;
18
19
  private _incrementCounts;
20
+ private _updateFlagSets;
19
21
  /**
20
22
  * Add a given split.
21
23
  * The returned promise is resolved when the operation success
@@ -75,10 +77,9 @@ export declare class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
75
77
  /**
76
78
  * Get list of split names related to a given flag set names list.
77
79
  * The returned promise is resolved with the list of split names,
78
- * or rejected if wrapper operation fails.
79
- * @todo this is a no-op method to be implemented
80
+ * or rejected if any wrapper operation fails.
80
81
  */
81
- getNamesByFlagSets(): Promise<ISet<string>[]>;
82
+ getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>;
82
83
  /**
83
84
  * Check traffic type existence.
84
85
  * The returned promise is resolved with a boolean indicating whether the TT exist or not.