@splitsoftware/splitio-commons 1.9.1 → 1.9.2-rc.0

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 (130) hide show
  1. package/cjs/evaluator/index.js +18 -1
  2. package/cjs/logger/constants.js +7 -2
  3. package/cjs/logger/messages/error.js +2 -0
  4. package/cjs/logger/messages/warn.js +3 -1
  5. package/cjs/myLogger.js +34 -0
  6. package/cjs/sdkClient/client.js +33 -0
  7. package/cjs/sdkClient/clientAttributesDecoration.js +20 -0
  8. package/cjs/sdkClient/clientCS.js +4 -0
  9. package/cjs/sdkClient/clientInputValidation.js +52 -3
  10. package/cjs/sdkManager/index.js +2 -1
  11. package/cjs/services/splitApi.js +7 -1
  12. package/cjs/storages/KeyBuilder.js +3 -0
  13. package/cjs/storages/KeyBuilderSS.js +4 -0
  14. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +63 -27
  15. package/cjs/storages/inLocalStorage/index.js +2 -2
  16. package/cjs/storages/inMemory/InMemoryStorage.js +2 -2
  17. package/cjs/storages/inMemory/InMemoryStorageCS.js +3 -3
  18. package/cjs/storages/inMemory/SplitsCacheInMemory.js +47 -2
  19. package/cjs/storages/inRedis/SplitsCacheInRedis.js +11 -0
  20. package/cjs/storages/pluggable/SplitsCachePluggable.js +11 -0
  21. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  22. package/cjs/sync/polling/updaters/splitChangesUpdater.js +24 -4
  23. package/cjs/sync/submitters/telemetrySubmitter.js +15 -1
  24. package/cjs/utils/constants/index.js +6 -2
  25. package/cjs/utils/lang/sets.js +9 -1
  26. package/cjs/utils/settingsValidation/splitFilters.js +77 -2
  27. package/esm/evaluator/index.js +16 -0
  28. package/esm/logger/constants.js +5 -0
  29. package/esm/logger/messages/error.js +2 -0
  30. package/esm/logger/messages/warn.js +3 -1
  31. package/esm/myLogger.js +31 -0
  32. package/esm/sdkClient/client.js +35 -2
  33. package/esm/sdkClient/clientAttributesDecoration.js +20 -0
  34. package/esm/sdkClient/clientCS.js +4 -0
  35. package/esm/sdkClient/clientInputValidation.js +52 -3
  36. package/esm/sdkManager/index.js +2 -1
  37. package/esm/services/splitApi.js +7 -1
  38. package/esm/storages/KeyBuilder.js +3 -0
  39. package/esm/storages/KeyBuilderSS.js +4 -0
  40. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +63 -27
  41. package/esm/storages/inLocalStorage/index.js +2 -2
  42. package/esm/storages/inMemory/InMemoryStorage.js +2 -2
  43. package/esm/storages/inMemory/InMemoryStorageCS.js +3 -3
  44. package/esm/storages/inMemory/SplitsCacheInMemory.js +47 -2
  45. package/esm/storages/inRedis/SplitsCacheInRedis.js +11 -0
  46. package/esm/storages/pluggable/SplitsCachePluggable.js +11 -0
  47. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  48. package/esm/sync/polling/updaters/splitChangesUpdater.js +24 -4
  49. package/esm/sync/submitters/telemetrySubmitter.js +15 -1
  50. package/esm/utils/constants/index.js +4 -0
  51. package/esm/utils/lang/sets.js +7 -0
  52. package/esm/utils/settingsValidation/splitFilters.js +76 -2
  53. package/package.json +2 -2
  54. package/src/dtos/types.ts +3 -2
  55. package/src/evaluator/index.ts +24 -0
  56. package/src/logger/constants.ts +5 -0
  57. package/src/logger/messages/error.ts +2 -0
  58. package/src/logger/messages/warn.ts +3 -1
  59. package/src/myLogger.ts +36 -0
  60. package/src/sdkClient/client.ts +42 -2
  61. package/src/sdkClient/clientAttributesDecoration.ts +24 -0
  62. package/src/sdkClient/clientCS.ts +4 -0
  63. package/src/sdkClient/clientInputValidation.ts +56 -4
  64. package/src/sdkManager/index.ts +2 -1
  65. package/src/services/splitApi.ts +6 -1
  66. package/src/storages/AbstractSplitsCacheAsync.ts +2 -0
  67. package/src/storages/AbstractSplitsCacheSync.ts +3 -0
  68. package/src/storages/KeyBuilder.ts +4 -0
  69. package/src/storages/KeyBuilderSS.ts +4 -0
  70. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +74 -28
  71. package/src/storages/inLocalStorage/index.ts +2 -2
  72. package/src/storages/inMemory/InMemoryStorage.ts +2 -2
  73. package/src/storages/inMemory/InMemoryStorageCS.ts +3 -3
  74. package/src/storages/inMemory/SplitsCacheInMemory.ts +50 -1
  75. package/src/storages/inRedis/SplitsCacheInRedis.ts +12 -0
  76. package/src/storages/pluggable/SplitsCachePluggable.ts +12 -0
  77. package/src/storages/types.ts +7 -3
  78. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -0
  79. package/src/sync/polling/updaters/splitChangesUpdater.ts +27 -4
  80. package/src/sync/submitters/telemetrySubmitter.ts +19 -2
  81. package/src/sync/submitters/types.ts +7 -1
  82. package/src/types.ts +118 -1
  83. package/src/utils/constants/index.ts +4 -0
  84. package/src/utils/lang/sets.ts +8 -0
  85. package/src/utils/settingsValidation/splitFilters.ts +82 -2
  86. package/types/dtos/types.d.ts +1 -0
  87. package/types/evaluator/index.d.ts +1 -0
  88. package/types/logger/constants.d.ts +5 -0
  89. package/types/myLogger.d.ts +5 -0
  90. package/types/sdkClient/clientAttributesDecoration.d.ts +4 -0
  91. package/types/sdkClient/types.d.ts +18 -0
  92. package/types/storages/AbstractSplitsCacheAsync.d.ts +2 -0
  93. package/types/storages/AbstractSplitsCacheSync.d.ts +2 -0
  94. package/types/storages/KeyBuilder.d.ts +1 -0
  95. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +6 -1
  96. package/types/storages/inMemory/CountsCacheInMemory.d.ts +20 -0
  97. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +20 -0
  98. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +9 -1
  99. package/types/storages/inRedis/CountsCacheInRedis.d.ts +9 -0
  100. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +9 -0
  101. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +8 -0
  102. package/types/storages/metadataBuilder.d.ts +3 -0
  103. package/types/storages/pluggable/SplitsCachePluggable.d.ts +8 -0
  104. package/types/storages/types.d.ts +4 -0
  105. package/types/sync/offline/LocalhostFromFile.d.ts +2 -0
  106. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +2 -0
  107. package/types/sync/offline/updaters/splitChangesUpdater.d.ts +0 -0
  108. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +3 -3
  109. package/types/sync/submitters/eventsSyncTask.d.ts +8 -0
  110. package/types/sync/submitters/impressionCountsSubmitterInRedis.d.ts +5 -0
  111. package/types/sync/submitters/impressionCountsSyncTask.d.ts +13 -0
  112. package/types/sync/submitters/impressionsSyncTask.d.ts +14 -0
  113. package/types/sync/submitters/metricsSyncTask.d.ts +12 -0
  114. package/types/sync/submitters/submitterSyncTask.d.ts +10 -0
  115. package/types/sync/submitters/types.d.ts +7 -1
  116. package/types/sync/submitters/uniqueKeysSubmitterInRedis.d.ts +5 -0
  117. package/types/sync/syncTaskComposite.d.ts +5 -0
  118. package/types/trackers/filter/bloomFilter.d.ts +10 -0
  119. package/types/trackers/filter/dictionaryFilter.d.ts +8 -0
  120. package/types/trackers/filter/types.d.ts +5 -0
  121. package/types/types.d.ts +118 -1
  122. package/types/utils/constants/index.d.ts +4 -0
  123. package/types/utils/lang/sets.d.ts +1 -0
  124. package/types/utils/settingsValidation/splitFilters.d.ts +1 -0
  125. package/types/utils/timeTracker/index.d.ts +70 -0
  126. package/types/sdkClient/identity.d.ts +0 -6
  127. package/types/utils/inputValidation/sdkKey.d.ts +0 -7
  128. /package/types/storages/inMemory/{UniqueKeysCacheInMemory.d.ts → uniqueKeysCacheInMemory.d.ts} +0 -0
  129. /package/types/storages/inMemory/{UniqueKeysCacheInMemoryCS.d.ts → uniqueKeysCacheInMemoryCS.d.ts} +0 -0
  130. /package/types/storages/inRedis/{UniqueKeysCacheInRedis.d.ts → uniqueKeysCacheInRedis.d.ts} +0 -0
@@ -4,6 +4,7 @@ import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
4
4
  import { KeyBuilderCS } from '../KeyBuilderCS';
5
5
  import { ILogger } from '../../logger/types';
6
6
  import { LOG_PREFIX } from './constants';
7
+ import { ISet, _Set, returnSetsUnion, setToArray } from '../../utils/lang/sets';
7
8
 
8
9
  /**
9
10
  * ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
@@ -12,8 +13,8 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
12
13
 
13
14
  private readonly keys: KeyBuilderCS;
14
15
  private readonly splitFiltersValidation: ISplitFiltersValidation;
16
+ private readonly flagSetsFilter: string[];
15
17
  private hasSync?: boolean;
16
- private cacheReadyButNeedsToFlush: boolean = false;
17
18
  private updateNewFilter?: boolean;
18
19
 
19
20
  /**
@@ -21,10 +22,11 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
21
22
  * @param {number | undefined} expirationTimestamp
22
23
  * @param {ISplitFiltersValidation} splitFiltersValidation
23
24
  */
24
- constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { byName: [], byPrefix: [] }, validFilters: [] }) {
25
+ constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) {
25
26
  super();
26
27
  this.keys = keys;
27
28
  this.splitFiltersValidation = splitFiltersValidation;
29
+ this.flagSetsFilter = this.splitFiltersValidation.groupedFilters.bySet;
28
30
 
29
31
  this._checkExpiration(expirationTimestamp);
30
32
 
@@ -106,6 +108,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
106
108
  this._incrementCounts(split);
107
109
  this._decrementCounts(previousSplit);
108
110
 
111
+ if (previousSplit) this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
112
+ this.addToFlagSets(split);
113
+
109
114
  return true;
110
115
  } catch (e) {
111
116
  this.log.error(LOG_PREFIX + e);
@@ -119,6 +124,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
119
124
  localStorage.removeItem(this.keys.buildSplitKey(name));
120
125
 
121
126
  this._decrementCounts(split);
127
+ if (split) this.removeFromFlagSets(split.name, split.sets);
122
128
 
123
129
  return true;
124
130
  } catch (e) {
@@ -133,11 +139,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
133
139
  }
134
140
 
135
141
  setChangeNumber(changeNumber: number): boolean {
136
- // when cache is ready but using a new split query, we must clear all split data
137
- if (this.cacheReadyButNeedsToFlush) {
138
- this.clear();
139
- this.cacheReadyButNeedsToFlush = false;
140
- }
141
142
 
142
143
  // when using a new split query, we must update it at the store
143
144
  if (this.updateNewFilter) {
@@ -220,7 +221,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
220
221
  * @override
221
222
  */
222
223
  checkCache(): boolean {
223
- return this.getChangeNumber() > -1 || this.cacheReadyButNeedsToFlush;
224
+ return this.getChangeNumber() > -1;
224
225
  }
225
226
 
226
227
  /**
@@ -237,7 +238,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
237
238
  }
238
239
 
239
240
  private _checkFilterQuery() {
240
- const { queryString, groupedFilters } = this.splitFiltersValidation;
241
+ const { queryString } = this.splitFiltersValidation;
241
242
  const queryKey = this.keys.buildSplitsFilterQueryKey();
242
243
  const currentQueryString = localStorage.getItem(queryKey);
243
244
 
@@ -246,29 +247,74 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
246
247
  // mark cache to update the new query filter on first successful splits fetch
247
248
  this.updateNewFilter = true;
248
249
 
249
- // if cache is ready:
250
- if (this.checkCache()) {
251
- // * set change number to -1, to fetch splits with -1 `since` value.
252
- localStorage.setItem(this.keys.buildSplitsTillKey(), '-1');
253
-
254
- // * remove from cache splits that doesn't match with the new filters
255
- this.getSplitNames().forEach((splitName) => {
256
- if (queryString && (
257
- // @TODO consider redefining `groupedFilters` to expose a method like `groupedFilters::filter(splitName): boolean`
258
- groupedFilters.byName.indexOf(splitName) > -1 ||
259
- groupedFilters.byPrefix.some((prefix: string) => splitName.startsWith(prefix + '__'))
260
- )) {
261
- // * set `cacheReadyButNeedsToFlush` so that `checkCache` returns true (the storage is ready to be used) and the data is cleared before updating on first successful splits fetch
262
- this.cacheReadyButNeedsToFlush = true;
263
- return;
264
- }
265
- this.removeSplit(splitName);
266
- });
267
- }
250
+ // if there is cache, clear it
251
+ if (this.checkCache()) this.clear();
252
+
268
253
  } catch (e) {
269
254
  this.log.error(LOG_PREFIX + e);
270
255
  }
271
256
  }
272
257
  // if the filter didn't change, nothing is done
273
258
  }
259
+
260
+ getNamesByFlagSets(flagSets: string[]): ISet<string>{
261
+ let toReturn: ISet<string> = new _Set([]);
262
+ flagSets.forEach(flagSet => {
263
+ const flagSetKey = this.keys.buildFlagSetKey(flagSet);
264
+ let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
265
+
266
+ if (flagSetFromLocalStorage) {
267
+ const flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
268
+ toReturn = returnSetsUnion(toReturn, flagSetCache);
269
+ }
270
+ });
271
+ return toReturn;
272
+
273
+ }
274
+
275
+ private addToFlagSets(featureFlag: ISplit) {
276
+ if (!featureFlag.sets) return;
277
+
278
+ featureFlag.sets.forEach(featureFlagSet => {
279
+
280
+ if (this.flagSetsFilter.length > 0 && !this.flagSetsFilter.some(filterFlagSet => filterFlagSet === featureFlagSet)) return;
281
+
282
+ const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
283
+
284
+ let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
285
+ if (!flagSetFromLocalStorage) flagSetFromLocalStorage = '[]';
286
+
287
+ const flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
288
+ flagSetCache.add(featureFlag.name);
289
+
290
+ localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
291
+ });
292
+ }
293
+
294
+ private removeFromFlagSets(featureFlagName: string, flagSets?: string[]) {
295
+ if (!flagSets) return;
296
+
297
+ flagSets.forEach(flagSet => {
298
+ this.removeNames(flagSet, featureFlagName);
299
+ });
300
+ }
301
+
302
+ private removeNames(flagSetName: string, featureFlagName: string) {
303
+ const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
304
+
305
+ let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
306
+
307
+ if (!flagSetFromLocalStorage) return;
308
+
309
+ const flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
310
+ flagSetCache.delete(featureFlagName);
311
+
312
+ if (flagSetCache.size === 0) {
313
+ localStorage.removeItem(flagSetKey);
314
+ return;
315
+ }
316
+
317
+ localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
318
+ }
319
+
274
320
  }
@@ -54,7 +54,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
54
54
  uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
55
55
 
56
56
  destroy() {
57
- this.splits = new SplitsCacheInMemory();
57
+ this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
58
58
  this.segments = new MySegmentsCacheInMemory();
59
59
  this.impressions.clear();
60
60
  this.impressionCounts && this.impressionCounts.clear();
@@ -75,7 +75,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
75
75
  telemetry: this.telemetry,
76
76
 
77
77
  destroy() {
78
- this.splits = new SplitsCacheInMemory();
78
+ this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
79
79
  this.segments = new MySegmentsCacheInMemory();
80
80
  }
81
81
  };
@@ -14,9 +14,9 @@ import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
14
14
  * @param params parameters required by EventsCacheSync
15
15
  */
16
16
  export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync {
17
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
18
18
 
19
- const splits = new SplitsCacheInMemory();
19
+ const splits = new SplitsCacheInMemory(__splitFiltersValidation);
20
20
  const segments = new SegmentsCacheInMemory();
21
21
 
22
22
  const storage = {
@@ -14,9 +14,9 @@ import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
14
14
  * @param params parameters required by EventsCacheSync
15
15
  */
16
16
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
17
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
18
18
 
19
- const splits = new SplitsCacheInMemory();
19
+ const splits = new SplitsCacheInMemory(__splitFiltersValidation);
20
20
  const segments = new MySegmentsCacheInMemory();
21
21
 
22
22
  const storage = {
@@ -50,7 +50,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
50
50
 
51
51
  // Set a new splits cache to clean it for the client without affecting other clients
52
52
  destroy() {
53
- this.splits = new SplitsCacheInMemory();
53
+ this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
54
54
  this.segments.clear();
55
55
  }
56
56
  };
@@ -1,6 +1,7 @@
1
- import { ISplit } from '../../dtos/types';
1
+ import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
2
2
  import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
3
3
  import { isFiniteNumber } from '../../utils/lang';
4
+ import { ISet, _Set, returnSetsUnion } from '../../utils/lang/sets';
4
5
 
5
6
  /**
6
7
  * Default ISplitsCacheSync implementation that stores split definitions in memory.
@@ -8,10 +9,17 @@ import { isFiniteNumber } from '../../utils/lang';
8
9
  */
9
10
  export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
10
11
 
12
+ private flagSetsFilter: string[];
11
13
  private splitsCache: Record<string, ISplit> = {};
12
14
  private ttCache: Record<string, number> = {};
13
15
  private changeNumber: number = -1;
14
16
  private splitsWithSegmentsCount: number = 0;
17
+ private flagSetsCache: Record<string, ISet<string>> = {};
18
+
19
+ constructor(splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) {
20
+ super();
21
+ this.flagSetsFilter = splitFiltersValidation.groupedFilters.bySet;
22
+ }
15
23
 
16
24
  clear() {
17
25
  this.splitsCache = {};
@@ -28,6 +36,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
28
36
  this.ttCache[previousTtName]--;
29
37
  if (!this.ttCache[previousTtName]) delete this.ttCache[previousTtName];
30
38
 
39
+ this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
40
+
31
41
  if (usesSegments(previousSplit)) { // Substract from segments count for the previous version of this Split.
32
42
  this.splitsWithSegmentsCount--;
33
43
  }
@@ -39,6 +49,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
39
49
  // Update TT cache
40
50
  const ttName = split.trafficTypeName;
41
51
  this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
52
+ this.addToFlagSets(split);
42
53
 
43
54
  // Add to segments count for the new version of the Split
44
55
  if (usesSegments(split)) this.splitsWithSegmentsCount++;
@@ -58,6 +69,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
58
69
  const ttName = split.trafficTypeName;
59
70
  this.ttCache[ttName]--; // Update tt cache
60
71
  if (!this.ttCache[ttName]) delete this.ttCache[ttName];
72
+ this.removeFromFlagSets(split.name, split.sets);
61
73
 
62
74
  // Update the segments count.
63
75
  if (usesSegments(split)) this.splitsWithSegmentsCount--;
@@ -93,4 +105,41 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
93
105
  return this.getChangeNumber() === -1 || this.splitsWithSegmentsCount > 0;
94
106
  }
95
107
 
108
+ getNamesByFlagSets(flagSets: string[]): ISet<string>{
109
+ let toReturn: ISet<string> = new _Set([]);
110
+ flagSets.forEach(flagSet => {
111
+ const featureFlagNames = this.flagSetsCache[flagSet];
112
+ if (featureFlagNames) {
113
+ toReturn = returnSetsUnion(toReturn, featureFlagNames);
114
+ }
115
+ });
116
+ return toReturn;
117
+
118
+ }
119
+
120
+ private addToFlagSets(featureFlag: ISplit) {
121
+ if (!featureFlag.sets) return;
122
+ featureFlag.sets.forEach(featureFlagSet => {
123
+
124
+ if (this.flagSetsFilter.length > 0 && !this.flagSetsFilter.some(filterFlagSet => filterFlagSet === featureFlagSet)) return;
125
+
126
+ if (!this.flagSetsCache[featureFlagSet]) this.flagSetsCache[featureFlagSet] = new _Set([]);
127
+
128
+ this.flagSetsCache[featureFlagSet].add(featureFlag.name);
129
+ });
130
+ }
131
+
132
+ private removeFromFlagSets(featureFlagName :string, flagSets: string[] | undefined) {
133
+ if (!flagSets) return;
134
+ flagSets.forEach(flagSet => {
135
+ this.removeNames(flagSet, featureFlagName);
136
+ });
137
+ }
138
+
139
+ private removeNames(flagSetName: string, featureFlagName: string) {
140
+ if (!this.flagSetsCache[flagSetName]) return;
141
+ this.flagSetsCache[flagSetName].delete(featureFlagName);
142
+ if (this.flagSetsCache[flagSetName].size === 0) delete this.flagSetsCache[flagSetName];
143
+ }
144
+
96
145
  }
@@ -5,6 +5,7 @@ import { ILogger } from '../../logger/types';
5
5
  import { LOG_PREFIX } from './constants';
6
6
  import { ISplit } from '../../dtos/types';
7
7
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
8
+ import { ISet, _Set } from '../../utils/lang/sets';
8
9
 
9
10
  /**
10
11
  * Discard errors for an answer of multiple operations.
@@ -188,6 +189,17 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
188
189
  );
189
190
  }
190
191
 
192
+ /**
193
+ * Get list of split names related to a given flag set names list.
194
+ * The returned promise is resolved with the list of split names,
195
+ * or rejected if wrapper operation fails.
196
+ * @todo this is a no-op method to be implemented
197
+ */
198
+ getNamesByFlagSets(): Promise<ISet<string>> {
199
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
200
+ return new Promise(flagSets => new _Set([]));
201
+ }
202
+
191
203
  /**
192
204
  * Check traffic type existence.
193
205
  * The returned promise is resolved with a boolean indicating whether the TT exist or not.
@@ -5,6 +5,7 @@ import { ILogger } from '../../logger/types';
5
5
  import { ISplit } from '../../dtos/types';
6
6
  import { LOG_PREFIX } from './constants';
7
7
  import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
8
+ import { ISet, _Set } from '../../utils/lang/sets';
8
9
 
9
10
  /**
10
11
  * ISplitsCacheAsync implementation for pluggable storages.
@@ -154,6 +155,17 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
154
155
  );
155
156
  }
156
157
 
158
+ /**
159
+ * Get list of split names related to a given flag set names list.
160
+ * The returned promise is resolved with the list of split names,
161
+ * or rejected if wrapper operation fails.
162
+ * @todo this is a no-op method to be implemented
163
+ */
164
+ getNamesByFlagSets(): Promise<ISet<string>> {
165
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
166
+ return new Promise(flagSets => new _Set([]));
167
+ }
168
+
157
169
  /**
158
170
  * Check traffic type existence.
159
171
  * The returned promise is resolved with a boolean indicating whether the TT exist or not.
@@ -1,6 +1,7 @@
1
1
  import { MaybeThenable, ISplit } from '../dtos/types';
2
2
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
3
3
  import { SplitIO, ImpressionDTO, ISettings } from '../types';
4
+ import { ISet } from '../utils/lang/sets';
4
5
 
5
6
  /**
6
7
  * Interface of a pluggable storage wrapper.
@@ -208,7 +209,8 @@ export interface ISplitsCacheBase {
208
209
  clear(): MaybeThenable<boolean | void>,
209
210
  // should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
210
211
  checkCache(): MaybeThenable<boolean>,
211
- killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>
212
+ killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
213
+ getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>>
212
214
  }
213
215
 
214
216
  export interface ISplitsCacheSync extends ISplitsCacheBase {
@@ -224,7 +226,8 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
224
226
  usesSegments(): boolean,
225
227
  clear(): void,
226
228
  checkCache(): boolean,
227
- killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean
229
+ killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
230
+ getNamesByFlagSets(flagSets: string[]): ISet<string>
228
231
  }
229
232
 
230
233
  export interface ISplitsCacheAsync extends ISplitsCacheBase {
@@ -240,7 +243,8 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
240
243
  usesSegments(): Promise<boolean>,
241
244
  clear(): Promise<boolean | void>,
242
245
  checkCache(): Promise<boolean>,
243
- killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>
246
+ killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
247
+ getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>>
244
248
  }
245
249
 
246
250
  /** Segments cache */
@@ -24,6 +24,7 @@ export function splitsSyncTaskFactory(
24
24
  splitChangesFetcherFactory(fetchSplitChanges),
25
25
  storage.splits,
26
26
  storage.segments,
27
+ settings.sync.__splitFiltersValidation,
27
28
  readiness.splits,
28
29
  settings.startup.requestTimeoutBeforeReady,
29
30
  settings.startup.retriesOnFailureBeforeReady,
@@ -1,12 +1,13 @@
1
1
  import { _Set, setToArray, ISet } from '../../../utils/lang/sets';
2
2
  import { ISegmentsCacheBase, ISplitsCacheBase } from '../../../storages/types';
3
3
  import { ISplitChangesFetcher } from '../fetchers/types';
4
- import { ISplit, ISplitChangesResponse } from '../../../dtos/types';
4
+ import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
5
5
  import { ISplitsEventEmitter } from '../../../readiness/types';
6
6
  import { timeout } from '../../../utils/promise/timeout';
7
7
  import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
8
8
  import { ILogger } from '../../../logger/types';
9
9
  import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
10
+ import { startsWith } from '../../../utils/lang';
10
11
 
11
12
  type ISplitChangesUpdater = (noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }) => Promise<boolean>
12
13
 
@@ -45,15 +46,36 @@ interface ISplitMutations {
45
46
  segments: string[]
46
47
  }
47
48
 
49
+ /**
50
+ * If there are defined filters and one feature flag doesn't match with them, its status is changed to 'ARCHIVE' to avoid storing it
51
+ * If there are set filter defined, names filter is ignored
52
+ *
53
+ * @param featureFlag feature flag to be evaluated
54
+ * @param filters splitFiltersValidation bySet | byName
55
+ */
56
+ function matchFilters(featureFlag: ISplit, filters: ISplitFiltersValidation) {
57
+ const { bySet: setsFilter, byName: namesFilter, byPrefix: prefixFilter} = filters.groupedFilters;
58
+ if (setsFilter.length > 0) return featureFlag.sets && featureFlag.sets.some((featureFlagSet: string) => setsFilter.indexOf(featureFlagSet) > -1);
59
+
60
+ const namesFilterConfigured = namesFilter.length > 0;
61
+ const prefixFilterConfigured = prefixFilter.length > 0;
62
+
63
+ if (!namesFilterConfigured && !prefixFilterConfigured) return true;
64
+
65
+ const matchNames = namesFilterConfigured && namesFilter.indexOf(featureFlag.name) > -1;
66
+ const matchPrefix = prefixFilterConfigured && prefixFilter.some(prefix => startsWith(featureFlag.name, prefix));
67
+ return matchNames || matchPrefix;
68
+ }
69
+
48
70
  /**
49
71
  * Given the list of splits from /splitChanges endpoint, it returns the mutations,
50
72
  * i.e., an object with added splits, removed splits and used segments.
51
73
  * Exported for testing purposes.
52
74
  */
53
- export function computeSplitsMutation(entries: ISplit[]): ISplitMutations {
75
+ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersValidation): ISplitMutations {
54
76
  const segments = new _Set<string>();
55
77
  const computed = entries.reduce((accum, split) => {
56
- if (split.status === 'ACTIVE') {
78
+ if (split.status === 'ACTIVE' && matchFilters(split, filters)) {
57
79
  accum.added.push([split.name, split]);
58
80
 
59
81
  parseSegments(split).forEach((segmentName: string) => {
@@ -90,6 +112,7 @@ export function splitChangesUpdaterFactory(
90
112
  splitChangesFetcher: ISplitChangesFetcher,
91
113
  splits: ISplitsCacheBase,
92
114
  segments: ISegmentsCacheBase,
115
+ splitFiltersValidation: ISplitFiltersValidation,
93
116
  splitsEventEmitter?: ISplitsEventEmitter,
94
117
  requestTimeoutBeforeReady: number = 0,
95
118
  retriesOnFailureBeforeReady: number = 0,
@@ -126,7 +149,7 @@ export function splitChangesUpdaterFactory(
126
149
  .then((splitChanges: ISplitChangesResponse) => {
127
150
  startingUp = false;
128
151
 
129
- const mutation = computeSplitsMutation(splitChanges.splits);
152
+ const mutation = computeSplitsMutation(splitChanges.splits, splitFiltersValidation);
130
153
 
131
154
  log.debug(SYNC_SPLITS_NEW, [mutation.added.length]);
132
155
  log.debug(SYNC_SPLITS_REMOVED, [mutation.removed.length]);
@@ -3,12 +3,13 @@ import { submitterFactory, firstPushWindowDecorator } from './submitter';
3
3
  import { TelemetryConfigStatsPayload, TelemetryConfigStats } from './types';
4
4
  import { CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, NONE, DEBUG_ENUM, OPTIMIZED_ENUM, NONE_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
5
5
  import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
6
- import { ConsentStatus, ISettings, SDKMode } from '../../types';
6
+ import { ConsentStatus, ISettings, SDKMode, SplitIO } from '../../types';
7
7
  import { base } from '../../utils/settingsValidation';
8
8
  import { usedKeysMap } from '../../utils/inputValidation/apiKey';
9
9
  import { timer } from '../../utils/timeTracker/timer';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
11
  import { objectAssign } from '../../utils/lang/objectAssign';
12
+ import { ISplitFiltersValidation } from '../../dtos/types';
12
13
 
13
14
  const OPERATION_MODE_MAP = {
14
15
  [STANDALONE_MODE]: STANDALONE_ENUM,
@@ -38,6 +39,18 @@ function getRedundantActiveFactories() {
38
39
  }, 0);
39
40
  }
40
41
 
42
+ function getTelemetryFlagSetsStats(splitFiltersValidation: ISplitFiltersValidation) {
43
+ // Group every configured flag set in an unique array called originalSets
44
+ let flagSetsTotal = 0;
45
+ splitFiltersValidation.validFilters.forEach((filter: SplitIO.SplitFilter) => {
46
+ if (filter.type === 'bySet') flagSetsTotal += filter.values.length;
47
+ });
48
+
49
+ const flagSetsValid = splitFiltersValidation.groupedFilters.bySet.length;
50
+ const flagSetsIgnored = flagSetsTotal - flagSetsValid;
51
+ return { flagSetsTotal, flagSetsIgnored };
52
+ }
53
+
41
54
  export function getTelemetryConfigStats(mode: SDKMode, storageType: string): TelemetryConfigStats {
42
55
  return {
43
56
  oM: OPERATION_MODE_MAP[mode], // @ts-ignore lower case of storage type
@@ -59,6 +72,8 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
59
72
  const { urls, scheduler } = settings;
60
73
  const isClientSide = settings.core.key !== undefined;
61
74
 
75
+ const { flagSetsTotal, flagSetsIgnored } = getTelemetryFlagSetsStats(settings.sync.__splitFiltersValidation);
76
+
62
77
  return objectAssign(getTelemetryConfigStats(settings.mode, settings.storage.type), {
63
78
  sE: settings.streamingEnabled,
64
79
  rR: {
@@ -86,7 +101,9 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
86
101
  nR: telemetry.getNonReadyUsage(),
87
102
  t: telemetry.popTags(),
88
103
  i: settings.integrations && settings.integrations.map(int => int.type),
89
- uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0
104
+ uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0,
105
+ fsT: flagSetsTotal,
106
+ fsI: flagSetsIgnored
90
107
  });
91
108
  }
92
109
  };
@@ -124,7 +124,11 @@ export type TREATMENTS = 'ts';
124
124
  export type TREATMENT_WITH_CONFIG = 'tc';
125
125
  export type TREATMENTS_WITH_CONFIG = 'tcs';
126
126
  export type TRACK = 'tr';
127
- export type Method = TREATMENT | TREATMENTS | TREATMENT_WITH_CONFIG | TREATMENTS_WITH_CONFIG | TRACK;
127
+ export type TREATMENTS_BY_FLAGSET = 'tf'
128
+ export type TREATMENTS_BY_FLAGSETS = 'tfs'
129
+ export type TREATMENTS_WITH_CONFIG_BY_FLAGSET = 'tcf'
130
+ export type TREATMENTS_WITH_CONFIG_BY_FLAGSETS = 'tcfs'
131
+ export type Method = TREATMENT | TREATMENTS | TREATMENT_WITH_CONFIG | TREATMENTS_WITH_CONFIG | TRACK | TREATMENTS_BY_FLAGSET | TREATMENTS_BY_FLAGSETS | TREATMENTS_WITH_CONFIG_BY_FLAGSET | TREATMENTS_WITH_CONFIG_BY_FLAGSETS;
128
132
 
129
133
  export type MethodLatencies = Partial<Record<Method, Array<number>>>;
130
134
 
@@ -234,6 +238,8 @@ export type TelemetryConfigStatsPayload = TelemetryConfigStats & {
234
238
  nR: number, // SDKNotReadyUsage
235
239
  i?: Array<string>, // integrations
236
240
  uC: number, // userConsent
241
+ fsT: number, // flagSetsTotal
242
+ fsI: number, // flagSetsInvalid
237
243
  }
238
244
 
239
245
  export interface ISubmitterManager extends ISyncTask {