@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
@@ -25,20 +25,28 @@ var RBSegmentsCacheInRedis = /** @class */ (function () {
25
25
  var _this = this;
26
26
  return Promise.all([
27
27
  this.setChangeNumber(changeNumber),
28
- Promise.all(toAdd.map(function (toAdd) {
29
- var key = _this.keys.buildRBSegmentKey(toAdd.name);
30
- var stringifiedNewRBSegment = JSON.stringify(toAdd);
31
- return _this.redis.set(key, stringifiedNewRBSegment).then(function () { return true; });
32
- })),
33
- Promise.all(toRemove.map(function (toRemove) {
34
- var key = _this.keys.buildRBSegmentKey(toRemove);
35
- return _this.redis.del(key).then(function (status) { return status === 1; });
36
- }))
28
+ Promise.all(toAdd.map(function (rbSegment) { return _this.add(rbSegment); })),
29
+ Promise.all(toRemove.map(function (name) { return _this.remove(name); }))
37
30
  ]).then(function (_a) {
38
31
  var added = _a[1], removed = _a[2];
39
- return added.some(function (result) { return result; }) || removed.some(function (result) { return result; });
32
+ return toRemove.filter(function (_, i) { return removed[i]; })
33
+ .concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (rbSegment) { return rbSegment.name; }));
40
34
  });
41
35
  };
36
+ RBSegmentsCacheInRedis.prototype.add = function (rbSegment) {
37
+ var _this = this;
38
+ var key = this.keys.buildRBSegmentKey(rbSegment.name);
39
+ return this.get(rbSegment.name).then(function (previous) {
40
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
41
+ return false;
42
+ var stringifiedNewRBSegment = JSON.stringify(rbSegment);
43
+ return _this.redis.set(key, stringifiedNewRBSegment).then(function () { return true; });
44
+ });
45
+ };
46
+ RBSegmentsCacheInRedis.prototype.remove = function (name) {
47
+ var key = this.keys.buildRBSegmentKey(name);
48
+ return this.redis.del(key).then(function (status) { return status === 1; });
49
+ };
42
50
  RBSegmentsCacheInRedis.prototype.setChangeNumber = function (changeNumber) {
43
51
  if (changeNumber !== undefined) {
44
52
  return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
@@ -60,6 +60,8 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
60
60
  var parsedPreviousDefinition, stringifiedNewDefinition;
61
61
  try {
62
62
  parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
63
+ if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber)
64
+ return false;
63
65
  stringifiedNewDefinition = JSON.stringify(definition);
64
66
  }
65
67
  catch (e) {
@@ -74,8 +76,8 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
74
76
  if (parsedPreviousDefinition)
75
77
  return _this._decrementCounts(parsedPreviousDefinition);
76
78
  });
77
- }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); });
78
- }).then(function () { return true; });
79
+ }).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); }).then(function () { return true; });
80
+ });
79
81
  };
80
82
  /**
81
83
  * Remove a given definition.
@@ -85,11 +87,11 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
85
87
  DefinitionsCachePluggable.prototype.remove = function (name) {
86
88
  var _this = this;
87
89
  return this.get(name).then(function (definition) {
88
- if (definition) {
89
- return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); });
90
- }
91
- }).then(function () {
92
- return _this.wrapper.del(_this.keys.buildDefinitionKey(name));
90
+ if (!definition)
91
+ return false;
92
+ return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); }).then(function () {
93
+ return _this.wrapper.del(_this.keys.buildDefinitionKey(name));
94
+ });
93
95
  });
94
96
  };
95
97
  /**
@@ -25,20 +25,28 @@ var RBSegmentsCachePluggable = /** @class */ (function () {
25
25
  var _this = this;
26
26
  return Promise.all([
27
27
  this.setChangeNumber(changeNumber),
28
- Promise.all(toAdd.map(function (toAdd) {
29
- var key = _this.keys.buildRBSegmentKey(toAdd.name);
30
- var stringifiedNewRBSegment = JSON.stringify(toAdd);
31
- return _this.wrapper.set(key, stringifiedNewRBSegment).then(function () { return true; });
32
- })),
33
- Promise.all(toRemove.map(function (toRemove) {
34
- var key = _this.keys.buildRBSegmentKey(toRemove);
35
- return _this.wrapper.del(key);
36
- }))
28
+ Promise.all(toAdd.map(function (rbSegment) { return _this.add(rbSegment); })),
29
+ Promise.all(toRemove.map(function (name) { return _this.remove(name); }))
37
30
  ]).then(function (_a) {
38
31
  var added = _a[1], removed = _a[2];
39
- return added.some(function (result) { return result; }) || removed.some(function (result) { return result; });
32
+ return toRemove.filter(function (_, i) { return removed[i]; })
33
+ .concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (rbSegment) { return rbSegment.name; }));
40
34
  });
41
35
  };
36
+ RBSegmentsCachePluggable.prototype.add = function (rbSegment) {
37
+ var _this = this;
38
+ var key = this.keys.buildRBSegmentKey(rbSegment.name);
39
+ return this.get(rbSegment.name).then(function (previous) {
40
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
41
+ return false;
42
+ var stringifiedNewRBSegment = JSON.stringify(rbSegment);
43
+ return _this.wrapper.set(key, stringifiedNewRBSegment).then(function () { return true; });
44
+ });
45
+ };
46
+ RBSegmentsCachePluggable.prototype.remove = function (name) {
47
+ var key = this.keys.buildRBSegmentKey(name);
48
+ return this.wrapper.del(key);
49
+ };
42
50
  RBSegmentsCachePluggable.prototype.setChangeNumber = function (changeNumber) {
43
51
  if (changeNumber !== undefined) {
44
52
  return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
@@ -0,0 +1,33 @@
1
+ import { __assign } from "tslib";
2
+ import { CONTROL } from '../../../utils/constants';
3
+ /**
4
+ * Factory of a stateful function that, given the current mocked definitions, returns the changes since the previous call
5
+ * as an `IDefinitionChangesResponse`, i.e., the full definitions that were added or modified, and the names of the removed ones.
6
+ * Definitions are compared by value. The response `till` is an incrementing value based on the current time.
7
+ * Definitions have no `changeNumber`, so storages never consider them outdated.
8
+ *
9
+ * The response has no `d` property if there are no changes, except for the first call, which reports the (possibly empty) mock.
10
+ * It is meant to be used by `IDefinitionsParser` implementations, such as `splitsParserFromSettings`.
11
+ */
12
+ export function definitionChangesBuilderFactory() {
13
+ var previous; // serialized definitions of the last reported mock, by name
14
+ var lastTill = 0;
15
+ return function definitionChangesBuilder(mock) {
16
+ var current = {};
17
+ var updatedNames = Object.keys(mock).reduce(function (updatedNames, name) {
18
+ current[name] = JSON.stringify(mock[name]);
19
+ if (!previous || previous[name] !== current[name])
20
+ updatedNames.push(name);
21
+ return updatedNames;
22
+ }, []);
23
+ var removed = previous ? Object.keys(previous).filter(function (name) { return current[name] === undefined; }) : [];
24
+ // Only the first call reports an empty change
25
+ if (previous && updatedNames.length === 0 && removed.length === 0)
26
+ return {};
27
+ previous = current;
28
+ // Incrementing `till` based on the current time
29
+ var till = lastTill = Math.max(Date.now(), lastTill + 1);
30
+ var updated = updatedNames.map(function (name) { return (__assign({ name: name, status: 'ACTIVE', killed: false, trafficAllocation: 100, defaultTreatment: CONTROL }, mock[name])); });
31
+ return { d: { updated: updated, removed: removed, till: till } };
32
+ };
33
+ }
@@ -1,42 +1,15 @@
1
- import { isObject, forOwn, merge } from '../../../utils/lang';
1
+ import { isObject, forOwn } from '../../../utils/lang';
2
2
  import { parseCondition } from './parseCondition';
3
- function hasTreatmentChanged(prev, curr) {
4
- if (typeof prev !== typeof curr)
5
- return true;
6
- if (typeof prev === 'string') { // strings treatments, just compare
7
- return prev !== curr;
8
- }
9
- else { // has treatment and config, compare both
10
- return prev.treatment !== curr.treatment || prev.config !== curr.config;
11
- }
12
- }
3
+ import { definitionChangesBuilderFactory } from './definitionChangesBuilder';
13
4
  export function splitsParserFromSettingsFactory() {
14
- var previousMock = { 'emptyMock': '1' };
15
- function mockUpdated(currentData) {
16
- var names = Object.keys(currentData);
17
- // Different amount of items
18
- if (names.length !== Object.keys(previousMock).length) {
19
- previousMock = merge({}, currentData);
20
- return true;
21
- }
22
- return names.some(function (name) {
23
- var newSplit = !previousMock[name];
24
- var newTreatment = hasTreatmentChanged(previousMock[name], currentData[name]);
25
- var changed = newSplit || newTreatment;
26
- if (changed)
27
- previousMock = merge({}, currentData);
28
- return changed;
29
- });
30
- }
5
+ var definitionChangesBuilder = definitionChangesBuilderFactory();
31
6
  /**
32
7
  *
33
8
  * @param settings - validated object with mocked features mapping.
34
9
  */
35
10
  return function splitsParserFromSettings(settings) {
36
11
  var features = settings.features || {};
37
- if (!mockUpdated(features))
38
- return false;
39
- var splitObjects = {};
12
+ var mock = {};
40
13
  forOwn(features, function (data, splitName) {
41
14
  var treatment = data;
42
15
  var config = null;
@@ -47,12 +20,12 @@ export function splitsParserFromSettingsFactory() {
47
20
  var configurations = {};
48
21
  if (config !== null)
49
22
  configurations[treatment] = config;
50
- splitObjects[splitName] = {
23
+ mock[splitName] = {
51
24
  trafficTypeName: 'localhost',
52
25
  conditions: [parseCondition({ treatment: treatment })],
53
26
  configurations: configurations
54
27
  };
55
28
  });
56
- return splitObjects;
29
+ return definitionChangesBuilder(mock);
57
30
  };
58
31
  }
@@ -1,6 +1,4 @@
1
- import { forOwn } from '../../../utils/lang';
2
1
  import { syncTaskFactory } from '../../syncTask';
3
- import { CONTROL } from '../../../utils/constants';
4
2
  import { SDK_DEFINITIONS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED, FLAGS_UPDATE, SEGMENTS_UPDATE } from '../../../readiness/constants';
5
3
  import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
6
4
  /**
@@ -10,53 +8,35 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
10
8
  var log = settings.log, definitions = storage.definitions;
11
9
  var startingUp = true;
12
10
  return function objectUpdater() {
13
- var splits = [];
14
- var loadError = null;
15
- var splitsMock = {};
11
+ var changes;
16
12
  try {
17
- splitsMock = splitsParser(settings);
13
+ changes = splitsParser(settings);
18
14
  }
19
15
  catch (err) {
20
- loadError = err;
21
16
  log.error(ERROR_SYNC_OFFLINE_LOADING, [err]);
22
17
  }
23
- if (!loadError && splitsMock) {
24
- log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
25
- forOwn(splitsMock, function (val, name) {
26
- // @ts-ignore Split changeNumber and seed is undefined in localhost mode
27
- splits.push({
28
- name: name,
29
- status: 'ACTIVE',
30
- killed: false,
31
- trafficAllocation: 100,
32
- defaultTreatment: CONTROL,
33
- conditions: val.conditions || [],
34
- configurations: val.configurations,
35
- trafficTypeName: val.trafficTypeName
36
- });
37
- });
38
- return Promise.all([
39
- definitions.clear(),
40
- definitions.update(splits, [], Date.now())
41
- ]).then(function () {
42
- readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [] });
43
- if (startingUp) {
44
- startingUp = false;
45
- Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
46
- // Emits SDK_READY_FROM_CACHE
47
- if (!cacheMetadata.initialCacheLoad) {
48
- readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
49
- }
50
- // Emits SDK_READY
51
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
52
- });
53
- }
54
- return true;
55
- });
56
- }
57
- else {
18
+ if (!changes || !changes.d)
58
19
  return Promise.resolve(true);
59
- }
20
+ var _a = changes.d, updated = _a.updated, removed = _a.removed, till = _a.till;
21
+ log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(changes.d)]);
22
+ // On start, clear definitions that might be in the storage from a previous session (InLocalStorage), since the parser only tracks its own changes.
23
+ return Promise.resolve(startingUp && definitions.clear()).then(function () {
24
+ return definitions.update(updated, removed, till);
25
+ }).then(function (names) {
26
+ readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: names });
27
+ if (startingUp) {
28
+ startingUp = false;
29
+ Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
30
+ // Emits SDK_READY_FROM_CACHE
31
+ if (!cacheMetadata.initialCacheLoad) {
32
+ readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
33
+ }
34
+ // Emits SDK_READY
35
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
36
+ });
37
+ }
38
+ return true;
39
+ });
60
40
  };
61
41
  }
62
42
  /**
@@ -68,9 +68,8 @@ export function computeMutation(update, segments, filters) {
68
68
  else {
69
69
  accum.removed.push(ruleEntity.name);
70
70
  }
71
- accum.names.push(ruleEntity.name);
72
71
  return accum;
73
- }, { updated: [], removed: update.removed, names: update.removed.slice() });
72
+ }, { updated: [], removed: update.removed });
74
73
  }
75
74
  function convertInstantUpdateToDefinitionChanges(instantUpdate) {
76
75
  var payload = instantUpdate.payload, changeNumber = instantUpdate.changeNumber;
@@ -142,30 +141,28 @@ export function definitionChangesUpdaterFactory(log, definitionChangesFetcher, s
142
141
  .then(function (definitionChanges) {
143
142
  var usedSegments = new Set();
144
143
  var updatedDefinitions = [];
145
- var ffUpdate = false;
146
144
  if (definitionChanges.d) {
147
- var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed, names = _a.names;
148
- updatedDefinitions = names;
145
+ var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed;
149
146
  log.debug(SYNC_UPDATE, [definitionChangesFetcher.type, updated.length, removed.length]);
150
- ffUpdate = definitions.update(updated, removed, definitionChanges.d.till);
147
+ updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
151
148
  }
152
- var rbsUpdate = false;
149
+ var updatedRBSNames = [];
153
150
  if (definitionChanges.rbs) {
154
151
  var _b = computeMutation(definitionChanges.rbs, usedSegments), updated = _b.updated, removed = _b.removed;
155
152
  log.debug(SYNC_UPDATE, ['rule-based segments', updated.length, removed.length]);
156
- rbsUpdate = rbSegments.update(updated, removed, definitionChanges.rbs.till);
153
+ updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
157
154
  }
158
- return Promise.all([ffUpdate, rbsUpdate,
155
+ return Promise.all([updatedDefinitions, updatedRBSNames,
159
156
  // @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
160
157
  segments.registerSegments(setToArray(usedSegments))
161
158
  ]).then(function (_a) {
162
- var ffChanged = _a[0], rbsChanged = _a[1];
159
+ var updatedDefinitions = _a[0], updatedRBSNames = _a[1];
163
160
  if (storage.save)
164
161
  storage.save();
165
162
  startingUp = false;
166
163
  if (definitionsEventEmitter) {
167
164
  // To emit SDK_DEFINITIONS_ARRIVED for server-side SDK, we must wait for all registered segments to be fetched
168
- return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((ffChanged || rbsChanged) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
165
+ return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((updatedDefinitions.length > 0 || updatedRBSNames.length > 0) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
169
166
  .catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
170
167
  .then(function (emitSplitsArrivedEvent) {
171
168
  // emit SDK events
@@ -1,5 +1,5 @@
1
1
  import { STREAMING_PARSING_SPLIT_UPDATE } from '../../../logger/constants';
2
- import { SDK_DEFINITIONS_ARRIVED } from '../../../readiness/constants';
2
+ import { SDK_DEFINITIONS_ARRIVED, FLAGS_UPDATE } from '../../../readiness/constants';
3
3
  import { Backoff } from '../../../utils/Backoff';
4
4
  import { SPLITS } from '../../../utils/constants';
5
5
  import { RB_SEGMENT_UPDATE } from '../constants';
@@ -118,7 +118,7 @@ export function DefinitionsUpdateWorker(params, pollingManager) {
118
118
  var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
119
119
  if (storage.definitions.killLocally(splitName, defaultTreatment, changeNumber)) {
120
120
  // trigger an SDK_UPDATE if Split was killed locally
121
- definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, true);
121
+ definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [splitName] }, true /* isSplitKill */);
122
122
  }
123
123
  // queues the SplitChanges fetch (only if changeNumber is newer)
124
124
  ff.put({ changeNumber: changeNumber });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "3.1.1-rc.12",
3
+ "version": "3.1.1-rc.13",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -12,13 +12,14 @@ export abstract class AbstractDefinitionsCacheAsync implements IDefinitionsCache
12
12
  protected abstract remove(name: string): Promise<boolean>
13
13
  protected abstract setChangeNumber(changeNumber?: number): MaybeThenable<boolean | void>
14
14
 
15
- update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): Promise<boolean> {
15
+ update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): Promise<string[]> {
16
16
  return Promise.all([
17
17
  this.setChangeNumber(changeNumber),
18
18
  Promise.all(toAdd.map(addedFF => this.add(addedFF))),
19
19
  Promise.all(toRemove.map(removedFF => this.remove(removedFF)))
20
20
  ]).then(([, added, removed]) => {
21
- return added.some(result => result) || removed.some(result => result);
21
+ return toRemove.filter((_, i) => removed[i])
22
+ .concat(toAdd.filter((_, i) => added[i]).map(addedFF => addedFF.name));
22
23
  });
23
24
  }
24
25
 
@@ -13,9 +13,9 @@ export abstract class AbstractDefinitionsCacheSync implements IDefinitionsCacheS
13
13
  protected abstract remove(name: string): boolean
14
14
  protected abstract setChangeNumber(changeNumber?: number): boolean | void
15
15
 
16
- update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): boolean {
17
- let updated = toAdd.map(addedFF => this.add(addedFF)).some(result => result);
18
- updated = toRemove.map(removedFF => this.remove(removedFF)).some(result => result) || updated;
16
+ update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): string[] {
17
+ const updated = toRemove.filter(removedFF => this.remove(removedFF))
18
+ .concat(toAdd.filter(addedFF => this.add(addedFF)).map(addedFF => addedFF.name));
19
19
  this.setChangeNumber(changeNumber);
20
20
  return updated;
21
21
  }
@@ -79,13 +79,15 @@ export class DefinitionsCacheInLocal extends AbstractDefinitionsCacheSync {
79
79
  this.hasSync = false;
80
80
  }
81
81
 
82
- add(definition: IDefinition) {
82
+ add(definition: IDefinition): boolean {
83
83
  const name = definition.name;
84
84
  const definitionKey = this.keys.buildDefinitionKey(name);
85
85
  const definitionFromStorage = this.storage.getItem(definitionKey);
86
86
  const previousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : null;
87
87
 
88
88
  if (previousDefinition) {
89
+ if (previousDefinition.changeNumber >= definition.changeNumber) return false;
90
+
89
91
  this._decrementCounts(previousDefinition);
90
92
  this.removeFromSets(previousDefinition.name, previousDefinition.sets);
91
93
  }
@@ -25,9 +25,9 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
25
25
  this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
26
26
  }
27
27
 
28
- update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): boolean {
29
- let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
30
- updated = toRemove.map(toRemove => this.remove(toRemove)).some(result => result) || updated;
28
+ update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[] {
29
+ const updated = toRemove.filter(name => this.remove(name))
30
+ .concat(toAdd.filter(rbSegment => this.add(rbSegment)).map(rbSegment => rbSegment.name));
31
31
  this.setChangeNumber(changeNumber);
32
32
  return updated;
33
33
  }
@@ -54,6 +54,8 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
54
54
  const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
55
55
  const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
56
56
 
57
+ if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
58
+
57
59
  this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
58
60
 
59
61
  let usesSegmentsDiff = 0;
@@ -31,6 +31,7 @@ export class DefinitionsCacheInMemory extends AbstractDefinitionsCacheSync {
31
31
  const name = definition.name;
32
32
  const previousDefinition = this.get(name);
33
33
  if (previousDefinition) { // We had this Split already
34
+ if (previousDefinition.changeNumber >= definition.changeNumber) return false;
34
35
 
35
36
  const previousTtName = previousDefinition.trafficTypeName;
36
37
  this.ttCache[previousTtName]--;
@@ -15,9 +15,9 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
15
15
  this.segmentsCount = 0;
16
16
  }
17
17
 
18
- update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): boolean {
19
- let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
20
- updated = toRemove.map(toRemove => this.remove(toRemove)).some(result => result) || updated;
18
+ update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[] {
19
+ const updated = toRemove.filter(name => this.remove(name))
20
+ .concat(toAdd.filter(rbSegment => this.add(rbSegment)).map(rbSegment => rbSegment.name));
21
21
  if (changeNumber !== undefined) this.changeNumber = changeNumber;
22
22
  return updated;
23
23
  }
@@ -25,6 +25,7 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
25
25
  private add(rbSegment: IRBSegment): boolean {
26
26
  const name = rbSegment.name;
27
27
  const previous = this.get(name);
28
+ if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
28
29
  if (previous && usesSegments(previous)) this.segmentsCount--;
29
30
 
30
31
  this.cache[name] = rbSegment;
@@ -91,6 +91,8 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
91
91
  let parsedPreviousDefinition: IDefinition, stringifiedNewDefinition;
92
92
  try {
93
93
  parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
94
+ if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber) return false;
95
+
94
96
  stringifiedNewDefinition = JSON.stringify(definition);
95
97
  } catch (e) {
96
98
  throw new Error('Error parsing feature flag definition: ' + e);
@@ -104,8 +106,8 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
104
106
  return this._incrementCounts(definition).then(() => {
105
107
  if (parsedPreviousDefinition) return this._decrementCounts(parsedPreviousDefinition);
106
108
  });
107
- }).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets));
108
- }).then(() => true);
109
+ }).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets)).then(() => true);
110
+ });
109
111
  }
110
112
 
111
113
  /**
@@ -115,11 +117,11 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
115
117
  */
116
118
  remove(name: string) {
117
119
  return this.get(name).then((definition) => {
118
- if (definition) {
119
- return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets));
120
- }
121
- }).then(() => {
122
- return this.redis.del(this.keys.buildDefinitionKey(name)).then((status: number) => status === 1);
120
+ if (!definition) return false;
121
+
122
+ return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets)).then(() => {
123
+ return this.redis.del(this.keys.buildDefinitionKey(name)).then((status: number) => status === 1);
124
+ });
123
125
  });
124
126
  }
125
127
 
@@ -37,23 +37,32 @@ export class RBSegmentsCacheInRedis implements IRBSegmentsCacheAsync {
37
37
  });
38
38
  }
39
39
 
40
- update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<boolean> {
40
+ update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<string[]> {
41
41
  return Promise.all([
42
42
  this.setChangeNumber(changeNumber),
43
- Promise.all(toAdd.map(toAdd => {
44
- const key = this.keys.buildRBSegmentKey(toAdd.name);
45
- const stringifiedNewRBSegment = JSON.stringify(toAdd);
46
- return this.redis.set(key, stringifiedNewRBSegment).then(() => true);
47
- })),
48
- Promise.all(toRemove.map(toRemove => {
49
- const key = this.keys.buildRBSegmentKey(toRemove);
50
- return this.redis.del(key).then((status: number) => status === 1);
51
- }))
43
+ Promise.all(toAdd.map(rbSegment => this.add(rbSegment))),
44
+ Promise.all(toRemove.map(name => this.remove(name)))
52
45
  ]).then(([, added, removed]) => {
53
- return added.some(result => result) || removed.some(result => result);
46
+ return toRemove.filter((_, i) => removed[i])
47
+ .concat(toAdd.filter((_, i) => added[i]).map(rbSegment => rbSegment.name));
54
48
  });
55
49
  }
56
50
 
51
+ private add(rbSegment: IRBSegment): Promise<boolean> {
52
+ const key = this.keys.buildRBSegmentKey(rbSegment.name);
53
+ return this.get(rbSegment.name).then(previous => {
54
+ if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
55
+
56
+ const stringifiedNewRBSegment = JSON.stringify(rbSegment);
57
+ return this.redis.set(key, stringifiedNewRBSegment).then(() => true);
58
+ });
59
+ }
60
+
61
+ private remove(name: string): Promise<boolean> {
62
+ const key = this.keys.buildRBSegmentKey(name);
63
+ return this.redis.del(key).then((status: number) => status === 1);
64
+ }
65
+
57
66
  setChangeNumber(changeNumber?: number) {
58
67
  if (changeNumber !== undefined) {
59
68
  return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
@@ -75,11 +75,14 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
75
75
  let parsedPreviousDefinition: IDefinition, stringifiedNewDefinition;
76
76
  try {
77
77
  parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
78
+ if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber) return false;
79
+
78
80
  stringifiedNewDefinition = JSON.stringify(definition);
79
81
  } catch (e) {
80
82
  throw new Error('Error parsing feature flag definition: ' + e);
81
83
  }
82
84
 
85
+
83
86
  return this.wrapper.set(definitionKey, stringifiedNewDefinition).then(() => {
84
87
  // avoid unnecessary increment/decrement operations
85
88
  if (parsedPreviousDefinition && parsedPreviousDefinition.trafficTypeName === definition.trafficTypeName) return;
@@ -88,8 +91,8 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
88
91
  return this._incrementCounts(definition).then(() => {
89
92
  if (parsedPreviousDefinition) return this._decrementCounts(parsedPreviousDefinition);
90
93
  });
91
- }).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets));
92
- }).then(() => true);
94
+ }).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets)).then(() => true);
95
+ });
93
96
  }
94
97
 
95
98
  /**
@@ -99,11 +102,11 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
99
102
  */
100
103
  remove(name: string) {
101
104
  return this.get(name).then((definition) => {
102
- if (definition) {
103
- return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets));
104
- }
105
- }).then(() => {
106
- return this.wrapper.del(this.keys.buildDefinitionKey(name));
105
+ if (!definition) return false;
106
+
107
+ return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets)).then(() => {
108
+ return this.wrapper.del(this.keys.buildDefinitionKey(name));
109
+ });
107
110
  });
108
111
  }
109
112