@splitsoftware/splitio-commons 1.12.1-rc.2 → 1.12.1-rc.3

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 (32) hide show
  1. package/CHANGES.txt +10 -9
  2. package/cjs/sdkFactory/index.js +1 -1
  3. package/cjs/sdkManager/index.js +7 -4
  4. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +5 -1
  5. package/cjs/storages/inRedis/RedisAdapter.js +29 -12
  6. package/cjs/storages/inRedis/SegmentsCacheInRedis.js +2 -2
  7. package/cjs/storages/inRedis/SplitsCacheInRedis.js +5 -9
  8. package/cjs/utils/redis/RedisMock.js +7 -1
  9. package/esm/sdkFactory/index.js +1 -1
  10. package/esm/sdkManager/index.js +7 -4
  11. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +5 -1
  12. package/esm/storages/inRedis/RedisAdapter.js +29 -12
  13. package/esm/storages/inRedis/SegmentsCacheInRedis.js +2 -2
  14. package/esm/storages/inRedis/SplitsCacheInRedis.js +5 -9
  15. package/esm/utils/redis/RedisMock.js +7 -1
  16. package/package.json +1 -1
  17. package/src/sdkFactory/index.ts +1 -1
  18. package/src/sdkFactory/types.ts +3 -7
  19. package/src/sdkManager/index.ts +10 -7
  20. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +5 -1
  21. package/src/storages/inRedis/RedisAdapter.ts +35 -17
  22. package/src/storages/inRedis/SegmentsCacheInRedis.ts +2 -2
  23. package/src/storages/inRedis/SplitsCacheInRedis.ts +5 -9
  24. package/src/trackers/impressionObserver/utils.ts +1 -1
  25. package/src/utils/redis/RedisMock.ts +9 -1
  26. package/types/sdkFactory/types.d.ts +3 -3
  27. package/types/sdkManager/index.d.ts +2 -3
  28. package/types/storages/inRedis/RedisAdapter.d.ts +1 -2
  29. package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +1 -1
  30. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +2 -7
  31. package/types/trackers/impressionObserver/utils.d.ts +1 -1
  32. package/types/utils/redis/RedisMock.d.ts +1 -0
package/CHANGES.txt CHANGED
@@ -1,17 +1,18 @@
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
+ - Updated Redis adapter to handle timeouts and queueing of some missing commands: 'hincrby', 'popNRaw', and 'pipeline.exec'.
5
+ - Bugfixing - Fixed manager methods in consumer modes to return results in a promise when the SDK is not operational (not ready or destroyed).
5
6
 
6
7
  1.11.0 (November 3, 2023)
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):
8
- - Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
9
- - getTreatmentsByFlagSet and getTreatmentsByFlagSets
10
- - getTreatmentsWithConfigByFlagSets and getTreatmentsWithConfigByFlagSets
11
- - Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
12
- - Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init.
13
- - Added `sets` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager to expose flag sets on flag views.
14
- - Bugfixing - Fixed SDK key validation in NodeJS to ensure the SDK_READY_TIMED_OUT event is emitted when a client-side type SDK key is provided instead of a server-side one (Related to issue https://github.com/splitio/javascript-client/issues/768).
8
+ - 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):
9
+ - Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
10
+ - getTreatmentsByFlagSet and getTreatmentsByFlagSets
11
+ - getTreatmentsWithConfigByFlagSets and getTreatmentsWithConfigByFlagSets
12
+ - Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
13
+ - Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init.
14
+ - Added `sets` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager to expose flag sets on flag views.
15
+ - Bugfixing - Fixed SDK key validation in NodeJS to ensure the SDK_READY_TIMED_OUT event is emitted when a client-side type SDK key is provided instead of a server-side one (Related to issue https://github.com/splitio/javascript-client/issues/768).
15
16
 
16
17
  1.10.0 (October 20, 2023)
17
18
  - Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager (Related to issue https://github.com/splitio/javascript-commons/issues/225).
@@ -66,7 +66,7 @@ function sdkFactory(params) {
66
66
  ctx.signalListener = signalListener;
67
67
  // SDK client and manager
68
68
  var clientMethod = sdkClientMethodFactory(ctx);
69
- var managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
69
+ var managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
70
70
  syncManager && syncManager.start();
71
71
  signalListener && signalListener.start();
72
72
  log.info(constants_1.NEW_FACTORY);
@@ -5,6 +5,7 @@ var objectAssign_1 = require("../utils/lang/objectAssign");
5
5
  var thenable_1 = require("../utils/promise/thenable");
6
6
  var lang_1 = require("../utils/lang");
7
7
  var inputValidation_1 = require("../utils/inputValidation");
8
+ var utils_1 = require("../trackers/impressionObserver/utils");
8
9
  var SPLIT_FN_LABEL = 'split';
9
10
  var SPLITS_FN_LABEL = 'splits';
10
11
  var NAMES_FN_LABEL = 'names';
@@ -41,8 +42,10 @@ function objectsToViews(splitObjects) {
41
42
  });
42
43
  return views;
43
44
  }
44
- function sdkManagerFactory(log, splits, _a) {
45
+ function sdkManagerFactory(settings, splits, _a) {
45
46
  var readinessManager = _a.readinessManager, sdkStatus = _a.sdkStatus;
47
+ var log = settings.log;
48
+ var isSync = (0, utils_1.isStorageSync)(settings);
46
49
  return (0, objectAssign_1.objectAssign)(
47
50
  // Proto-linkage of the readiness Event Emitter
48
51
  Object.create(sdkStatus), {
@@ -52,7 +55,7 @@ function sdkManagerFactory(log, splits, _a) {
52
55
  split: function (featureFlagName) {
53
56
  var splitName = (0, inputValidation_1.validateSplit)(log, featureFlagName, SPLIT_FN_LABEL);
54
57
  if (!(0, inputValidation_1.validateIfNotDestroyed)(log, readinessManager, SPLIT_FN_LABEL) || !(0, inputValidation_1.validateIfOperational)(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
55
- return null;
58
+ return isSync ? null : Promise.resolve(null);
56
59
  }
57
60
  var split = splits.getSplit(splitName);
58
61
  if ((0, thenable_1.thenable)(split)) {
@@ -69,7 +72,7 @@ function sdkManagerFactory(log, splits, _a) {
69
72
  */
70
73
  splits: function () {
71
74
  if (!(0, inputValidation_1.validateIfNotDestroyed)(log, readinessManager, SPLITS_FN_LABEL) || !(0, inputValidation_1.validateIfOperational)(log, readinessManager, SPLITS_FN_LABEL)) {
72
- return [];
75
+ return isSync ? [] : Promise.resolve([]);
73
76
  }
74
77
  var currentSplits = splits.getAll();
75
78
  return (0, thenable_1.thenable)(currentSplits) ?
@@ -81,7 +84,7 @@ function sdkManagerFactory(log, splits, _a) {
81
84
  */
82
85
  names: function () {
83
86
  if (!(0, inputValidation_1.validateIfNotDestroyed)(log, readinessManager, NAMES_FN_LABEL) || !(0, inputValidation_1.validateIfOperational)(log, readinessManager, NAMES_FN_LABEL)) {
84
- return [];
87
+ return isSync ? [] : Promise.resolve([]);
85
88
  }
86
89
  var splitNames = splits.getSplitNames();
87
90
  return (0, thenable_1.thenable)(splitNames) ?
@@ -23,7 +23,11 @@ var ImpressionCountsCacheInRedis = /** @class */ (function (_super) {
23
23
  var keys = Object.keys(counts);
24
24
  if (!keys.length)
25
25
  return Promise.resolve(false);
26
- return this.redis.pipelineExec(keys.map(function (key) { return ['hincrby', _this.key, key, counts[key]]; }))
26
+ var pipeline = this.redis.pipeline();
27
+ keys.forEach(function (key) {
28
+ pipeline.hincrby(_this.key, key, counts[key]);
29
+ });
30
+ return pipeline.exec()
27
31
  .then(function (data) {
28
32
  // If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
29
33
  if (data.length && data.length === keys.length) {
@@ -9,7 +9,8 @@ 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', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw', 'flushdb', 'pipelineExec'];
12
+ var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw'];
13
+ var METHODS_TO_PROMISE_WRAP_EXEC = ['pipeline'];
13
14
  // Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
14
15
  var DEFAULT_OPTIONS = {
15
16
  connectionTimeout: 10000,
@@ -27,6 +28,7 @@ var DEFAULT_LIBRARY_OPTIONS = {
27
28
  var RedisAdapter = /** @class */ (function (_super) {
28
29
  (0, tslib_1.__extends)(RedisAdapter, _super);
29
30
  function RedisAdapter(log, storageSettings) {
31
+ if (storageSettings === void 0) { storageSettings = {}; }
30
32
  var _this = this;
31
33
  var options = RedisAdapter._defineOptions(storageSettings);
32
34
  // Call the ioredis constructor
@@ -40,9 +42,6 @@ var RedisAdapter = /** @class */ (function (_super) {
40
42
  _this._setDisconnectWrapper();
41
43
  return _this;
42
44
  }
43
- RedisAdapter.prototype.pipelineExec = function (commands) {
44
- return this.pipeline(commands).exec();
45
- };
46
45
  RedisAdapter.prototype._listenToEvents = function () {
47
46
  var _this = this;
48
47
  this.once('ready', function () {
@@ -61,13 +60,15 @@ var RedisAdapter = /** @class */ (function (_super) {
61
60
  };
62
61
  RedisAdapter.prototype._setTimeoutWrappers = function () {
63
62
  var instance = this;
64
- METHODS_TO_PROMISE_WRAP.forEach(function (methodName) {
65
- var originalMethod = instance[methodName];
66
- instance[methodName] = function () {
63
+ var wrapCommand = function (originalMethod, methodName) {
64
+ // The value of "this" in this function should be the instance actually executing the method. It might be the instance referred (the base one)
65
+ // or it can be the instance of a Pipeline object.
66
+ return function () {
67
67
  var params = arguments;
68
+ var caller = this;
68
69
  function commandWrapper() {
69
70
  instance.log.debug(LOG_PREFIX + "Executing " + methodName + ".");
70
- var result = originalMethod.apply(instance, params);
71
+ var result = originalMethod.apply(caller, params);
71
72
  if ((0, thenable_1.thenable)(result)) {
72
73
  // For handling pending commands on disconnect, add to the set and remove once finished.
73
74
  // On sync commands there's no need, only thenables.
@@ -86,10 +87,10 @@ var RedisAdapter = /** @class */ (function (_super) {
86
87
  return result;
87
88
  }
88
89
  if (instance._notReadyCommandsQueue) {
89
- return new Promise(function (res, rej) {
90
+ return new Promise(function (resolve, reject) {
90
91
  instance._notReadyCommandsQueue.unshift({
91
- resolve: res,
92
- reject: rej,
92
+ resolve: resolve,
93
+ reject: reject,
93
94
  command: commandWrapper,
94
95
  name: methodName.toUpperCase()
95
96
  });
@@ -99,6 +100,22 @@ var RedisAdapter = /** @class */ (function (_super) {
99
100
  return commandWrapper();
100
101
  }
101
102
  };
103
+ };
104
+ // Wrap regular async methods to track timeouts and queue when Redis is not yet executing commands.
105
+ METHODS_TO_PROMISE_WRAP.forEach(function (methodName) {
106
+ var originalFn = instance[methodName];
107
+ instance[methodName] = wrapCommand(originalFn, methodName);
108
+ });
109
+ // Special handling for pipeline~like methods. We need to wrap the async trigger, which is exec, but return the Pipeline right away.
110
+ METHODS_TO_PROMISE_WRAP_EXEC.forEach(function (methodName) {
111
+ var originalFn = instance[methodName];
112
+ // "First level wrapper" to handle the sync execution and wrap async, queueing later if applicable.
113
+ instance[methodName] = function () {
114
+ var res = originalFn.apply(instance, arguments);
115
+ var originalExec = res.exec;
116
+ res.exec = wrapCommand(originalExec, methodName + '.exec').bind(res);
117
+ return res;
118
+ };
102
119
  });
103
120
  };
104
121
  RedisAdapter.prototype._setDisconnectWrapper = function () {
@@ -160,7 +177,7 @@ var RedisAdapter = /** @class */ (function (_super) {
160
177
  * Parses the options into what we care about.
161
178
  */
162
179
  RedisAdapter._defineOptions = function (_a) {
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;
180
+ var connectionTimeout = _a.connectionTimeout, operationTimeout = _a.operationTimeout, url = _a.url, host = _a.host, port = _a.port, db = _a.db, pass = _a.pass, tls = _a.tls;
164
181
  var parsedOptions = {
165
182
  connectionTimeout: connectionTimeout,
166
183
  operationTimeout: operationTimeout,
@@ -54,9 +54,9 @@ var SegmentsCacheInRedis = /** @class */ (function () {
54
54
  SegmentsCacheInRedis.prototype.getRegisteredSegments = function () {
55
55
  return this.redis.smembers(this.keys.buildRegisteredSegmentsKey());
56
56
  };
57
- // @TODO remove/review. It is not being used.
57
+ // @TODO remove or implement. It is not being used.
58
58
  SegmentsCacheInRedis.prototype.clear = function () {
59
- return this.redis.flushdb().then(function (status) { return status === 'OK'; });
59
+ return Promise.resolve();
60
60
  };
61
61
  return SegmentsCacheInRedis;
62
62
  }());
@@ -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.pipelineExec(listOfKeys.map(function (k) { return ['get', k]; })); })
174
+ .then(function (listOfKeys) { return _this.redis.pipeline(listOfKeys.map(function (k) { return ['get', k]; })).exec(); })
175
175
  .then(processPipelineAnswer)
176
176
  .then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) {
177
177
  return JSON.parse(splitDefinition);
@@ -189,11 +189,11 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
189
189
  /**
190
190
  * Get list of feature flag names related to a given list of flag set names.
191
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.
192
+ * or rejected if the pipelined redis operation fails (e.g., timeout).
193
193
  */
194
194
  SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
195
195
  var _this = this;
196
- return this.redis.pipelineExec(flagSets.map(function (flagSet) { return ['smembers', _this.keys.buildFlagSetKey(flagSet)]; }))
196
+ return this.redis.pipeline(flagSets.map(function (flagSet) { return ['smembers', _this.keys.buildFlagSetKey(flagSet)]; })).exec()
197
197
  .then(function (results) { return results.map(function (_a, index) {
198
198
  var e = _a[0], value = _a[1];
199
199
  if (e === null)
@@ -228,13 +228,9 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
228
228
  return true;
229
229
  });
230
230
  };
231
- /**
232
- * Delete everything in the current database.
233
- *
234
- * @NOTE documentation says it never fails.
235
- */
231
+ // @TODO remove or implement. It is not being used.
236
232
  SplitsCacheInRedis.prototype.clear = function () {
237
- return this.redis.flushdb().then(function (status) { return status === 'OK'; });
233
+ return Promise.resolve();
238
234
  };
239
235
  /**
240
236
  * Fetches multiple splits definitions.
@@ -9,16 +9,22 @@ function asyncFunction(data) {
9
9
  return Promise.resolve(data);
10
10
  }
11
11
  var IDENTITY_METHODS = [];
12
- var ASYNC_METHODS = ['rpush', 'hincrby', 'pipelineExec'];
12
+ var ASYNC_METHODS = ['rpush', 'hincrby'];
13
+ var PIPELINE_METHODS = ['rpush', 'hincrby'];
13
14
  var RedisMock = /** @class */ (function () {
14
15
  function RedisMock() {
15
16
  var _this = this;
17
+ this.pipelineMethods = { exec: jest.fn(asyncFunction) };
16
18
  IDENTITY_METHODS.forEach(function (method) {
17
19
  _this[method] = jest.fn(identityFunction);
18
20
  });
19
21
  ASYNC_METHODS.forEach(function (method) {
20
22
  _this[method] = jest.fn(asyncFunction);
21
23
  });
24
+ PIPELINE_METHODS.forEach(function (method) {
25
+ _this.pipelineMethods[method] = _this[method];
26
+ });
27
+ this.pipeline = jest.fn(function () { return _this.pipelineMethods; });
22
28
  }
23
29
  return RedisMock;
24
30
  }());
@@ -63,7 +63,7 @@ export function sdkFactory(params) {
63
63
  ctx.signalListener = signalListener;
64
64
  // SDK client and manager
65
65
  var clientMethod = sdkClientMethodFactory(ctx);
66
- var managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
66
+ var managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
67
67
  syncManager && syncManager.start();
68
68
  signalListener && signalListener.start();
69
69
  log.info(NEW_FACTORY);
@@ -2,6 +2,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { thenable } from '../utils/promise/thenable';
3
3
  import { find } from '../utils/lang';
4
4
  import { validateSplit, validateSplitExistence, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation';
5
+ import { isStorageSync } from '../trackers/impressionObserver/utils';
5
6
  var SPLIT_FN_LABEL = 'split';
6
7
  var SPLITS_FN_LABEL = 'splits';
7
8
  var NAMES_FN_LABEL = 'names';
@@ -38,8 +39,10 @@ function objectsToViews(splitObjects) {
38
39
  });
39
40
  return views;
40
41
  }
41
- export function sdkManagerFactory(log, splits, _a) {
42
+ export function sdkManagerFactory(settings, splits, _a) {
42
43
  var readinessManager = _a.readinessManager, sdkStatus = _a.sdkStatus;
44
+ var log = settings.log;
45
+ var isSync = isStorageSync(settings);
43
46
  return objectAssign(
44
47
  // Proto-linkage of the readiness Event Emitter
45
48
  Object.create(sdkStatus), {
@@ -49,7 +52,7 @@ export function sdkManagerFactory(log, splits, _a) {
49
52
  split: function (featureFlagName) {
50
53
  var splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
51
54
  if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
52
- return null;
55
+ return isSync ? null : Promise.resolve(null);
53
56
  }
54
57
  var split = splits.getSplit(splitName);
55
58
  if (thenable(split)) {
@@ -66,7 +69,7 @@ export function sdkManagerFactory(log, splits, _a) {
66
69
  */
67
70
  splits: function () {
68
71
  if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
69
- return [];
72
+ return isSync ? [] : Promise.resolve([]);
70
73
  }
71
74
  var currentSplits = splits.getAll();
72
75
  return thenable(currentSplits) ?
@@ -78,7 +81,7 @@ export function sdkManagerFactory(log, splits, _a) {
78
81
  */
79
82
  names: function () {
80
83
  if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
81
- return [];
84
+ return isSync ? [] : Promise.resolve([]);
82
85
  }
83
86
  var splitNames = splits.getSplitNames();
84
87
  return thenable(splitNames) ?
@@ -20,7 +20,11 @@ var ImpressionCountsCacheInRedis = /** @class */ (function (_super) {
20
20
  var keys = Object.keys(counts);
21
21
  if (!keys.length)
22
22
  return Promise.resolve(false);
23
- return this.redis.pipelineExec(keys.map(function (key) { return ['hincrby', _this.key, key, counts[key]]; }))
23
+ var pipeline = this.redis.pipeline();
24
+ keys.forEach(function (key) {
25
+ pipeline.hincrby(_this.key, key, counts[key]);
26
+ });
27
+ return pipeline.exec()
24
28
  .then(function (data) {
25
29
  // If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
26
30
  if (data.length && data.length === keys.length) {
@@ -6,7 +6,8 @@ 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', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw', 'flushdb', 'pipelineExec'];
9
+ var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw'];
10
+ var METHODS_TO_PROMISE_WRAP_EXEC = ['pipeline'];
10
11
  // Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
11
12
  var DEFAULT_OPTIONS = {
12
13
  connectionTimeout: 10000,
@@ -24,6 +25,7 @@ var DEFAULT_LIBRARY_OPTIONS = {
24
25
  var RedisAdapter = /** @class */ (function (_super) {
25
26
  __extends(RedisAdapter, _super);
26
27
  function RedisAdapter(log, storageSettings) {
28
+ if (storageSettings === void 0) { storageSettings = {}; }
27
29
  var _this = this;
28
30
  var options = RedisAdapter._defineOptions(storageSettings);
29
31
  // Call the ioredis constructor
@@ -37,9 +39,6 @@ var RedisAdapter = /** @class */ (function (_super) {
37
39
  _this._setDisconnectWrapper();
38
40
  return _this;
39
41
  }
40
- RedisAdapter.prototype.pipelineExec = function (commands) {
41
- return this.pipeline(commands).exec();
42
- };
43
42
  RedisAdapter.prototype._listenToEvents = function () {
44
43
  var _this = this;
45
44
  this.once('ready', function () {
@@ -58,13 +57,15 @@ var RedisAdapter = /** @class */ (function (_super) {
58
57
  };
59
58
  RedisAdapter.prototype._setTimeoutWrappers = function () {
60
59
  var instance = this;
61
- METHODS_TO_PROMISE_WRAP.forEach(function (methodName) {
62
- var originalMethod = instance[methodName];
63
- instance[methodName] = function () {
60
+ var wrapCommand = function (originalMethod, methodName) {
61
+ // The value of "this" in this function should be the instance actually executing the method. It might be the instance referred (the base one)
62
+ // or it can be the instance of a Pipeline object.
63
+ return function () {
64
64
  var params = arguments;
65
+ var caller = this;
65
66
  function commandWrapper() {
66
67
  instance.log.debug(LOG_PREFIX + "Executing " + methodName + ".");
67
- var result = originalMethod.apply(instance, params);
68
+ var result = originalMethod.apply(caller, params);
68
69
  if (thenable(result)) {
69
70
  // For handling pending commands on disconnect, add to the set and remove once finished.
70
71
  // On sync commands there's no need, only thenables.
@@ -83,10 +84,10 @@ var RedisAdapter = /** @class */ (function (_super) {
83
84
  return result;
84
85
  }
85
86
  if (instance._notReadyCommandsQueue) {
86
- return new Promise(function (res, rej) {
87
+ return new Promise(function (resolve, reject) {
87
88
  instance._notReadyCommandsQueue.unshift({
88
- resolve: res,
89
- reject: rej,
89
+ resolve: resolve,
90
+ reject: reject,
90
91
  command: commandWrapper,
91
92
  name: methodName.toUpperCase()
92
93
  });
@@ -96,6 +97,22 @@ var RedisAdapter = /** @class */ (function (_super) {
96
97
  return commandWrapper();
97
98
  }
98
99
  };
100
+ };
101
+ // Wrap regular async methods to track timeouts and queue when Redis is not yet executing commands.
102
+ METHODS_TO_PROMISE_WRAP.forEach(function (methodName) {
103
+ var originalFn = instance[methodName];
104
+ instance[methodName] = wrapCommand(originalFn, methodName);
105
+ });
106
+ // Special handling for pipeline~like methods. We need to wrap the async trigger, which is exec, but return the Pipeline right away.
107
+ METHODS_TO_PROMISE_WRAP_EXEC.forEach(function (methodName) {
108
+ var originalFn = instance[methodName];
109
+ // "First level wrapper" to handle the sync execution and wrap async, queueing later if applicable.
110
+ instance[methodName] = function () {
111
+ var res = originalFn.apply(instance, arguments);
112
+ var originalExec = res.exec;
113
+ res.exec = wrapCommand(originalExec, methodName + '.exec').bind(res);
114
+ return res;
115
+ };
99
116
  });
100
117
  };
101
118
  RedisAdapter.prototype._setDisconnectWrapper = function () {
@@ -157,7 +174,7 @@ var RedisAdapter = /** @class */ (function (_super) {
157
174
  * Parses the options into what we care about.
158
175
  */
159
176
  RedisAdapter._defineOptions = function (_a) {
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;
177
+ var connectionTimeout = _a.connectionTimeout, operationTimeout = _a.operationTimeout, url = _a.url, host = _a.host, port = _a.port, db = _a.db, pass = _a.pass, tls = _a.tls;
161
178
  var parsedOptions = {
162
179
  connectionTimeout: connectionTimeout,
163
180
  operationTimeout: operationTimeout,
@@ -51,9 +51,9 @@ var SegmentsCacheInRedis = /** @class */ (function () {
51
51
  SegmentsCacheInRedis.prototype.getRegisteredSegments = function () {
52
52
  return this.redis.smembers(this.keys.buildRegisteredSegmentsKey());
53
53
  };
54
- // @TODO remove/review. It is not being used.
54
+ // @TODO remove or implement. It is not being used.
55
55
  SegmentsCacheInRedis.prototype.clear = function () {
56
- return this.redis.flushdb().then(function (status) { return status === 'OK'; });
56
+ return Promise.resolve();
57
57
  };
58
58
  return SegmentsCacheInRedis;
59
59
  }());
@@ -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.pipelineExec(listOfKeys.map(function (k) { return ['get', k]; })); })
171
+ .then(function (listOfKeys) { return _this.redis.pipeline(listOfKeys.map(function (k) { return ['get', k]; })).exec(); })
172
172
  .then(processPipelineAnswer)
173
173
  .then(function (splitDefinitions) { return splitDefinitions.map(function (splitDefinition) {
174
174
  return JSON.parse(splitDefinition);
@@ -186,11 +186,11 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
186
186
  /**
187
187
  * Get list of feature flag names related to a given list of flag set names.
188
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.
189
+ * or rejected if the pipelined redis operation fails (e.g., timeout).
190
190
  */
191
191
  SplitsCacheInRedis.prototype.getNamesByFlagSets = function (flagSets) {
192
192
  var _this = this;
193
- return this.redis.pipelineExec(flagSets.map(function (flagSet) { return ['smembers', _this.keys.buildFlagSetKey(flagSet)]; }))
193
+ return this.redis.pipeline(flagSets.map(function (flagSet) { return ['smembers', _this.keys.buildFlagSetKey(flagSet)]; })).exec()
194
194
  .then(function (results) { return results.map(function (_a, index) {
195
195
  var e = _a[0], value = _a[1];
196
196
  if (e === null)
@@ -225,13 +225,9 @@ var SplitsCacheInRedis = /** @class */ (function (_super) {
225
225
  return true;
226
226
  });
227
227
  };
228
- /**
229
- * Delete everything in the current database.
230
- *
231
- * @NOTE documentation says it never fails.
232
- */
228
+ // @TODO remove or implement. It is not being used.
233
229
  SplitsCacheInRedis.prototype.clear = function () {
234
- return this.redis.flushdb().then(function (status) { return status === 'OK'; });
230
+ return Promise.resolve();
235
231
  };
236
232
  /**
237
233
  * Fetches multiple splits definitions.
@@ -6,16 +6,22 @@ function asyncFunction(data) {
6
6
  return Promise.resolve(data);
7
7
  }
8
8
  var IDENTITY_METHODS = [];
9
- var ASYNC_METHODS = ['rpush', 'hincrby', 'pipelineExec'];
9
+ var ASYNC_METHODS = ['rpush', 'hincrby'];
10
+ var PIPELINE_METHODS = ['rpush', 'hincrby'];
10
11
  var RedisMock = /** @class */ (function () {
11
12
  function RedisMock() {
12
13
  var _this = this;
14
+ this.pipelineMethods = { exec: jest.fn(asyncFunction) };
13
15
  IDENTITY_METHODS.forEach(function (method) {
14
16
  _this[method] = jest.fn(identityFunction);
15
17
  });
16
18
  ASYNC_METHODS.forEach(function (method) {
17
19
  _this[method] = jest.fn(asyncFunction);
18
20
  });
21
+ PIPELINE_METHODS.forEach(function (method) {
22
+ _this.pipelineMethods[method] = _this[method];
23
+ });
24
+ this.pipeline = jest.fn(function () { return _this.pipelineMethods; });
19
25
  }
20
26
  return RedisMock;
21
27
  }());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.12.1-rc.2",
3
+ "version": "1.12.1-rc.3",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -83,7 +83,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
83
83
 
84
84
  // SDK client and manager
85
85
  const clientMethod = sdkClientMethodFactory(ctx);
86
- const managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
86
+ const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
87
87
 
88
88
  syncManager && syncManager.start();
89
89
  signalListener && signalListener.start();
@@ -1,9 +1,9 @@
1
1
  import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/types';
2
2
  import { ISignalListener } from '../listeners/types';
3
- import { ILogger } from '../logger/types';
4
3
  import { IReadinessManager, ISdkReadinessManager } from '../readiness/types';
4
+ import type { sdkManagerFactory } from '../sdkManager';
5
5
  import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
6
- import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
6
+ import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/types';
7
7
  import { ISyncManager } from '../sync/types';
8
8
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
9
9
  import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
@@ -87,11 +87,7 @@ export interface ISdkFactoryParams {
87
87
  syncManagerFactory?: (params: ISdkFactoryContextSync) => ISyncManager,
88
88
 
89
89
  // Sdk manager factory
90
- sdkManagerFactory: (
91
- log: ILogger,
92
- splits: ISplitsCacheSync | ISplitsCacheAsync,
93
- sdkReadinessManager: ISdkReadinessManager
94
- ) => SplitIO.IManager | SplitIO.IAsyncManager,
90
+ sdkManagerFactory: typeof sdkManagerFactory,
95
91
 
96
92
  // Sdk client method factory (ISDK::client method).
97
93
  // It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
@@ -5,8 +5,8 @@ import { validateSplit, validateSplitExistence, validateIfNotDestroyed, validate
5
5
  import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
6
6
  import { ISdkReadinessManager } from '../readiness/types';
7
7
  import { ISplit } from '../dtos/types';
8
- import { SplitIO } from '../types';
9
- import { ILogger } from '../logger/types';
8
+ import { ISettings, SplitIO } from '../types';
9
+ import { isStorageSync } from '../trackers/impressionObserver/utils';
10
10
 
11
11
  const SPLIT_FN_LABEL = 'split';
12
12
  const SPLITS_FN_LABEL = 'splits';
@@ -49,11 +49,14 @@ function objectsToViews(splitObjects: ISplit[]) {
49
49
  }
50
50
 
51
51
  export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplitsCacheAsync>(
52
- log: ILogger,
52
+ settings: Pick<ISettings, 'log' | 'mode'>,
53
53
  splits: TSplitCache,
54
- { readinessManager, sdkStatus }: ISdkReadinessManager
54
+ { readinessManager, sdkStatus }: ISdkReadinessManager,
55
55
  ): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager {
56
56
 
57
+ const log = settings.log;
58
+ const isSync = isStorageSync(settings);
59
+
57
60
  return objectAssign(
58
61
  // Proto-linkage of the readiness Event Emitter
59
62
  Object.create(sdkStatus),
@@ -64,7 +67,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
64
67
  split(featureFlagName: string) {
65
68
  const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
66
69
  if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
67
- return null;
70
+ return isSync ? null : Promise.resolve(null);
68
71
  }
69
72
 
70
73
  const split = splits.getSplit(splitName);
@@ -85,7 +88,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
85
88
  */
86
89
  splits() {
87
90
  if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
88
- return [];
91
+ return isSync ? [] : Promise.resolve([]);
89
92
  }
90
93
  const currentSplits = splits.getAll();
91
94
 
@@ -98,7 +101,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
98
101
  */
99
102
  names() {
100
103
  if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
101
- return [];
104
+ return isSync ? [] : Promise.resolve([]);
102
105
  }
103
106
  const splitNames = splits.getSplitNames();
104
107
 
@@ -27,7 +27,11 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
27
27
  const keys = Object.keys(counts);
28
28
  if (!keys.length) return Promise.resolve(false);
29
29
 
30
- return this.redis.pipelineExec(keys.map(key => ['hincrby', this.key, key, counts[key]]))
30
+ const pipeline = this.redis.pipeline();
31
+ keys.forEach(key => {
32
+ pipeline.hincrby(this.key, key, counts[key]);
33
+ });
34
+ return pipeline.exec()
31
35
  .then(data => {
32
36
  // If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
33
37
  if (data.length && data.length === keys.length) {
@@ -1,4 +1,4 @@
1
- import ioredis from 'ioredis';
1
+ import ioredis, { Pipeline } from 'ioredis';
2
2
  import { ILogger } from '../../logger/types';
3
3
  import { merge, isString } from '../../utils/lang';
4
4
  import { _Set, setToArray, ISet } from '../../utils/lang/sets';
@@ -8,7 +8,8 @@ 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', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw', 'flushdb', 'pipelineExec'];
11
+ const METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'expire', 'mget', 'lrange', 'ltrim', 'hset', 'hincrby', 'popNRaw'];
12
+ const METHODS_TO_PROMISE_WRAP_EXEC = ['pipeline'];
12
13
 
13
14
  // Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
14
15
  const DEFAULT_OPTIONS = {
@@ -38,7 +39,7 @@ export class RedisAdapter extends ioredis {
38
39
  private _notReadyCommandsQueue?: IRedisCommand[];
39
40
  private _runningCommands: ISet<Promise<any>>;
40
41
 
41
- constructor(log: ILogger, storageSettings?: Record<string, any>) {
42
+ constructor(log: ILogger, storageSettings: Record<string, any> = {}) {
42
43
  const options = RedisAdapter._defineOptions(storageSettings);
43
44
  // Call the ioredis constructor
44
45
  super(...RedisAdapter._defineLibrarySettings(options));
@@ -52,10 +53,6 @@ export class RedisAdapter extends ioredis {
52
53
  this._setDisconnectWrapper();
53
54
  }
54
55
 
55
- pipelineExec(commands?: (string | number)[][]): Promise<Array<[Error | null, any]>> {
56
- return this.pipeline(commands as string[][]).exec();
57
- }
58
-
59
56
  _listenToEvents() {
60
57
  this.once('ready', () => {
61
58
  const commandsCount = this._notReadyCommandsQueue ? this._notReadyCommandsQueue.length : 0;
@@ -76,21 +73,22 @@ export class RedisAdapter extends ioredis {
76
73
  _setTimeoutWrappers() {
77
74
  const instance: Record<string, any> = this;
78
75
 
79
- METHODS_TO_PROMISE_WRAP.forEach(methodName => {
80
- const originalMethod = instance[methodName];
81
-
82
- instance[methodName] = function () {
76
+ const wrapCommand = (originalMethod: Function, methodName: string) => {
77
+ // The value of "this" in this function should be the instance actually executing the method. It might be the instance referred (the base one)
78
+ // or it can be the instance of a Pipeline object.
79
+ return function (this: RedisAdapter | Pipeline) {
83
80
  const params = arguments;
81
+ const caller = this;
84
82
 
85
83
  function commandWrapper() {
86
84
  instance.log.debug(`${LOG_PREFIX}Executing ${methodName}.`);
87
- const result = originalMethod.apply(instance, params);
85
+ const result = originalMethod.apply(caller, params);
88
86
 
89
87
  if (thenable(result)) {
90
88
  // For handling pending commands on disconnect, add to the set and remove once finished.
91
89
  // On sync commands there's no need, only thenables.
92
90
  instance._runningCommands.add(result);
93
- const cleanUpRunningCommandsCb = () => {
91
+ const cleanUpRunningCommandsCb = function () {
94
92
  instance._runningCommands.delete(result);
95
93
  };
96
94
  // Both success and error remove from queue.
@@ -107,10 +105,10 @@ export class RedisAdapter extends ioredis {
107
105
  }
108
106
 
109
107
  if (instance._notReadyCommandsQueue) {
110
- return new Promise((res, rej) => {
108
+ return new Promise((resolve, reject) => {
111
109
  instance._notReadyCommandsQueue.unshift({
112
- resolve: res,
113
- reject: rej,
110
+ resolve,
111
+ reject,
114
112
  command: commandWrapper,
115
113
  name: methodName.toUpperCase()
116
114
  });
@@ -119,6 +117,26 @@ export class RedisAdapter extends ioredis {
119
117
  return commandWrapper();
120
118
  }
121
119
  };
120
+ };
121
+
122
+ // Wrap regular async methods to track timeouts and queue when Redis is not yet executing commands.
123
+ METHODS_TO_PROMISE_WRAP.forEach(methodName => {
124
+ const originalFn = instance[methodName];
125
+ instance[methodName] = wrapCommand(originalFn, methodName);
126
+ });
127
+
128
+ // Special handling for pipeline~like methods. We need to wrap the async trigger, which is exec, but return the Pipeline right away.
129
+ METHODS_TO_PROMISE_WRAP_EXEC.forEach(methodName => {
130
+ const originalFn = instance[methodName];
131
+ // "First level wrapper" to handle the sync execution and wrap async, queueing later if applicable.
132
+ instance[methodName] = function () {
133
+ const res = originalFn.apply(instance, arguments);
134
+ const originalExec = res.exec;
135
+
136
+ res.exec = wrapCommand(originalExec, methodName + '.exec').bind(res);
137
+
138
+ return res;
139
+ };
122
140
  });
123
141
  }
124
142
 
@@ -181,7 +199,7 @@ export class RedisAdapter extends ioredis {
181
199
  /**
182
200
  * Parses the options into what we care about.
183
201
  */
184
- static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any> = {}) {
202
+ static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any>) {
185
203
  const parsedOptions = {
186
204
  connectionTimeout, operationTimeout, url, host, port, db, pass, tls
187
205
  };
@@ -72,8 +72,8 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
72
72
  return this.redis.smembers(this.keys.buildRegisteredSegmentsKey());
73
73
  }
74
74
 
75
- // @TODO remove/review. It is not being used.
75
+ // @TODO remove or implement. It is not being used.
76
76
  clear() {
77
- return this.redis.flushdb().then(status => status === 'OK');
77
+ return Promise.resolve();
78
78
  }
79
79
  }
@@ -192,7 +192,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
192
192
  */
193
193
  getAll(): Promise<ISplit[]> {
194
194
  return this.redis.keys(this.keys.searchPatternForSplitKeys())
195
- .then((listOfKeys) => this.redis.pipelineExec(listOfKeys.map(k => ['get', k])))
195
+ .then((listOfKeys) => this.redis.pipeline(listOfKeys.map(k => ['get', k])).exec())
196
196
  .then(processPipelineAnswer)
197
197
  .then((splitDefinitions) => splitDefinitions.map((splitDefinition) => {
198
198
  return JSON.parse(splitDefinition);
@@ -213,10 +213,10 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
213
213
  /**
214
214
  * Get list of feature flag names related to a given list of flag set names.
215
215
  * The returned promise is resolved with the list of feature flag names per flag set,
216
- * or rejected if the pipelined redis operation fails.
216
+ * or rejected if the pipelined redis operation fails (e.g., timeout).
217
217
  */
218
218
  getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]> {
219
- return this.redis.pipelineExec(flagSets.map(flagSet => ['smembers', this.keys.buildFlagSetKey(flagSet)]))
219
+ return this.redis.pipeline(flagSets.map(flagSet => ['smembers', this.keys.buildFlagSetKey(flagSet)])).exec()
220
220
  .then((results) => results.map(([e, value], index) => {
221
221
  if (e === null) return value;
222
222
 
@@ -252,13 +252,9 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
252
252
  });
253
253
  }
254
254
 
255
- /**
256
- * Delete everything in the current database.
257
- *
258
- * @NOTE documentation says it never fails.
259
- */
255
+ // @TODO remove or implement. It is not being used.
260
256
  clear() {
261
- return this.redis.flushdb().then(status => status === 'OK');
257
+ return Promise.resolve();
262
258
  }
263
259
 
264
260
  /**
@@ -4,6 +4,6 @@ import { ISettings } from '../../types';
4
4
  /**
5
5
  * Storage is async if mode is consumer or partial consumer
6
6
  */
7
- export function isStorageSync(settings: ISettings) {
7
+ export function isStorageSync(settings: Pick<ISettings, 'mode'>) {
8
8
  return [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false;
9
9
  }
@@ -8,10 +8,13 @@ function asyncFunction(data: any): Promise<any> {
8
8
  }
9
9
 
10
10
  const IDENTITY_METHODS: string[] = [];
11
- const ASYNC_METHODS = ['rpush', 'hincrby', 'pipelineExec'];
11
+ const ASYNC_METHODS = ['rpush', 'hincrby'];
12
+ const PIPELINE_METHODS = ['rpush', 'hincrby'];
12
13
 
13
14
  export class RedisMock {
14
15
 
16
+ private pipelineMethods: any = { exec: jest.fn(asyncFunction) };
17
+
15
18
  constructor() {
16
19
  IDENTITY_METHODS.forEach(method => {
17
20
  this[method] = jest.fn(identityFunction);
@@ -19,5 +22,10 @@ export class RedisMock {
19
22
  ASYNC_METHODS.forEach(method => {
20
23
  this[method] = jest.fn(asyncFunction);
21
24
  });
25
+ PIPELINE_METHODS.forEach(method => {
26
+ this.pipelineMethods[method] = this[method];
27
+ });
28
+
29
+ this.pipeline = jest.fn(() => { return this.pipelineMethods; });
22
30
  }
23
31
  }
@@ -1,9 +1,9 @@
1
1
  import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/types';
2
2
  import { ISignalListener } from '../listeners/types';
3
- import { ILogger } from '../logger/types';
4
3
  import { IReadinessManager, ISdkReadinessManager } from '../readiness/types';
4
+ import type { sdkManagerFactory } from '../sdkManager';
5
5
  import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
6
- import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
6
+ import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/types';
7
7
  import { ISyncManager } from '../sync/types';
8
8
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
9
9
  import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
@@ -66,7 +66,7 @@ export interface ISdkFactoryParams {
66
66
  storageFactory: (params: IStorageFactoryParams) => IStorageSync | IStorageAsync;
67
67
  splitApiFactory?: (settings: ISettings, platform: IPlatform, telemetryTracker: ITelemetryTracker) => ISplitApi;
68
68
  syncManagerFactory?: (params: ISdkFactoryContextSync) => ISyncManager;
69
- sdkManagerFactory: (log: ILogger, splits: ISplitsCacheSync | ISplitsCacheAsync, sdkReadinessManager: ISdkReadinessManager) => SplitIO.IManager | SplitIO.IAsyncManager;
69
+ sdkManagerFactory: typeof sdkManagerFactory;
70
70
  sdkClientMethodFactory: (params: ISdkFactoryContext) => ({
71
71
  (): SplitIO.ICsClient;
72
72
  (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient;
@@ -1,5 +1,4 @@
1
1
  import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
2
2
  import { ISdkReadinessManager } from '../readiness/types';
3
- import { SplitIO } from '../types';
4
- import { ILogger } from '../logger/types';
5
- export declare function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplitsCacheAsync>(log: ILogger, splits: TSplitCache, { readinessManager, sdkStatus }: ISdkReadinessManager): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager;
3
+ import { ISettings, SplitIO } from '../types';
4
+ export declare function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplitsCacheAsync>(settings: Pick<ISettings, 'log' | 'mode'>, splits: TSplitCache, { readinessManager, sdkStatus }: ISdkReadinessManager): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager;
@@ -9,7 +9,6 @@ export declare class RedisAdapter extends ioredis {
9
9
  private _notReadyCommandsQueue?;
10
10
  private _runningCommands;
11
11
  constructor(log: ILogger, storageSettings?: Record<string, any>);
12
- pipelineExec(commands?: (string | number)[][]): Promise<Array<[Error | null, any]>>;
13
12
  _listenToEvents(): void;
14
13
  _setTimeoutWrappers(): void;
15
14
  _setDisconnectWrapper(): void;
@@ -21,5 +20,5 @@ export declare class RedisAdapter extends ioredis {
21
20
  /**
22
21
  * Parses the options into what we care about.
23
22
  */
24
- static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }?: Record<string, any>): object;
23
+ static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any>): object;
25
24
  }
@@ -14,5 +14,5 @@ export declare class SegmentsCacheInRedis implements ISegmentsCacheAsync {
14
14
  getChangeNumber(name: string): Promise<number>;
15
15
  registerSegments(segments: string[]): Promise<boolean>;
16
16
  getRegisteredSegments(): Promise<string[]>;
17
- clear(): Promise<boolean>;
17
+ clear(): Promise<void>;
18
18
  }
@@ -77,7 +77,7 @@ export declare class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
77
77
  /**
78
78
  * Get list of feature flag names related to a given list of flag set names.
79
79
  * The returned promise is resolved with the list of feature flag names per flag set,
80
- * or rejected if the pipelined redis operation fails.
80
+ * or rejected if the pipelined redis operation fails (e.g., timeout).
81
81
  */
82
82
  getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>;
83
83
  /**
@@ -87,12 +87,7 @@ export declare class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
87
87
  * It will never be rejected.
88
88
  */
89
89
  trafficTypeExists(trafficType: string): Promise<boolean>;
90
- /**
91
- * Delete everything in the current database.
92
- *
93
- * @NOTE documentation says it never fails.
94
- */
95
- clear(): Promise<boolean>;
90
+ clear(): Promise<void>;
96
91
  /**
97
92
  * Fetches multiple splits definitions.
98
93
  * Returned promise is rejected if redis operation fails.
@@ -2,4 +2,4 @@ import { ISettings } from '../../types';
2
2
  /**
3
3
  * Storage is async if mode is consumer or partial consumer
4
4
  */
5
- export declare function isStorageSync(settings: ISettings): boolean;
5
+ export declare function isStorageSync(settings: Pick<ISettings, 'mode'>): boolean;
@@ -1,3 +1,4 @@
1
1
  export declare class RedisMock {
2
+ private pipelineMethods;
2
3
  constructor();
3
4
  }