@splitsoftware/splitio-commons 3.1.1-rc.11 → 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 (77) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/services/authProvider.js +2 -2
  3. package/cjs/{sync/streaming/AuthClient/index.js → services/fetchAuth.js} +2 -2
  4. package/cjs/services/splitApi.js +2 -2
  5. package/cjs/storages/AbstractDefinitionsCacheAsync.js +2 -1
  6. package/cjs/storages/AbstractDefinitionsCacheSync.js +2 -2
  7. package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +4 -1
  8. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -3
  9. package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +4 -1
  10. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +6 -3
  11. package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +12 -8
  12. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +21 -11
  13. package/cjs/storages/pluggable/DefinitionsCachePluggable.js +12 -8
  14. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +21 -11
  15. package/cjs/sync/offline/splitsParser/definitionChangesBuilder.js +37 -0
  16. package/cjs/sync/offline/splitsParser/splitsParserFromSettings.js +5 -32
  17. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +26 -46
  18. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
  19. package/cjs/sync/polling/updaters/definitionChangesUpdater.js +22 -15
  20. package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +6 -4
  21. package/cjs/sync/streaming/parseUtils.js +4 -4
  22. package/cjs/sync/streaming/pushManager.js +4 -3
  23. package/esm/services/authProvider.js +1 -1
  24. package/esm/{sync/streaming/AuthClient/index.js → services/fetchAuth.js} +2 -2
  25. package/esm/services/splitApi.js +1 -1
  26. package/esm/storages/AbstractDefinitionsCacheAsync.js +2 -1
  27. package/esm/storages/AbstractDefinitionsCacheSync.js +2 -2
  28. package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +4 -1
  29. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -3
  30. package/esm/storages/inMemory/DefinitionsCacheInMemory.js +4 -1
  31. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +6 -3
  32. package/esm/storages/inRedis/DefinitionsCacheInRedis.js +12 -8
  33. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +21 -11
  34. package/esm/storages/pluggable/DefinitionsCachePluggable.js +12 -8
  35. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +21 -11
  36. package/esm/sync/offline/splitsParser/definitionChangesBuilder.js +33 -0
  37. package/esm/sync/offline/splitsParser/splitsParserFromSettings.js +6 -33
  38. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +23 -43
  39. package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
  40. package/esm/sync/polling/updaters/definitionChangesUpdater.js +22 -15
  41. package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +8 -6
  42. package/esm/sync/streaming/parseUtils.js +2 -2
  43. package/esm/sync/streaming/pushManager.js +4 -3
  44. package/package.json +1 -1
  45. package/src/dtos/types.ts +4 -4
  46. package/src/services/authProvider.ts +2 -2
  47. package/src/{sync/streaming/AuthClient/index.ts → services/fetchAuth.ts} +3 -4
  48. package/src/services/splitApi.ts +1 -1
  49. package/src/services/types.ts +21 -4
  50. package/src/storages/AbstractDefinitionsCacheAsync.ts +5 -4
  51. package/src/storages/AbstractDefinitionsCacheSync.ts +4 -4
  52. package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +5 -3
  53. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +7 -5
  54. package/src/storages/inMemory/DefinitionsCacheInMemory.ts +3 -2
  55. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +5 -4
  56. package/src/storages/inRedis/DefinitionsCacheInRedis.ts +15 -11
  57. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +26 -15
  58. package/src/storages/pluggable/DefinitionsCachePluggable.ts +14 -9
  59. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +24 -13
  60. package/src/storages/types.ts +6 -6
  61. package/src/sync/offline/splitsParser/definitionChangesBuilder.ts +49 -0
  62. package/src/sync/offline/splitsParser/splitsParserFromSettings.ts +8 -38
  63. package/src/sync/offline/splitsParser/types.ts +7 -3
  64. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +25 -45
  65. package/src/sync/polling/fetchers/splitChangesFetcher.ts +5 -5
  66. package/src/sync/polling/types.ts +22 -1
  67. package/src/sync/polling/updaters/definitionChangesUpdater.ts +35 -33
  68. package/src/sync/streaming/SSEClient/index.ts +1 -1
  69. package/src/sync/streaming/SSEClient/types.ts +1 -1
  70. package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +17 -13
  71. package/src/sync/streaming/UpdateWorkers/types.ts +6 -0
  72. package/src/sync/streaming/parseUtils.ts +3 -4
  73. package/src/sync/streaming/pushManager.ts +4 -3
  74. package/src/sync/syncManagerOnline.ts +0 -1
  75. package/cjs/sync/streaming/AuthClient/types.js +0 -2
  76. package/esm/sync/streaming/AuthClient/types.js +0 -1
  77. package/src/sync/streaming/AuthClient/types.ts +0 -19
@@ -8,6 +8,7 @@ var lang_1 = require("../../../utils/lang");
8
8
  var constants_3 = require("../../../utils/constants");
9
9
  var sets_1 = require("../../../utils/lang/sets");
10
10
  var constants_4 = require("../../streaming/constants");
11
+ var thenable_1 = require("../../../utils/promise/thenable");
11
12
  /**
12
13
  * Collect segments from a raw FF or RBS definition.
13
14
  * Exported for testing purposes.
@@ -71,9 +72,8 @@ function computeMutation(update, segments, filters) {
71
72
  else {
72
73
  accum.removed.push(ruleEntity.name);
73
74
  }
74
- accum.names.push(ruleEntity.name);
75
75
  return accum;
76
- }, { updated: [], removed: update.removed, names: update.removed.slice() });
76
+ }, { updated: [], removed: update.removed });
77
77
  }
78
78
  exports.computeMutation = computeMutation;
79
79
  function convertInstantUpdateToDefinitionChanges(instantUpdate) {
@@ -115,7 +115,7 @@ function definitionChangesUpdaterFactory(log, definitionChangesFetcher, storage,
115
115
  * @param noCache - true to revalidate data to fetch
116
116
  * @param till - query param to bypass CDN requests
117
117
  */
118
- return function definitionChangesUpdater(noCache, till, instantUpdate) {
118
+ return function definitionChangesUpdater(noCache, till, instantUpdate, pcns) {
119
119
  /**
120
120
  * @param since - current changeNumber at definitionsCache
121
121
  * @param retry - current number of retry attempts
@@ -123,8 +123,14 @@ function definitionChangesUpdaterFactory(log, definitionChangesFetcher, storage,
123
123
  function _definitionChangesUpdater(sinces, retry) {
124
124
  if (retry === void 0) { retry = 0; }
125
125
  var since = sinces[0], rbSince = sinces[1];
126
- log.debug(constants_2.SYNC_FETCH, [definitionChangesFetcher.type, since, rbSince]);
127
- return Promise.resolve(instantUpdate ?
126
+ // If provided, `since` and `rbSince` must not be higher than it, to avoid missing an update that the scoped `pcn` indicates hasn't been fetched yet.
127
+ if (pcns) {
128
+ if (pcns.since !== undefined && pcns.since < since)
129
+ since = pcns.since;
130
+ if (pcns.rbSince !== undefined && pcns.rbSince < rbSince)
131
+ rbSince = pcns.rbSince;
132
+ }
133
+ var definitionChangesRequest = instantUpdate ?
128
134
  instantUpdate.type === constants_4.RB_SEGMENT_UPDATE ?
129
135
  { rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
130
136
  // IFFU edge case: a change to definition that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
@@ -133,34 +139,35 @@ function definitionChangesUpdaterFactory(log, definitionChangesFetcher, storage,
133
139
  { d: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
134
140
  definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
135
141
  }) :
136
- definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator))
142
+ definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
143
+ if ((0, thenable_1.thenable)(definitionChangesRequest))
144
+ log.debug(constants_2.SYNC_FETCH, [definitionChangesFetcher.type, since, rbSince]);
145
+ return Promise.resolve(definitionChangesRequest)
137
146
  .then(function (definitionChanges) {
138
147
  var usedSegments = new Set();
139
148
  var updatedDefinitions = [];
140
- var ffUpdate = false;
141
149
  if (definitionChanges.d) {
142
- var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed, names = _a.names;
143
- updatedDefinitions = names;
150
+ var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed;
144
151
  log.debug(constants_2.SYNC_UPDATE, [definitionChangesFetcher.type, updated.length, removed.length]);
145
- ffUpdate = definitions.update(updated, removed, definitionChanges.d.till);
152
+ updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
146
153
  }
147
- var rbsUpdate = false;
154
+ var updatedRBSNames = [];
148
155
  if (definitionChanges.rbs) {
149
156
  var _b = computeMutation(definitionChanges.rbs, usedSegments), updated = _b.updated, removed = _b.removed;
150
157
  log.debug(constants_2.SYNC_UPDATE, ['rule-based segments', updated.length, removed.length]);
151
- rbsUpdate = rbSegments.update(updated, removed, definitionChanges.rbs.till);
158
+ updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
152
159
  }
153
- return Promise.all([ffUpdate, rbsUpdate,
160
+ return Promise.all([updatedDefinitions, updatedRBSNames,
154
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
155
162
  segments.registerSegments((0, sets_1.setToArray)(usedSegments))
156
163
  ]).then(function (_a) {
157
- var ffChanged = _a[0], rbsChanged = _a[1];
164
+ var updatedDefinitions = _a[0], updatedRBSNames = _a[1];
158
165
  if (storage.save)
159
166
  storage.save();
160
167
  startingUp = false;
161
168
  if (definitionsEventEmitter) {
162
169
  // To emit SDK_DEFINITIONS_ARRIVED for server-side SDK, we must wait for all registered segments to be fetched
163
- 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))))
164
171
  .catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
165
172
  .then(function (emitSplitsArrivedEvent) {
166
173
  // emit SDK events
@@ -9,9 +9,11 @@ var constants_4 = require("../constants");
9
9
  var parseUtils_1 = require("../parseUtils");
10
10
  var constants_5 = require("./constants");
11
11
  /**
12
- * DefinitionsUpdateWorker factory
12
+ * DefinitionsUpdateWorker factory for Flags SDKs, where notifications pcn and changeNumber are environment-scoped
13
13
  */
14
- function DefinitionsUpdateWorker(log, storage, definitionsSyncTask, definitionsEventEmitter, telemetryTracker) {
14
+ function DefinitionsUpdateWorker(params, pollingManager) {
15
+ var log = params.settings.log, storage = params.storage, telemetryTracker = params.telemetryTracker, definitionsEventEmitter = params.readiness.definitions;
16
+ var definitionsSyncTask = pollingManager.definitionsSyncTask;
15
17
  var ff = DefinitionsUpdateWorker(storage.definitions);
16
18
  var rbs = DefinitionsUpdateWorker(storage.rbSegments);
17
19
  function DefinitionsUpdateWorker(cache) {
@@ -96,7 +98,7 @@ function DefinitionsUpdateWorker(log, storage, definitionsSyncTask, definitionsE
96
98
  put: function (parsedData) {
97
99
  if (parsedData.d && parsedData.c !== undefined) {
98
100
  try {
99
- var payload = (0, parseUtils_1.parseFFUpdatePayload)(parsedData.c, parsedData.d);
101
+ var payload = (0, parseUtils_1.parseUpdatePayload)(parsedData.c, parsedData.d);
100
102
  if (payload) {
101
103
  (parsedData.type === constants_4.RB_SEGMENT_UPDATE ? rbs : ff).put(parsedData, payload);
102
104
  return;
@@ -119,7 +121,7 @@ function DefinitionsUpdateWorker(log, storage, definitionsSyncTask, definitionsE
119
121
  var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
120
122
  if (storage.definitions.killLocally(splitName, defaultTreatment, changeNumber)) {
121
123
  // trigger an SDK_UPDATE if Split was killed locally
122
- 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 */);
123
125
  }
124
126
  // queues the SplitChanges fetch (only if changeNumber is newer)
125
127
  ff.put({ changeNumber: changeNumber });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDelay = exports.parseFFUpdatePayload = exports.isInBitmap = exports.parseBitmap = exports.parseCompressedData = void 0;
3
+ exports.getDelay = exports.parseUpdatePayload = exports.isInBitmap = exports.parseBitmap = exports.parseCompressedData = void 0;
4
4
  var decompress_1 = require("../../utils/decompress");
5
5
  var base64_1 = require("../../utils/base64");
6
6
  var murmur3_1 = require("../../utils/murmur3/murmur3");
@@ -79,14 +79,14 @@ function isInBitmap(bitmap, hash64hex) {
79
79
  }
80
80
  exports.isInBitmap = isInBitmap;
81
81
  /**
82
- * Parse feature flags notifications for instant feature flag updates
82
+ * Parse notifications for instant updates
83
83
  */
84
- function parseFFUpdatePayload(compression, data) {
84
+ function parseUpdatePayload(compression, data) {
85
85
  return compression > 0 ?
86
86
  parseCompressedData(data, compression, false) :
87
87
  JSON.parse((0, base64_1.decodeFromBase64)(data));
88
88
  }
89
- exports.parseFFUpdatePayload = parseFFUpdatePayload;
89
+ exports.parseUpdatePayload = parseUpdatePayload;
90
90
  var DEFAULT_MAX_INTERVAL = 60000;
91
91
  function getDelay(parsedData, matchingKey) {
92
92
  if (parsedData.h === 0)
@@ -21,8 +21,9 @@ var constants_3 = require("../../utils/constants");
21
21
  * - for server-side if key is not provided in settings.
22
22
  * - for client-side, with support for multiple clients, if key is provided in settings
23
23
  */
24
- function pushManagerFactory(params, pollingManager) {
25
- var settings = params.settings, storage = params.storage, serviceApi = params.serviceApi, readiness = params.readiness, platform = params.platform, telemetryTracker = params.telemetryTracker;
24
+ function pushManagerFactory(params, pollingManager, definitionsUpdateWorkerFactory) {
25
+ if (definitionsUpdateWorkerFactory === void 0) { definitionsUpdateWorkerFactory = DefinitionsUpdateWorker_1.DefinitionsUpdateWorker; }
26
+ var settings = params.settings, storage = params.storage, serviceApi = params.serviceApi, platform = params.platform, telemetryTracker = params.telemetryTracker;
26
27
  // `userKey` is the matching key of main client in client-side SDK.
27
28
  // It can be used to check if running on client-side or server-side SDK.
28
29
  var userKey = (0, key_1.checkIfServerSide)(settings) ? undefined : (0, key_1.getMatching)(settings.core.key);
@@ -44,7 +45,7 @@ function pushManagerFactory(params, pollingManager) {
44
45
  // MySegmentsUpdateWorker (client-side) are initiated in `add` method
45
46
  var segmentsUpdateWorker = userKey ? undefined : (0, SegmentsUpdateWorker_1.SegmentsUpdateWorker)(log, pollingManager.segmentsSyncTask, storage.segments);
46
47
  // For server-side we pass the segmentsSyncTask, used by DefinitionsUpdateWorker to fetch new segments
47
- var definitionsUpdateWorker = (0, DefinitionsUpdateWorker_1.DefinitionsUpdateWorker)(log, storage, pollingManager.definitionsSyncTask, readiness.definitions, telemetryTracker);
48
+ var definitionsUpdateWorker = definitionsUpdateWorkerFactory(params, pollingManager);
48
49
  // [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
49
50
  // Hash64 is used to process membership update events and dispatch actions to the corresponding MySegmentsUpdateWorker.
50
51
  var clients = {};
@@ -1,4 +1,4 @@
1
- import { fetchAuthFactory } from '../sync/streaming/AuthClient';
1
+ import { fetchAuthFactory } from './fetchAuth';
2
2
  import { Backoff } from '../utils/Backoff';
3
3
  import { LOG_PREFIX_SYNC_AUTH } from '../logger/constants';
4
4
  import { TOKEN } from '../utils/constants';
@@ -1,5 +1,5 @@
1
- import { objectAssign } from '../../../utils/lang/objectAssign';
2
- import { decodeJWTtoken } from '../../../utils/jwt';
1
+ import { objectAssign } from '../utils/lang/objectAssign';
2
+ import { decodeJWTtoken } from '../utils/jwt';
3
3
  /**
4
4
  * Factory of authentication function.
5
5
  *
@@ -2,7 +2,7 @@ import { splitHttpClientFactory } from './splitHttpClient';
2
2
  import { objectAssign } from '../utils/lang/objectAssign';
3
3
  import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MEMBERSHIPS } from '../utils/constants';
4
4
  import { ERROR_TOO_MANY_SETS } from '../logger/constants';
5
- import { fetchAuthFactory } from '../sync/streaming/AuthClient';
5
+ import { fetchAuthFactory } from './fetchAuth';
6
6
  var noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
7
7
  function userKeyToQueryParam(userKey) {
8
8
  return 'users=' + encodeURIComponent(userKey); // no need to check availability of `encodeURIComponent`, since it is a global highly supported.
@@ -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
  }
@@ -95,7 +97,8 @@ var DefinitionsCacheInLocal = /** @class */ (function (_super) {
95
97
  };
96
98
  DefinitionsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
97
99
  try {
98
- this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
100
+ if (changeNumber !== undefined)
101
+ this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
99
102
  // update "last updated" timestamp with current time
100
103
  this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
101
104
  this.hasSync = true;
@@ -15,14 +15,15 @@ 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
  };
23
23
  RBSegmentsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
24
24
  try {
25
- this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
25
+ if (changeNumber !== undefined)
26
+ this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
26
27
  this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
27
28
  }
28
29
  catch (e) {
@@ -42,6 +43,8 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
42
43
  var rbSegmentKey = this.keys.buildRBSegmentKey(name);
43
44
  var rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
44
45
  var previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
46
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
47
+ return false;
45
48
  this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
46
49
  var usesSegmentsDiff = 0;
47
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])
@@ -67,7 +69,8 @@ var DefinitionsCacheInMemory = /** @class */ (function (_super) {
67
69
  return this.definitionsCache[name] || null;
68
70
  };
69
71
  DefinitionsCacheInMemory.prototype.setChangeNumber = function (changeNumber) {
70
- this.changeNumber = changeNumber;
72
+ if (changeNumber !== undefined)
73
+ this.changeNumber = changeNumber;
71
74
  return true;
72
75
  };
73
76
  DefinitionsCacheInMemory.prototype.getChangeNumber = function () {
@@ -13,14 +13,17 @@ 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;
18
- this.changeNumber = changeNumber;
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
+ if (changeNumber !== undefined)
19
+ this.changeNumber = changeNumber;
19
20
  return updated;
20
21
  };
21
22
  RBSegmentsCacheInMemory.prototype.add = function (rbSegment) {
22
23
  var name = rbSegment.name;
23
24
  var previous = this.get(name);
25
+ if (previous && previous.changeNumber >= rbSegment.changeNumber)
26
+ return false;
24
27
  if (previous && usesSegments(previous))
25
28
  this.segmentsCount--;
26
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
  /**
@@ -123,7 +125,9 @@ var DefinitionsCacheInRedis = /** @class */ (function (_super) {
123
125
  * or rejected if it fails.
124
126
  */
125
127
  DefinitionsCacheInRedis.prototype.setChangeNumber = function (changeNumber) {
126
- return this.redis.set(this.keys.buildDefinitionsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
128
+ if (changeNumber !== undefined) {
129
+ return this.redis.set(this.keys.buildDefinitionsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
130
+ }
127
131
  };
128
132
  /**
129
133
  * Get till number or -1 if it's not defined.
@@ -25,22 +25,32 @@ 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
- return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
51
+ if (changeNumber !== undefined) {
52
+ return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
53
+ }
44
54
  };
45
55
  RBSegmentsCacheInRedis.prototype.getChangeNumber = function () {
46
56
  var _this = this;
@@ -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
  /**
@@ -183,7 +185,9 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
183
185
  * or rejected if it fails (e.g., wrapper operation fails).
184
186
  */
185
187
  DefinitionsCachePluggable.prototype.setChangeNumber = function (changeNumber) {
186
- return this.wrapper.set(this.keys.buildDefinitionsTillKey(), changeNumber + '');
188
+ if (changeNumber !== undefined) {
189
+ return this.wrapper.set(this.keys.buildDefinitionsTillKey(), changeNumber + '');
190
+ }
187
191
  };
188
192
  /**
189
193
  * Get till number or -1 if it's not defined.
@@ -25,22 +25,32 @@ 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
- return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
51
+ if (changeNumber !== undefined) {
52
+ return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
53
+ }
44
54
  };
45
55
  RBSegmentsCachePluggable.prototype.getChangeNumber = function () {
46
56
  var _this = this;
@@ -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
  }