@splitsoftware/splitio-commons 3.1.1-rc.12 → 3.1.1-rc.13

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 (50) hide show
  1. package/CHANGES.txt +2 -0
  2. package/cjs/storages/AbstractDefinitionsCacheAsync.js +2 -1
  3. package/cjs/storages/AbstractDefinitionsCacheSync.js +2 -2
  4. package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
  5. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
  6. package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
  7. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
  8. package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
  9. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
  10. package/cjs/storages/pluggable/DefinitionsCachePluggable.js +9 -7
  11. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
  12. package/cjs/sync/offline/splitsParser/definitionChangesBuilder.js +37 -0
  13. package/cjs/sync/offline/splitsParser/splitsParserFromSettings.js +5 -32
  14. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +26 -46
  15. package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -11
  16. package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +1 -1
  17. package/esm/storages/AbstractDefinitionsCacheAsync.js +2 -1
  18. package/esm/storages/AbstractDefinitionsCacheSync.js +2 -2
  19. package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
  20. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
  21. package/esm/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
  22. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
  23. package/esm/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
  24. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
  25. package/esm/storages/pluggable/DefinitionsCachePluggable.js +9 -7
  26. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
  27. package/esm/sync/offline/splitsParser/definitionChangesBuilder.js +33 -0
  28. package/esm/sync/offline/splitsParser/splitsParserFromSettings.js +6 -33
  29. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +23 -43
  30. package/esm/sync/polling/updaters/definitionChangesUpdater.js +8 -11
  31. package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +2 -2
  32. package/package.json +1 -1
  33. package/src/storages/AbstractDefinitionsCacheAsync.ts +3 -2
  34. package/src/storages/AbstractDefinitionsCacheSync.ts +3 -3
  35. package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +3 -1
  36. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +5 -3
  37. package/src/storages/inMemory/DefinitionsCacheInMemory.ts +1 -0
  38. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -3
  39. package/src/storages/inRedis/DefinitionsCacheInRedis.ts +9 -7
  40. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +20 -11
  41. package/src/storages/pluggable/DefinitionsCachePluggable.ts +10 -7
  42. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +20 -11
  43. package/src/storages/types.ts +6 -6
  44. package/src/sync/offline/splitsParser/definitionChangesBuilder.ts +49 -0
  45. package/src/sync/offline/splitsParser/splitsParserFromSettings.ts +8 -38
  46. package/src/sync/offline/splitsParser/types.ts +7 -3
  47. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +25 -45
  48. package/src/sync/polling/updaters/definitionChangesUpdater.ts +9 -13
  49. package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +2 -2
  50. package/src/sync/syncManagerOnline.ts +0 -1
package/CHANGES.txt CHANGED
@@ -4,6 +4,8 @@
4
4
  - Added support for ioredis v6 (Related to issue https://github.com/splitio/javascript-client/issues/951). Note that ioredis v6 requires Node.js 20 or above; stay on ioredis v4 or v5 for older Node.js versions.
5
5
  - Removed the `@types/ioredis` dependency, which is no longer required since the public type definitions don't depend on ioredis types. The `@types/node` dependency, previously installed transitively by `@types/ioredis`, is now declared explicitly, since the public type definitions use Node.js types for the `agent` and `tls` options.
6
6
  - Removed Configs SDK-related types and modules and moved them to the Configs SDK repository.
7
+ - Bugfix - Fixed the SDK_UPDATE event in localhost mode, which is now emitted with the correct list of names of the updated or removed feature flags.
8
+ - Bugfix - Fixed the SDK_UPDATE event emitted on SPLIT_KILL notifications, which now carries the metadata with the name of the killed feature flag (previously, the metadata was `true`). Also, SPLIT_KILL no longer triggers a second SDK_UPDATE event when the subsequent fetch returns the already killed feature flag with the same change number.
7
9
  - Bugfix - Fixed a memory leak in the `Backoff` utility, where promises returned by pending scheduled calls were never settled when the backoff was reset.
8
10
  - Bugfix - Fixed the authentication service to not send unauthenticated requests when the SDK is destroyed while an authentication retry is pending.
9
11
 
@@ -17,7 +17,8 @@ var AbstractDefinitionsCacheAsync = /** @class */ (function () {
17
17
  Promise.all(toRemove.map(function (removedFF) { return _this.remove(removedFF); }))
18
18
  ]).then(function (_a) {
19
19
  var added = _a[1], removed = _a[2];
20
- return added.some(function (result) { return result; }) || removed.some(function (result) { return result; });
20
+ return toRemove.filter(function (_, i) { return removed[i]; })
21
+ .concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (addedFF) { return addedFF.name; }));
21
22
  });
22
23
  };
23
24
  // @TODO revisit segment-related methods ('usesSegments', 'getRegisteredSegments', 'registerSegments')
@@ -12,8 +12,8 @@ var AbstractDefinitionsCacheSync = /** @class */ (function () {
12
12
  }
13
13
  AbstractDefinitionsCacheSync.prototype.update = function (toAdd, toRemove, changeNumber) {
14
14
  var _this = this;
15
- var updated = toAdd.map(function (addedFF) { return _this.add(addedFF); }).some(function (result) { return result; });
16
- updated = toRemove.map(function (removedFF) { return _this.remove(removedFF); }).some(function (result) { return result; }) || updated;
15
+ var updated = toRemove.filter(function (removedFF) { return _this.remove(removedFF); })
16
+ .concat(toAdd.filter(function (addedFF) { return _this.add(addedFF); }).map(function (addedFF) { return addedFF.name; }));
17
17
  this.setChangeNumber(changeNumber);
18
18
  return updated;
19
19
  };
@@ -75,6 +75,8 @@ var DefinitionsCacheInLocal = /** @class */ (function (_super) {
75
75
  var definitionFromStorage = this.storage.getItem(definitionKey);
76
76
  var previousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : null;
77
77
  if (previousDefinition) {
78
+ if (previousDefinition.changeNumber >= definition.changeNumber)
79
+ return false;
78
80
  this._decrementCounts(previousDefinition);
79
81
  this.removeFromSets(previousDefinition.name, previousDefinition.sets);
80
82
  }
@@ -18,8 +18,8 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
18
18
  };
19
19
  RBSegmentsCacheInLocal.prototype.update = function (toAdd, toRemove, changeNumber) {
20
20
  var _this = this;
21
- var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
22
- updated = toRemove.map(function (toRemove) { return _this.remove(toRemove); }).some(function (result) { return result; }) || updated;
21
+ var updated = toRemove.filter(function (name) { return _this.remove(name); })
22
+ .concat(toAdd.filter(function (rbSegment) { return _this.add(rbSegment); }).map(function (rbSegment) { return rbSegment.name; }));
23
23
  this.setChangeNumber(changeNumber);
24
24
  return updated;
25
25
  };
@@ -46,6 +46,8 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
46
46
  var rbSegmentKey = this.keys.buildRBSegmentKey(name);
47
47
  var rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
48
48
  var previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
49
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
50
+ return false;
49
51
  this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
50
52
  var usesSegmentsDiff = 0;
51
53
  if (previous && (0, AbstractDefinitionsCacheSync_1.usesSegments)(previous))
@@ -30,6 +30,8 @@ var DefinitionsCacheInMemory = /** @class */ (function (_super) {
30
30
  var name = definition.name;
31
31
  var previousDefinition = this.get(name);
32
32
  if (previousDefinition) { // We had this Split already
33
+ if (previousDefinition.changeNumber >= definition.changeNumber)
34
+ return false;
33
35
  var previousTtName = previousDefinition.trafficTypeName;
34
36
  this.ttCache[previousTtName]--;
35
37
  if (!this.ttCache[previousTtName])
@@ -16,8 +16,8 @@ var RBSegmentsCacheInMemory = /** @class */ (function () {
16
16
  };
17
17
  RBSegmentsCacheInMemory.prototype.update = function (toAdd, toRemove, changeNumber) {
18
18
  var _this = this;
19
- var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
20
- updated = toRemove.map(function (toRemove) { return _this.remove(toRemove); }).some(function (result) { return result; }) || updated;
19
+ var updated = toRemove.filter(function (name) { return _this.remove(name); })
20
+ .concat(toAdd.filter(function (rbSegment) { return _this.add(rbSegment); }).map(function (rbSegment) { return rbSegment.name; }));
21
21
  if (changeNumber !== undefined)
22
22
  this.changeNumber = changeNumber;
23
23
  return updated;
@@ -25,6 +25,8 @@ var RBSegmentsCacheInMemory = /** @class */ (function () {
25
25
  RBSegmentsCacheInMemory.prototype.add = function (rbSegment) {
26
26
  var name = rbSegment.name;
27
27
  var previous = this.get(name);
28
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
29
+ return false;
28
30
  if (previous && (0, AbstractDefinitionsCacheSync_1.usesSegments)(previous))
29
31
  this.segmentsCount--;
30
32
  this.cache[name] = rbSegment;
@@ -76,6 +76,8 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
76
76
  var parsedPreviousDefinition, stringifiedNewDefinition;
77
77
  try {
78
78
  parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
79
+ if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber)
80
+ return false;
79
81
  stringifiedNewDefinition = JSON.stringify(definition);
80
82
  }
81
83
  catch (e) {
@@ -90,8 +92,8 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
90
92
  if (parsedPreviousDefinition)
91
93
  return _this._decrementCounts(parsedPreviousDefinition);
92
94
  });
93
- }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); });
94
- }).then(function () { return true; });
95
+ }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); }).then(function () { return true; });
96
+ });
95
97
  };
96
98
  /**
97
99
  * Remove a given definition.
@@ -101,11 +103,11 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
101
103
  DefinitionsCacheInRedis.prototype.remove = function (name) {
102
104
  var _this = this;
103
105
  return this.get(name).then(function (definition) {
104
- if (definition) {
105
- return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); });
106
- }
107
- }).then(function () {
108
- return _this.redis.del(_this.keys.buildDefinitionKey(name)).then(function (status) { return status === 1; });
106
+ if (!definition)
107
+ return false;
108
+ return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); }).then(function () {
109
+ return _this.redis.del(_this.keys.buildDefinitionKey(name)).then(function (status) { return status === 1; });
110
+ });
109
111
  });
110
112
  };
111
113
  /**
@@ -28,20 +28,28 @@ var RBSegmentsCacheInRedis = /** @class */ (function () {
28
28
  var _this = this;
29
29
  return Promise.all([
30
30
  this.setChangeNumber(changeNumber),
31
- Promise.all(toAdd.map(function (toAdd) {
32
- var key = _this.keys.buildRBSegmentKey(toAdd.name);
33
- var stringifiedNewRBSegment = JSON.stringify(toAdd);
34
- return _this.redis.set(key, stringifiedNewRBSegment).then(function () { return true; });
35
- })),
36
- Promise.all(toRemove.map(function (toRemove) {
37
- var key = _this.keys.buildRBSegmentKey(toRemove);
38
- return _this.redis.del(key).then(function (status) { return status === 1; });
39
- }))
31
+ Promise.all(toAdd.map(function (rbSegment) { return _this.add(rbSegment); })),
32
+ Promise.all(toRemove.map(function (name) { return _this.remove(name); }))
40
33
  ]).then(function (_a) {
41
34
  var added = _a[1], removed = _a[2];
42
- return added.some(function (result) { return result; }) || removed.some(function (result) { return result; });
35
+ return toRemove.filter(function (_, i) { return removed[i]; })
36
+ .concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (rbSegment) { return rbSegment.name; }));
43
37
  });
44
38
  };
39
+ RBSegmentsCacheInRedis.prototype.add = function (rbSegment) {
40
+ var _this = this;
41
+ var key = this.keys.buildRBSegmentKey(rbSegment.name);
42
+ return this.get(rbSegment.name).then(function (previous) {
43
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
44
+ return false;
45
+ var stringifiedNewRBSegment = JSON.stringify(rbSegment);
46
+ return _this.redis.set(key, stringifiedNewRBSegment).then(function () { return true; });
47
+ });
48
+ };
49
+ RBSegmentsCacheInRedis.prototype.remove = function (name) {
50
+ var key = this.keys.buildRBSegmentKey(name);
51
+ return this.redis.del(key).then(function (status) { return status === 1; });
52
+ };
45
53
  RBSegmentsCacheInRedis.prototype.setChangeNumber = function (changeNumber) {
46
54
  if (changeNumber !== undefined) {
47
55
  return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
@@ -63,6 +63,8 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
63
63
  var parsedPreviousDefinition, stringifiedNewDefinition;
64
64
  try {
65
65
  parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
66
+ if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber)
67
+ return false;
66
68
  stringifiedNewDefinition = JSON.stringify(definition);
67
69
  }
68
70
  catch (e) {
@@ -77,8 +79,8 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
77
79
  if (parsedPreviousDefinition)
78
80
  return _this._decrementCounts(parsedPreviousDefinition);
79
81
  });
80
- }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); });
81
- }).then(function () { return true; });
82
+ }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); }).then(function () { return true; });
83
+ });
82
84
  };
83
85
  /**
84
86
  * Remove a given definition.
@@ -88,11 +90,11 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
88
90
  DefinitionsCachePluggable.prototype.remove = function (name) {
89
91
  var _this = this;
90
92
  return this.get(name).then(function (definition) {
91
- if (definition) {
92
- return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); });
93
- }
94
- }).then(function () {
95
- return _this.wrapper.del(_this.keys.buildDefinitionKey(name));
93
+ if (!definition)
94
+ return false;
95
+ return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); }).then(function () {
96
+ return _this.wrapper.del(_this.keys.buildDefinitionKey(name));
97
+ });
96
98
  });
97
99
  };
98
100
  /**
@@ -28,20 +28,28 @@ var RBSegmentsCachePluggable = /** @class */ (function () {
28
28
  var _this = this;
29
29
  return Promise.all([
30
30
  this.setChangeNumber(changeNumber),
31
- Promise.all(toAdd.map(function (toAdd) {
32
- var key = _this.keys.buildRBSegmentKey(toAdd.name);
33
- var stringifiedNewRBSegment = JSON.stringify(toAdd);
34
- return _this.wrapper.set(key, stringifiedNewRBSegment).then(function () { return true; });
35
- })),
36
- Promise.all(toRemove.map(function (toRemove) {
37
- var key = _this.keys.buildRBSegmentKey(toRemove);
38
- return _this.wrapper.del(key);
39
- }))
31
+ Promise.all(toAdd.map(function (rbSegment) { return _this.add(rbSegment); })),
32
+ Promise.all(toRemove.map(function (name) { return _this.remove(name); }))
40
33
  ]).then(function (_a) {
41
34
  var added = _a[1], removed = _a[2];
42
- return added.some(function (result) { return result; }) || removed.some(function (result) { return result; });
35
+ return toRemove.filter(function (_, i) { return removed[i]; })
36
+ .concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (rbSegment) { return rbSegment.name; }));
43
37
  });
44
38
  };
39
+ RBSegmentsCachePluggable.prototype.add = function (rbSegment) {
40
+ var _this = this;
41
+ var key = this.keys.buildRBSegmentKey(rbSegment.name);
42
+ return this.get(rbSegment.name).then(function (previous) {
43
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
44
+ return false;
45
+ var stringifiedNewRBSegment = JSON.stringify(rbSegment);
46
+ return _this.wrapper.set(key, stringifiedNewRBSegment).then(function () { return true; });
47
+ });
48
+ };
49
+ RBSegmentsCachePluggable.prototype.remove = function (name) {
50
+ var key = this.keys.buildRBSegmentKey(name);
51
+ return this.wrapper.del(key);
52
+ };
45
53
  RBSegmentsCachePluggable.prototype.setChangeNumber = function (changeNumber) {
46
54
  if (changeNumber !== undefined) {
47
55
  return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.definitionChangesBuilderFactory = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var constants_1 = require("../../../utils/constants");
6
+ /**
7
+ * Factory of a stateful function that, given the current mocked definitions, returns the changes since the previous call
8
+ * as an `IDefinitionChangesResponse`, i.e., the full definitions that were added or modified, and the names of the removed ones.
9
+ * Definitions are compared by value. The response `till` is an incrementing value based on the current time.
10
+ * Definitions have no `changeNumber`, so storages never consider them outdated.
11
+ *
12
+ * The response has no `d` property if there are no changes, except for the first call, which reports the (possibly empty) mock.
13
+ * It is meant to be used by `IDefinitionsParser` implementations, such as `splitsParserFromSettings`.
14
+ */
15
+ function definitionChangesBuilderFactory() {
16
+ var previous; // serialized definitions of the last reported mock, by name
17
+ var lastTill = 0;
18
+ return function definitionChangesBuilder(mock) {
19
+ var current = {};
20
+ var updatedNames = Object.keys(mock).reduce(function (updatedNames, name) {
21
+ current[name] = JSON.stringify(mock[name]);
22
+ if (!previous || previous[name] !== current[name])
23
+ updatedNames.push(name);
24
+ return updatedNames;
25
+ }, []);
26
+ var removed = previous ? Object.keys(previous).filter(function (name) { return current[name] === undefined; }) : [];
27
+ // Only the first call reports an empty change
28
+ if (previous && updatedNames.length === 0 && removed.length === 0)
29
+ return {};
30
+ previous = current;
31
+ // Incrementing `till` based on the current time
32
+ var till = lastTill = Math.max(Date.now(), lastTill + 1);
33
+ var updated = updatedNames.map(function (name) { return (tslib_1.__assign({ name: name, status: 'ACTIVE', killed: false, trafficAllocation: 100, defaultTreatment: constants_1.CONTROL }, mock[name])); });
34
+ return { d: { updated: updated, removed: removed, till: till } };
35
+ };
36
+ }
37
+ exports.definitionChangesBuilderFactory = definitionChangesBuilderFactory;
@@ -3,43 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.splitsParserFromSettingsFactory = void 0;
4
4
  var lang_1 = require("../../../utils/lang");
5
5
  var parseCondition_1 = require("./parseCondition");
6
- function hasTreatmentChanged(prev, curr) {
7
- if (typeof prev !== typeof curr)
8
- return true;
9
- if (typeof prev === 'string') { // strings treatments, just compare
10
- return prev !== curr;
11
- }
12
- else { // has treatment and config, compare both
13
- return prev.treatment !== curr.treatment || prev.config !== curr.config;
14
- }
15
- }
6
+ var definitionChangesBuilder_1 = require("./definitionChangesBuilder");
16
7
  function splitsParserFromSettingsFactory() {
17
- var previousMock = { 'emptyMock': '1' };
18
- function mockUpdated(currentData) {
19
- var names = Object.keys(currentData);
20
- // Different amount of items
21
- if (names.length !== Object.keys(previousMock).length) {
22
- previousMock = (0, lang_1.merge)({}, currentData);
23
- return true;
24
- }
25
- return names.some(function (name) {
26
- var newSplit = !previousMock[name];
27
- var newTreatment = hasTreatmentChanged(previousMock[name], currentData[name]);
28
- var changed = newSplit || newTreatment;
29
- if (changed)
30
- previousMock = (0, lang_1.merge)({}, currentData);
31
- return changed;
32
- });
33
- }
8
+ var definitionChangesBuilder = (0, definitionChangesBuilder_1.definitionChangesBuilderFactory)();
34
9
  /**
35
10
  *
36
11
  * @param settings - validated object with mocked features mapping.
37
12
  */
38
13
  return function splitsParserFromSettings(settings) {
39
14
  var features = settings.features || {};
40
- if (!mockUpdated(features))
41
- return false;
42
- var splitObjects = {};
15
+ var mock = {};
43
16
  (0, lang_1.forOwn)(features, function (data, splitName) {
44
17
  var treatment = data;
45
18
  var config = null;
@@ -50,13 +23,13 @@ function splitsParserFromSettingsFactory() {
50
23
  var configurations = {};
51
24
  if (config !== null)
52
25
  configurations[treatment] = config;
53
- splitObjects[splitName] = {
26
+ mock[splitName] = {
54
27
  trafficTypeName: 'localhost',
55
28
  conditions: [(0, parseCondition_1.parseCondition)({ treatment: treatment })],
56
29
  configurations: configurations
57
30
  };
58
31
  });
59
- return splitObjects;
32
+ return definitionChangesBuilder(mock);
60
33
  };
61
34
  }
62
35
  exports.splitsParserFromSettingsFactory = splitsParserFromSettingsFactory;
@@ -1,11 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fromObjectSyncTaskFactory = exports.fromObjectUpdaterFactory = void 0;
4
- var lang_1 = require("../../../utils/lang");
5
4
  var syncTask_1 = require("../../syncTask");
6
- var constants_1 = require("../../../utils/constants");
7
- var constants_2 = require("../../../readiness/constants");
8
- var constants_3 = require("../../../logger/constants");
5
+ var constants_1 = require("../../../readiness/constants");
6
+ var constants_2 = require("../../../logger/constants");
9
7
  /**
10
8
  * Offline equivalent of `splitChangesUpdaterFactory`
11
9
  */
@@ -13,53 +11,35 @@ function fromObjectUpdaterFactory(splitsParser, storage, readiness, settings) {
13
11
  var log = settings.log, definitions = storage.definitions;
14
12
  var startingUp = true;
15
13
  return function objectUpdater() {
16
- var splits = [];
17
- var loadError = null;
18
- var splitsMock = {};
14
+ var changes;
19
15
  try {
20
- splitsMock = splitsParser(settings);
16
+ changes = splitsParser(settings);
21
17
  }
22
18
  catch (err) {
23
- loadError = err;
24
- log.error(constants_3.ERROR_SYNC_OFFLINE_LOADING, [err]);
19
+ log.error(constants_2.ERROR_SYNC_OFFLINE_LOADING, [err]);
25
20
  }
26
- if (!loadError && splitsMock) {
27
- log.debug(constants_3.SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
28
- (0, lang_1.forOwn)(splitsMock, function (val, name) {
29
- // @ts-ignore Split changeNumber and seed is undefined in localhost mode
30
- splits.push({
31
- name: name,
32
- status: 'ACTIVE',
33
- killed: false,
34
- trafficAllocation: 100,
35
- defaultTreatment: constants_1.CONTROL,
36
- conditions: val.conditions || [],
37
- configurations: val.configurations,
38
- trafficTypeName: val.trafficTypeName
39
- });
40
- });
41
- return Promise.all([
42
- definitions.clear(),
43
- definitions.update(splits, [], Date.now())
44
- ]).then(function () {
45
- readiness.definitions.emit(constants_2.SDK_DEFINITIONS_ARRIVED, { type: constants_2.FLAGS_UPDATE, names: [] });
46
- if (startingUp) {
47
- startingUp = false;
48
- Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
49
- // Emits SDK_READY_FROM_CACHE
50
- if (!cacheMetadata.initialCacheLoad) {
51
- readiness.definitions.emit(constants_2.SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
52
- }
53
- // Emits SDK_READY
54
- readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED, { type: constants_2.SEGMENTS_UPDATE, names: [] });
55
- });
56
- }
57
- return true;
58
- });
59
- }
60
- else {
21
+ if (!changes || !changes.d)
61
22
  return Promise.resolve(true);
62
- }
23
+ var _a = changes.d, updated = _a.updated, removed = _a.removed, till = _a.till;
24
+ log.debug(constants_2.SYNC_OFFLINE_DATA, [JSON.stringify(changes.d)]);
25
+ // On start, clear definitions that might be in the storage from a previous session (InLocalStorage), since the parser only tracks its own changes.
26
+ return Promise.resolve(startingUp && definitions.clear()).then(function () {
27
+ return definitions.update(updated, removed, till);
28
+ }).then(function (names) {
29
+ readiness.definitions.emit(constants_1.SDK_DEFINITIONS_ARRIVED, { type: constants_1.FLAGS_UPDATE, names: names });
30
+ if (startingUp) {
31
+ startingUp = false;
32
+ Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
33
+ // Emits SDK_READY_FROM_CACHE
34
+ if (!cacheMetadata.initialCacheLoad) {
35
+ readiness.definitions.emit(constants_1.SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
36
+ }
37
+ // Emits SDK_READY
38
+ readiness.segments.emit(constants_1.SDK_SEGMENTS_ARRIVED, { type: constants_1.SEGMENTS_UPDATE, names: [] });
39
+ });
40
+ }
41
+ return true;
42
+ });
63
43
  };
64
44
  }
65
45
  exports.fromObjectUpdaterFactory = fromObjectUpdaterFactory;
@@ -72,9 +72,8 @@ function computeMutation(update, segments, filters) {
72
72
  else {
73
73
  accum.removed.push(ruleEntity.name);
74
74
  }
75
- accum.names.push(ruleEntity.name);
76
75
  return accum;
77
- }, { updated: [], removed: update.removed, names: update.removed.slice() });
76
+ }, { updated: [], removed: update.removed });
78
77
  }
79
78
  exports.computeMutation = computeMutation;
80
79
  function convertInstantUpdateToDefinitionChanges(instantUpdate) {
@@ -147,30 +146,28 @@ function definitionChangesUpdaterFactory(log, definitionChangesFetcher, storage,
147
146
  .then(function (definitionChanges) {
148
147
  var usedSegments = new Set();
149
148
  var updatedDefinitions = [];
150
- var ffUpdate = false;
151
149
  if (definitionChanges.d) {
152
- var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed, names = _a.names;
153
- updatedDefinitions = names;
150
+ var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed;
154
151
  log.debug(constants_2.SYNC_UPDATE, [definitionChangesFetcher.type, updated.length, removed.length]);
155
- ffUpdate = definitions.update(updated, removed, definitionChanges.d.till);
152
+ updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
156
153
  }
157
- var rbsUpdate = false;
154
+ var updatedRBSNames = [];
158
155
  if (definitionChanges.rbs) {
159
156
  var _b = computeMutation(definitionChanges.rbs, usedSegments), updated = _b.updated, removed = _b.removed;
160
157
  log.debug(constants_2.SYNC_UPDATE, ['rule-based segments', updated.length, removed.length]);
161
- rbsUpdate = rbSegments.update(updated, removed, definitionChanges.rbs.till);
158
+ updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
162
159
  }
163
- return Promise.all([ffUpdate, rbsUpdate,
160
+ return Promise.all([updatedDefinitions, updatedRBSNames,
164
161
  // @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
165
162
  segments.registerSegments((0, sets_1.setToArray)(usedSegments))
166
163
  ]).then(function (_a) {
167
- var ffChanged = _a[0], rbsChanged = _a[1];
164
+ var updatedDefinitions = _a[0], updatedRBSNames = _a[1];
168
165
  if (storage.save)
169
166
  storage.save();
170
167
  startingUp = false;
171
168
  if (definitionsEventEmitter) {
172
169
  // To emit SDK_DEFINITIONS_ARRIVED for server-side SDK, we must wait for all registered segments to be fetched
173
- return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((ffChanged || rbsChanged) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
170
+ return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((updatedDefinitions.length > 0 || updatedRBSNames.length > 0) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
174
171
  .catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
175
172
  .then(function (emitSplitsArrivedEvent) {
176
173
  // emit SDK events
@@ -121,7 +121,7 @@ function DefinitionsUpdateWorker(params, pollingManager) {
121
121
  var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
122
122
  if (storage.definitions.killLocally(splitName, defaultTreatment, changeNumber)) {
123
123
  // trigger an SDK_UPDATE if Split was killed locally
124
- definitionsEventEmitter.emit(constants_2.SDK_DEFINITIONS_ARRIVED, true);
124
+ definitionsEventEmitter.emit(constants_2.SDK_DEFINITIONS_ARRIVED, { type: constants_2.FLAGS_UPDATE, names: [splitName] }, true /* isSplitKill */);
125
125
  }
126
126
  // queues the SplitChanges fetch (only if changeNumber is newer)
127
127
  ff.put({ changeNumber: changeNumber });
@@ -14,7 +14,8 @@ var AbstractDefinitionsCacheAsync = /** @class */ (function () {
14
14
  Promise.all(toRemove.map(function (removedFF) { return _this.remove(removedFF); }))
15
15
  ]).then(function (_a) {
16
16
  var added = _a[1], removed = _a[2];
17
- return added.some(function (result) { return result; }) || removed.some(function (result) { return result; });
17
+ return toRemove.filter(function (_, i) { return removed[i]; })
18
+ .concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (addedFF) { return addedFF.name; }));
18
19
  });
19
20
  };
20
21
  // @TODO revisit segment-related methods ('usesSegments', 'getRegisteredSegments', 'registerSegments')
@@ -9,8 +9,8 @@ var AbstractDefinitionsCacheSync = /** @class */ (function () {
9
9
  }
10
10
  AbstractDefinitionsCacheSync.prototype.update = function (toAdd, toRemove, changeNumber) {
11
11
  var _this = this;
12
- var updated = toAdd.map(function (addedFF) { return _this.add(addedFF); }).some(function (result) { return result; });
13
- updated = toRemove.map(function (removedFF) { return _this.remove(removedFF); }).some(function (result) { return result; }) || updated;
12
+ var updated = toRemove.filter(function (removedFF) { return _this.remove(removedFF); })
13
+ .concat(toAdd.filter(function (addedFF) { return _this.add(addedFF); }).map(function (addedFF) { return addedFF.name; }));
14
14
  this.setChangeNumber(changeNumber);
15
15
  return updated;
16
16
  };
@@ -72,6 +72,8 @@ var DefinitionsCacheInLocal = /** @class */ (function (_super) {
72
72
  var definitionFromStorage = this.storage.getItem(definitionKey);
73
73
  var previousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : null;
74
74
  if (previousDefinition) {
75
+ if (previousDefinition.changeNumber >= definition.changeNumber)
76
+ return false;
75
77
  this._decrementCounts(previousDefinition);
76
78
  this.removeFromSets(previousDefinition.name, previousDefinition.sets);
77
79
  }
@@ -15,8 +15,8 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
15
15
  };
16
16
  RBSegmentsCacheInLocal.prototype.update = function (toAdd, toRemove, changeNumber) {
17
17
  var _this = this;
18
- var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
19
- updated = toRemove.map(function (toRemove) { return _this.remove(toRemove); }).some(function (result) { return result; }) || updated;
18
+ var updated = toRemove.filter(function (name) { return _this.remove(name); })
19
+ .concat(toAdd.filter(function (rbSegment) { return _this.add(rbSegment); }).map(function (rbSegment) { return rbSegment.name; }));
20
20
  this.setChangeNumber(changeNumber);
21
21
  return updated;
22
22
  };
@@ -43,6 +43,8 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
43
43
  var rbSegmentKey = this.keys.buildRBSegmentKey(name);
44
44
  var rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
45
45
  var previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
46
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
47
+ return false;
46
48
  this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
47
49
  var usesSegmentsDiff = 0;
48
50
  if (previous && usesSegments(previous))
@@ -27,6 +27,8 @@ var DefinitionsCacheInMemory = /** @class */ (function (_super) {
27
27
  var name = definition.name;
28
28
  var previousDefinition = this.get(name);
29
29
  if (previousDefinition) { // We had this Split already
30
+ if (previousDefinition.changeNumber >= definition.changeNumber)
31
+ return false;
30
32
  var previousTtName = previousDefinition.trafficTypeName;
31
33
  this.ttCache[previousTtName]--;
32
34
  if (!this.ttCache[previousTtName])
@@ -13,8 +13,8 @@ var RBSegmentsCacheInMemory = /** @class */ (function () {
13
13
  };
14
14
  RBSegmentsCacheInMemory.prototype.update = function (toAdd, toRemove, changeNumber) {
15
15
  var _this = this;
16
- var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
17
- updated = toRemove.map(function (toRemove) { return _this.remove(toRemove); }).some(function (result) { return result; }) || updated;
16
+ var updated = toRemove.filter(function (name) { return _this.remove(name); })
17
+ .concat(toAdd.filter(function (rbSegment) { return _this.add(rbSegment); }).map(function (rbSegment) { return rbSegment.name; }));
18
18
  if (changeNumber !== undefined)
19
19
  this.changeNumber = changeNumber;
20
20
  return updated;
@@ -22,6 +22,8 @@ var RBSegmentsCacheInMemory = /** @class */ (function () {
22
22
  RBSegmentsCacheInMemory.prototype.add = function (rbSegment) {
23
23
  var name = rbSegment.name;
24
24
  var previous = this.get(name);
25
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
26
+ return false;
25
27
  if (previous && usesSegments(previous))
26
28
  this.segmentsCount--;
27
29
  this.cache[name] = rbSegment;
@@ -73,6 +73,8 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
73
73
  var parsedPreviousDefinition, stringifiedNewDefinition;
74
74
  try {
75
75
  parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
76
+ if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber)
77
+ return false;
76
78
  stringifiedNewDefinition = JSON.stringify(definition);
77
79
  }
78
80
  catch (e) {
@@ -87,8 +89,8 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
87
89
  if (parsedPreviousDefinition)
88
90
  return _this._decrementCounts(parsedPreviousDefinition);
89
91
  });
90
- }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); });
91
- }).then(function () { return true; });
92
+ }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); }).then(function () { return true; });
93
+ });
92
94
  };
93
95
  /**
94
96
  * Remove a given definition.
@@ -98,11 +100,11 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
98
100
  DefinitionsCacheInRedis.prototype.remove = function (name) {
99
101
  var _this = this;
100
102
  return this.get(name).then(function (definition) {
101
- if (definition) {
102
- return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); });
103
- }
104
- }).then(function () {
105
- return _this.redis.del(_this.keys.buildDefinitionKey(name)).then(function (status) { return status === 1; });
103
+ if (!definition)
104
+ return false;
105
+ return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); }).then(function () {
106
+ return _this.redis.del(_this.keys.buildDefinitionKey(name)).then(function (status) { return status === 1; });
107
+ });
106
108
  });
107
109
  };
108
110
  /**