@splitsoftware/splitio-commons 2.1.0-rc.2 → 2.1.1-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 (174) hide show
  1. package/CHANGES.txt +2 -7
  2. package/README.md +1 -0
  3. package/cjs/evaluator/combiners/and.js +2 -6
  4. package/cjs/evaluator/combiners/ifelseif.js +6 -6
  5. package/cjs/evaluator/condition/index.js +6 -5
  6. package/cjs/evaluator/index.js +7 -7
  7. package/cjs/evaluator/matchers/index.js +3 -1
  8. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  9. package/cjs/evaluator/matchers/rbsegment.js +43 -0
  10. package/cjs/evaluator/matchersTransform/index.js +4 -0
  11. package/cjs/evaluator/parser/index.js +2 -2
  12. package/cjs/evaluator/value/sanitize.js +1 -0
  13. package/cjs/logger/constants.js +5 -6
  14. package/cjs/logger/messages/debug.js +3 -4
  15. package/cjs/logger/messages/warn.js +1 -1
  16. package/cjs/readiness/readinessManager.js +0 -6
  17. package/cjs/services/splitApi.js +2 -2
  18. package/cjs/storages/AbstractSplitsCacheAsync.js +19 -1
  19. package/cjs/storages/AbstractSplitsCacheSync.js +17 -9
  20. package/cjs/storages/KeyBuilder.js +8 -15
  21. package/cjs/storages/KeyBuilderCS.js +11 -5
  22. package/cjs/storages/KeyBuilderSS.js +3 -0
  23. package/cjs/storages/dataLoader.js +3 -5
  24. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
  25. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +69 -15
  26. package/cjs/storages/inLocalStorage/index.js +7 -5
  27. package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
  28. package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
  29. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
  30. package/cjs/storages/inMemory/SplitsCacheInMemory.js +24 -31
  31. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
  32. package/cjs/storages/inRedis/SplitsCacheInRedis.js +4 -21
  33. package/cjs/storages/inRedis/constants.js +1 -1
  34. package/cjs/storages/inRedis/index.js +2 -0
  35. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
  36. package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -19
  37. package/cjs/storages/pluggable/index.js +3 -2
  38. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +14 -16
  39. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  40. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  41. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  42. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  43. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  44. package/cjs/sync/polling/updaters/splitChangesUpdater.js +62 -51
  45. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  46. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
  47. package/cjs/sync/streaming/constants.js +2 -1
  48. package/cjs/sync/streaming/pushManager.js +3 -16
  49. package/cjs/sync/syncManagerOnline.js +5 -10
  50. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  51. package/cjs/utils/constants/browser.js +5 -0
  52. package/cjs/utils/constants/index.js +3 -2
  53. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  54. package/esm/evaluator/combiners/and.js +2 -6
  55. package/esm/evaluator/combiners/ifelseif.js +7 -7
  56. package/esm/evaluator/condition/index.js +6 -5
  57. package/esm/evaluator/index.js +7 -7
  58. package/esm/evaluator/matchers/index.js +3 -1
  59. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  60. package/esm/evaluator/matchers/rbsegment.js +39 -0
  61. package/esm/evaluator/matchersTransform/index.js +4 -0
  62. package/esm/evaluator/parser/index.js +2 -2
  63. package/esm/evaluator/value/sanitize.js +1 -0
  64. package/esm/logger/constants.js +2 -3
  65. package/esm/logger/messages/debug.js +3 -4
  66. package/esm/logger/messages/warn.js +1 -1
  67. package/esm/readiness/readinessManager.js +0 -6
  68. package/esm/services/splitApi.js +2 -2
  69. package/esm/storages/AbstractSplitsCacheAsync.js +19 -1
  70. package/esm/storages/AbstractSplitsCacheSync.js +17 -9
  71. package/esm/storages/KeyBuilder.js +8 -15
  72. package/esm/storages/KeyBuilderCS.js +11 -5
  73. package/esm/storages/KeyBuilderSS.js +3 -0
  74. package/esm/storages/dataLoader.js +2 -4
  75. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
  76. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +69 -15
  77. package/esm/storages/inLocalStorage/index.js +7 -5
  78. package/esm/storages/inMemory/InMemoryStorage.js +3 -0
  79. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
  80. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
  81. package/esm/storages/inMemory/SplitsCacheInMemory.js +24 -31
  82. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
  83. package/esm/storages/inRedis/SplitsCacheInRedis.js +4 -21
  84. package/esm/storages/inRedis/constants.js +1 -1
  85. package/esm/storages/inRedis/index.js +2 -0
  86. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
  87. package/esm/storages/pluggable/SplitsCachePluggable.js +2 -19
  88. package/esm/storages/pluggable/index.js +3 -2
  89. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +14 -16
  90. package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  91. package/esm/sync/polling/pollingManagerCS.js +7 -7
  92. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  93. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  94. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  95. package/esm/sync/polling/updaters/splitChangesUpdater.js +63 -52
  96. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  97. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
  98. package/esm/sync/streaming/constants.js +1 -0
  99. package/esm/sync/streaming/pushManager.js +6 -19
  100. package/esm/sync/syncManagerOnline.js +5 -10
  101. package/esm/trackers/uniqueKeysTracker.js +1 -1
  102. package/esm/utils/constants/browser.js +2 -0
  103. package/esm/utils/constants/index.js +2 -1
  104. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  105. package/package.json +1 -1
  106. package/src/dtos/types.ts +32 -8
  107. package/src/evaluator/Engine.ts +1 -1
  108. package/src/evaluator/combiners/and.ts +5 -4
  109. package/src/evaluator/combiners/ifelseif.ts +7 -9
  110. package/src/evaluator/condition/engineUtils.ts +1 -1
  111. package/src/evaluator/condition/index.ts +12 -12
  112. package/src/evaluator/index.ts +7 -7
  113. package/src/evaluator/matchers/index.ts +3 -1
  114. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  115. package/src/evaluator/matchers/rbsegment.ts +61 -0
  116. package/src/evaluator/matchersTransform/index.ts +3 -0
  117. package/src/evaluator/parser/index.ts +3 -3
  118. package/src/evaluator/types.ts +2 -2
  119. package/src/evaluator/value/index.ts +2 -2
  120. package/src/evaluator/value/sanitize.ts +5 -4
  121. package/src/logger/constants.ts +2 -3
  122. package/src/logger/messages/debug.ts +3 -4
  123. package/src/logger/messages/warn.ts +1 -1
  124. package/src/readiness/readinessManager.ts +0 -5
  125. package/src/sdkManager/index.ts +1 -1
  126. package/src/services/splitApi.ts +2 -2
  127. package/src/services/types.ts +1 -1
  128. package/src/storages/AbstractSplitsCacheAsync.ts +23 -5
  129. package/src/storages/AbstractSplitsCacheSync.ts +22 -15
  130. package/src/storages/KeyBuilder.ts +9 -17
  131. package/src/storages/KeyBuilderCS.ts +13 -6
  132. package/src/storages/KeyBuilderSS.ts +4 -0
  133. package/src/storages/dataLoader.ts +2 -5
  134. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
  135. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +80 -16
  136. package/src/storages/inLocalStorage/index.ts +12 -8
  137. package/src/storages/inMemory/InMemoryStorage.ts +3 -0
  138. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
  139. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
  140. package/src/storages/inMemory/SplitsCacheInMemory.ts +22 -27
  141. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
  142. package/src/storages/inRedis/SplitsCacheInRedis.ts +4 -21
  143. package/src/storages/inRedis/constants.ts +1 -1
  144. package/src/storages/inRedis/index.ts +2 -0
  145. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
  146. package/src/storages/pluggable/SplitsCachePluggable.ts +2 -19
  147. package/src/storages/pluggable/index.ts +3 -2
  148. package/src/storages/types.ts +47 -18
  149. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +19 -21
  150. package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
  151. package/src/sync/polling/fetchers/types.ts +1 -0
  152. package/src/sync/polling/pollingManagerCS.ts +7 -7
  153. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -2
  154. package/src/sync/polling/types.ts +2 -2
  155. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
  156. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  157. package/src/sync/polling/updaters/splitChangesUpdater.ts +74 -63
  158. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  159. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  160. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
  161. package/src/sync/streaming/constants.ts +1 -0
  162. package/src/sync/streaming/parseUtils.ts +2 -2
  163. package/src/sync/streaming/pushManager.ts +6 -18
  164. package/src/sync/streaming/types.ts +3 -2
  165. package/src/sync/syncManagerOnline.ts +5 -11
  166. package/src/trackers/uniqueKeysTracker.ts +1 -1
  167. package/src/utils/constants/browser.ts +2 -0
  168. package/src/utils/constants/index.ts +2 -1
  169. package/src/utils/lang/index.ts +2 -2
  170. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  171. package/types/splitio.d.ts +1 -25
  172. package/cjs/storages/inLocalStorage/validateCache.js +0 -79
  173. package/esm/storages/inLocalStorage/validateCache.js +0 -75
  174. package/src/storages/inLocalStorage/validateCache.ts +0 -91
@@ -0,0 +1,68 @@
1
+ import { IRBSegment } from '../../dtos/types';
2
+ import { setToArray } from '../../utils/lang/sets';
3
+ import { usesSegments } from '../AbstractSplitsCacheSync';
4
+ import { IRBSegmentsCacheSync } from '../types';
5
+
6
+ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
7
+
8
+ private cache: Record<string, IRBSegment> = {};
9
+ private changeNumber: number = -1;
10
+ private segmentsCount: number = 0;
11
+
12
+ clear() {
13
+ this.cache = {};
14
+ this.changeNumber = -1;
15
+ this.segmentsCount = 0;
16
+ }
17
+
18
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
19
+ this.changeNumber = changeNumber;
20
+ const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
21
+ return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
22
+ }
23
+
24
+ private add(rbSegment: IRBSegment): boolean {
25
+ const name = rbSegment.name;
26
+ const previous = this.get(name);
27
+ if (previous && usesSegments(previous)) this.segmentsCount--;
28
+
29
+ this.cache[name] = rbSegment;
30
+ if (usesSegments(rbSegment)) this.segmentsCount++;
31
+
32
+ return true;
33
+ }
34
+
35
+ private remove(name: string): boolean {
36
+ const rbSegment = this.get(name);
37
+ if (!rbSegment) return false;
38
+
39
+ delete this.cache[name];
40
+
41
+ if (usesSegments(rbSegment)) this.segmentsCount--;
42
+
43
+ return true;
44
+ }
45
+
46
+ private getNames(): string[] {
47
+ return Object.keys(this.cache);
48
+ }
49
+
50
+ get(name: string): IRBSegment | null {
51
+ return this.cache[name] || null;
52
+ }
53
+
54
+ contains(names: Set<string>): boolean {
55
+ const namesArray = setToArray(names);
56
+ const namesInStorage = this.getNames();
57
+ return namesArray.every(name => namesInStorage.indexOf(name) !== -1);
58
+ }
59
+
60
+ getChangeNumber(): number {
61
+ return this.changeNumber;
62
+ }
63
+
64
+ usesSegments(): boolean {
65
+ return this.segmentsCount > 0;
66
+ }
67
+
68
+ }
@@ -26,7 +26,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
26
26
  this.segmentsCount = 0;
27
27
  }
28
28
 
29
- addSplit(name: string, split: ISplit): boolean {
29
+ addSplit(split: ISplit): boolean {
30
+ const name = split.name;
30
31
  const previousSplit = this.getSplit(name);
31
32
  if (previousSplit) { // We had this Split already
32
33
 
@@ -40,41 +41,35 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
40
41
  if (usesSegments(previousSplit)) this.segmentsCount--;
41
42
  }
42
43
 
43
- if (split) {
44
- // Store the Split.
45
- this.splitsCache[name] = split;
46
- // Update TT cache
47
- const ttName = split.trafficTypeName;
48
- this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
49
- this.addToFlagSets(split);
44
+ // Store the Split.
45
+ this.splitsCache[name] = split;
46
+ // Update TT cache
47
+ const ttName = split.trafficTypeName;
48
+ this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
49
+ this.addToFlagSets(split);
50
50
 
51
- // Add to segments count for the new version of the Split
52
- if (usesSegments(split)) this.segmentsCount++;
51
+ // Add to segments count for the new version of the Split
52
+ if (usesSegments(split)) this.segmentsCount++;
53
53
 
54
- return true;
55
- } else {
56
- return false;
57
- }
54
+ return true;
58
55
  }
59
56
 
60
57
  removeSplit(name: string): boolean {
61
58
  const split = this.getSplit(name);
62
- if (split) {
63
- // Delete the Split
64
- delete this.splitsCache[name];
59
+ if (!split) return false;
65
60
 
66
- const ttName = split.trafficTypeName;
67
- this.ttCache[ttName]--; // Update tt cache
68
- if (!this.ttCache[ttName]) delete this.ttCache[ttName];
69
- this.removeFromFlagSets(split.name, split.sets);
61
+ // Delete the Split
62
+ delete this.splitsCache[name];
70
63
 
71
- // Update the segments count.
72
- if (usesSegments(split)) this.segmentsCount--;
64
+ const ttName = split.trafficTypeName;
65
+ this.ttCache[ttName]--; // Update tt cache
66
+ if (!this.ttCache[ttName]) delete this.ttCache[ttName];
67
+ this.removeFromFlagSets(split.name, split.sets);
73
68
 
74
- return true;
75
- } else {
76
- return false;
77
- }
69
+ // Update the segments count.
70
+ if (usesSegments(split)) this.segmentsCount--;
71
+
72
+ return true;
78
73
  }
79
74
 
80
75
  getSplit(name: string): ISplit | null {
@@ -0,0 +1,79 @@
1
+ import { isNaNNumber } from '../../utils/lang';
2
+ import { IRBSegmentsCacheAsync } from '../types';
3
+ import { ILogger } from '../../logger/types';
4
+ import { IRBSegment } from '../../dtos/types';
5
+ import { LOG_PREFIX } from './constants';
6
+ import { setToArray } from '../../utils/lang/sets';
7
+ import { RedisAdapter } from './RedisAdapter';
8
+ import { KeyBuilderSS } from '../KeyBuilderSS';
9
+
10
+ export class RBSegmentsCacheInRedis implements IRBSegmentsCacheAsync {
11
+
12
+ private readonly log: ILogger;
13
+ private readonly keys: KeyBuilderSS;
14
+ private readonly redis: RedisAdapter;
15
+
16
+ constructor(log: ILogger, keys: KeyBuilderSS, redis: RedisAdapter) {
17
+ this.log = log;
18
+ this.keys = keys;
19
+ this.redis = redis;
20
+ }
21
+
22
+ get(name: string): Promise<IRBSegment | null> {
23
+ return this.redis.get(this.keys.buildRBSegmentKey(name))
24
+ .then(maybeRBSegment => maybeRBSegment && JSON.parse(maybeRBSegment));
25
+ }
26
+
27
+ private getNames(): Promise<string[]> {
28
+ return this.redis.keys(this.keys.searchPatternForRBSegmentKeys()).then(
29
+ (listOfKeys) => listOfKeys.map(this.keys.extractKey)
30
+ );
31
+ }
32
+
33
+ contains(names: Set<string>): Promise<boolean> {
34
+ const namesArray = setToArray(names);
35
+ return this.getNames().then(namesInStorage => {
36
+ return namesArray.every(name => namesInStorage.includes(name));
37
+ });
38
+ }
39
+
40
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean> {
41
+ return Promise.all([
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.name);
50
+ return this.redis.del(key).then(status => status === 1);
51
+ }))
52
+ ]).then(([, added, removed]) => {
53
+ return added.some(result => result) || removed.some(result => result);
54
+ });
55
+ }
56
+
57
+ setChangeNumber(changeNumber: number) {
58
+ return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
59
+ status => status === 'OK'
60
+ );
61
+ }
62
+
63
+ getChangeNumber(): Promise<number> {
64
+ return this.redis.get(this.keys.buildRBSegmentsTillKey()).then((value: string | null) => {
65
+ const i = parseInt(value as string, 10);
66
+
67
+ return isNaNNumber(i) ? -1 : i;
68
+ }).catch((e) => {
69
+ this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from storage. Error: ' + e);
70
+ return -1;
71
+ });
72
+ }
73
+
74
+ // @TODO implement if required by DataLoader or producer mode
75
+ clear() {
76
+ return Promise.resolve();
77
+ }
78
+
79
+ }
@@ -82,7 +82,8 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
82
82
  * The returned promise is resolved when the operation success
83
83
  * or rejected if it fails (e.g., redis operation fails)
84
84
  */
85
- addSplit(name: string, split: ISplit): Promise<boolean> {
85
+ addSplit(split: ISplit): Promise<boolean> {
86
+ const name = split.name;
86
87
  const splitKey = this.keys.buildSplitKey(name);
87
88
  return this.redis.get(splitKey).then(splitFromStorage => {
88
89
 
@@ -107,18 +108,9 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
107
108
  }).then(() => true);
108
109
  }
109
110
 
110
- /**
111
- * Add a list of splits.
112
- * The returned promise is resolved when the operation success
113
- * or rejected if it fails (e.g., redis operation fails)
114
- */
115
- addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
116
- return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
117
- }
118
-
119
111
  /**
120
112
  * Remove a given split.
121
- * The returned promise is resolved when the operation success, with 1 or 0 indicating if the split existed or not.
113
+ * The returned promise is resolved when the operation success, with true or false indicating if the split existed (and was removed) or not.
122
114
  * or rejected if it fails (e.g., redis operation fails).
123
115
  */
124
116
  removeSplit(name: string) {
@@ -127,19 +119,10 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
127
119
  return this._decrementCounts(split).then(() => this._updateFlagSets(name, split.sets));
128
120
  }
129
121
  }).then(() => {
130
- return this.redis.del(this.keys.buildSplitKey(name));
122
+ return this.redis.del(this.keys.buildSplitKey(name)).then(status => status === 1);
131
123
  });
132
124
  }
133
125
 
134
- /**
135
- * Remove a list of splits.
136
- * The returned promise is resolved when the operation success,
137
- * or rejected if it fails (e.g., redis operation fails).
138
- */
139
- removeSplits(names: string[]): Promise<any> {
140
- return Promise.all(names.map(name => this.removeSplit(name)));
141
- }
142
-
143
126
  /**
144
127
  * Get split definition or null if it's not defined.
145
128
  * Returned promise is rejected if redis operation fails.
@@ -1,4 +1,4 @@
1
1
  export const LOG_PREFIX = 'storage:redis: ';
2
2
  export const DEFAULT_CACHE_SIZE = 30000;
3
- export const REFRESH_RATE = 300000; // 300.000 ms = start after 5 mins
3
+ export const REFRESH_RATE = 300000; // 300000 ms = start after 5 mins
4
4
  export const TTL_REFRESH = 3600; // 1hr
@@ -11,6 +11,7 @@ import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
11
11
  import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
12
12
  import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
13
13
  import { metadataBuilder } from '../utils';
14
+ import { RBSegmentsCacheInRedis } from './RBSegmentsCacheInRedis';
14
15
 
15
16
  export interface InRedisStorageOptions {
16
17
  prefix?: string
@@ -50,6 +51,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
50
51
 
51
52
  return {
52
53
  splits: new SplitsCacheInRedis(log, keys, redisClient, settings.sync.__splitFiltersValidation),
54
+ rbSegments: new RBSegmentsCacheInRedis(log, keys, redisClient),
53
55
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
54
56
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
55
57
  impressionCounts: impressionCountsCache,
@@ -0,0 +1,76 @@
1
+ import { isNaNNumber } from '../../utils/lang';
2
+ import { KeyBuilder } from '../KeyBuilder';
3
+ import { IPluggableStorageWrapper, IRBSegmentsCacheAsync } from '../types';
4
+ import { ILogger } from '../../logger/types';
5
+ import { IRBSegment } from '../../dtos/types';
6
+ import { LOG_PREFIX } from './constants';
7
+ import { setToArray } from '../../utils/lang/sets';
8
+
9
+ export class RBSegmentsCachePluggable implements IRBSegmentsCacheAsync {
10
+
11
+ private readonly log: ILogger;
12
+ private readonly keys: KeyBuilder;
13
+ private readonly wrapper: IPluggableStorageWrapper;
14
+
15
+ constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper) {
16
+ this.log = log;
17
+ this.keys = keys;
18
+ this.wrapper = wrapper;
19
+ }
20
+
21
+ get(name: string): Promise<IRBSegment | null> {
22
+ return this.wrapper.get(this.keys.buildRBSegmentKey(name))
23
+ .then(maybeRBSegment => maybeRBSegment && JSON.parse(maybeRBSegment));
24
+ }
25
+
26
+ private getNames(): Promise<string[]> {
27
+ return this.wrapper.getKeysByPrefix(this.keys.buildRBSegmentKeyPrefix()).then(
28
+ (listOfKeys) => listOfKeys.map(this.keys.extractKey)
29
+ );
30
+ }
31
+
32
+ contains(names: Set<string>): Promise<boolean> {
33
+ const namesArray = setToArray(names);
34
+ return this.getNames().then(namesInStorage => {
35
+ return namesArray.every(name => namesInStorage.includes(name));
36
+ });
37
+ }
38
+
39
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean> {
40
+ return Promise.all([
41
+ this.setChangeNumber(changeNumber),
42
+ Promise.all(toAdd.map(toAdd => {
43
+ const key = this.keys.buildRBSegmentKey(toAdd.name);
44
+ const stringifiedNewRBSegment = JSON.stringify(toAdd);
45
+ return this.wrapper.set(key, stringifiedNewRBSegment).then(() => true);
46
+ })),
47
+ Promise.all(toRemove.map(toRemove => {
48
+ const key = this.keys.buildRBSegmentKey(toRemove.name);
49
+ return this.wrapper.del(key);
50
+ }))
51
+ ]).then(([, added, removed]) => {
52
+ return added.some(result => result) || removed.some(result => result);
53
+ });
54
+ }
55
+
56
+ setChangeNumber(changeNumber: number) {
57
+ return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
58
+ }
59
+
60
+ getChangeNumber(): Promise<number> {
61
+ return this.wrapper.get(this.keys.buildRBSegmentsTillKey()).then((value) => {
62
+ const i = parseInt(value as string, 10);
63
+
64
+ return isNaNNumber(i) ? -1 : i;
65
+ }).catch((e) => {
66
+ this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from storage. Error: ' + e);
67
+ return -1;
68
+ });
69
+ }
70
+
71
+ // @TODO implement if required by DataLoader or producer mode
72
+ clear() {
73
+ return Promise.resolve();
74
+ }
75
+
76
+ }
@@ -66,7 +66,8 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
66
66
  * The returned promise is resolved when the operation success
67
67
  * or rejected if it fails (e.g., wrapper operation fails)
68
68
  */
69
- addSplit(name: string, split: ISplit): Promise<boolean> {
69
+ addSplit(split: ISplit): Promise<boolean> {
70
+ const name = split.name;
70
71
  const splitKey = this.keys.buildSplitKey(name);
71
72
  return this.wrapper.get(splitKey).then(splitFromStorage => {
72
73
 
@@ -91,15 +92,6 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
91
92
  }).then(() => true);
92
93
  }
93
94
 
94
- /**
95
- * Add a list of splits.
96
- * The returned promise is resolved when the operation success
97
- * or rejected if it fails (e.g., wrapper operation fails)
98
- */
99
- addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
100
- return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
101
- }
102
-
103
95
  /**
104
96
  * Remove a given split.
105
97
  * The returned promise is resolved when the operation success, with a boolean indicating if the split existed or not.
@@ -115,15 +107,6 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
115
107
  });
116
108
  }
117
109
 
118
- /**
119
- * Remove a list of splits.
120
- * The returned promise is resolved when the operation success, with a boolean array indicating if the splits existed or not.
121
- * or rejected if it fails (e.g., wrapper operation fails).
122
- */
123
- removeSplits(names: string[]): Promise<void> { // @ts-ignore
124
- return Promise.all(names.map(name => this.removeSplit(name)));
125
- }
126
-
127
110
  /**
128
111
  * Get split.
129
112
  * The returned promise is resolved with the split definition or null if it's not defined,
@@ -20,6 +20,7 @@ import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
20
20
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
21
21
  import { metadataBuilder } from '../utils';
22
22
  import { LOG_PREFIX } from '../pluggable/constants';
23
+ import { RBSegmentsCachePluggable } from './RBSegmentsCachePluggable';
23
24
 
24
25
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
25
26
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -88,8 +89,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
88
89
  // Connects to wrapper and emits SDK_READY event on main client
89
90
  const connectPromise = wrapper.connect().then(() => {
90
91
  if (isSynchronizer) {
91
- // @TODO reuse InLocalStorage::validateCache logic
92
- // In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
92
+ // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
93
93
  return wrapper.get(keys.buildHashKey()).then((hash) => {
94
94
  const currentHash = getStorageHash(settings);
95
95
  if (hash !== currentHash) {
@@ -117,6 +117,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
117
117
 
118
118
  return {
119
119
  splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
120
+ rbSegments: new RBSegmentsCachePluggable(log, keys, wrapper),
120
121
  segments: new SegmentsCachePluggable(log, keys, wrapper),
121
122
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
122
123
  impressionCounts: impressionCountsCache,
@@ -1,5 +1,5 @@
1
1
  import SplitIO from '../../types/splitio';
2
- import { MaybeThenable, ISplit, IMySegmentsResponse } from '../dtos/types';
2
+ import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse } from '../dtos/types';
3
3
  import { MySegmentsData } from '../sync/polling/types';
4
4
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
5
5
  import { ISettings } from '../types';
@@ -177,11 +177,9 @@ export interface IPluggableStorageWrapper {
177
177
  /** Splits cache */
178
178
 
179
179
  export interface ISplitsCacheBase {
180
- addSplits(entries: [string, ISplit][]): MaybeThenable<boolean[] | void>,
181
- removeSplits(names: string[]): MaybeThenable<boolean[] | void>,
180
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): MaybeThenable<boolean>,
182
181
  getSplit(name: string): MaybeThenable<ISplit | null>,
183
182
  getSplits(names: string[]): MaybeThenable<Record<string, ISplit | null>>, // `fetchMany` in spec
184
- setChangeNumber(changeNumber: number): MaybeThenable<boolean | void>,
185
183
  // should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
186
184
  getChangeNumber(): MaybeThenable<number>,
187
185
  getAll(): MaybeThenable<ISplit[]>,
@@ -191,42 +189,70 @@ export interface ISplitsCacheBase {
191
189
  // only for Client-Side. Returns true if the storage is not synchronized yet (getChangeNumber() === -1) or contains a FF using segments or large segments
192
190
  usesSegments(): MaybeThenable<boolean>,
193
191
  clear(): MaybeThenable<boolean | void>,
192
+ // should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
193
+ checkCache(): MaybeThenable<boolean>,
194
194
  killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
195
195
  getNamesByFlagSets(flagSets: string[]): MaybeThenable<Set<string>[]>
196
196
  }
197
197
 
198
198
  export interface ISplitsCacheSync extends ISplitsCacheBase {
199
- addSplits(entries: [string, ISplit][]): boolean[],
200
- removeSplits(names: string[]): boolean[],
199
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean,
201
200
  getSplit(name: string): ISplit | null,
202
201
  getSplits(names: string[]): Record<string, ISplit | null>,
203
- setChangeNumber(changeNumber: number): boolean | void,
204
202
  getChangeNumber(): number,
205
203
  getAll(): ISplit[],
206
204
  getSplitNames(): string[],
207
205
  trafficTypeExists(trafficType: string): boolean,
208
206
  usesSegments(): boolean,
209
207
  clear(): void,
208
+ checkCache(): boolean,
210
209
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
211
210
  getNamesByFlagSets(flagSets: string[]): Set<string>[]
212
211
  }
213
212
 
214
213
  export interface ISplitsCacheAsync extends ISplitsCacheBase {
215
- addSplits(entries: [string, ISplit][]): Promise<boolean[] | void>,
216
- removeSplits(names: string[]): Promise<boolean[] | void>,
214
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): Promise<boolean>,
217
215
  getSplit(name: string): Promise<ISplit | null>,
218
216
  getSplits(names: string[]): Promise<Record<string, ISplit | null>>,
219
- setChangeNumber(changeNumber: number): Promise<boolean | void>,
220
217
  getChangeNumber(): Promise<number>,
221
218
  getAll(): Promise<ISplit[]>,
222
219
  getSplitNames(): Promise<string[]>,
223
220
  trafficTypeExists(trafficType: string): Promise<boolean>,
224
221
  usesSegments(): Promise<boolean>,
225
222
  clear(): Promise<boolean | void>,
223
+ checkCache(): Promise<boolean>,
226
224
  killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
227
225
  getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
228
226
  }
229
227
 
228
+ /** Rule-Based Segments cache */
229
+
230
+ export interface IRBSegmentsCacheBase {
231
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): MaybeThenable<boolean>,
232
+ get(name: string): MaybeThenable<IRBSegment | null>,
233
+ getChangeNumber(): MaybeThenable<number>,
234
+ clear(): MaybeThenable<boolean | void>,
235
+ contains(names: Set<string>): MaybeThenable<boolean>,
236
+ }
237
+
238
+ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
239
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean,
240
+ get(name: string): IRBSegment | null,
241
+ getChangeNumber(): number,
242
+ clear(): void,
243
+ contains(names: Set<string>): boolean,
244
+ // Used only for smart pausing in client-side standalone. Returns true if the storage contains a RBSegment using segments or large segments matchers
245
+ usesSegments(): boolean,
246
+ }
247
+
248
+ export interface IRBSegmentsCacheAsync extends IRBSegmentsCacheBase {
249
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean>,
250
+ get(name: string): Promise<IRBSegment | null>,
251
+ getChangeNumber(): Promise<number>,
252
+ clear(): Promise<boolean | void>,
253
+ contains(names: Set<string>): Promise<boolean>,
254
+ }
255
+
230
256
  /** Segments cache */
231
257
 
232
258
  export interface ISegmentsCacheBase {
@@ -424,15 +450,17 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync,
424
450
  */
425
451
 
426
452
  export interface IStorageBase<
427
- TSplitsCache extends ISplitsCacheBase,
428
- TSegmentsCache extends ISegmentsCacheBase,
429
- TImpressionsCache extends IImpressionsCacheBase,
430
- TImpressionsCountCache extends IImpressionCountsCacheBase,
431
- TEventsCache extends IEventsCacheBase,
432
- TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
433
- TUniqueKeysCache extends IUniqueKeysCacheBase
453
+ TSplitsCache extends ISplitsCacheBase = ISplitsCacheBase,
454
+ TRBSegmentsCache extends IRBSegmentsCacheBase = IRBSegmentsCacheBase,
455
+ TSegmentsCache extends ISegmentsCacheBase = ISegmentsCacheBase,
456
+ TImpressionsCache extends IImpressionsCacheBase = IImpressionsCacheBase,
457
+ TImpressionsCountCache extends IImpressionCountsCacheBase = IImpressionCountsCacheBase,
458
+ TEventsCache extends IEventsCacheBase = IEventsCacheBase,
459
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync = ITelemetryCacheSync | ITelemetryCacheAsync,
460
+ TUniqueKeysCache extends IUniqueKeysCacheBase = IUniqueKeysCacheBase
434
461
  > {
435
462
  splits: TSplitsCache,
463
+ rbSegments: TRBSegmentsCache,
436
464
  segments: TSegmentsCache,
437
465
  impressions: TImpressionsCache,
438
466
  impressionCounts: TImpressionsCountCache,
@@ -445,6 +473,7 @@ export interface IStorageBase<
445
473
 
446
474
  export interface IStorageSync extends IStorageBase<
447
475
  ISplitsCacheSync,
476
+ IRBSegmentsCacheSync,
448
477
  ISegmentsCacheSync,
449
478
  IImpressionsCacheSync,
450
479
  IImpressionCountsCacheSync,
@@ -453,12 +482,12 @@ export interface IStorageSync extends IStorageBase<
453
482
  IUniqueKeysCacheSync
454
483
  > {
455
484
  // Defined in client-side
456
- validateCache?: () => boolean, // @TODO support async
457
485
  largeSegments?: ISegmentsCacheSync,
458
486
  }
459
487
 
460
488
  export interface IStorageAsync extends IStorageBase<
461
489
  ISplitsCacheAsync,
490
+ IRBSegmentsCacheAsync,
462
491
  ISegmentsCacheAsync,
463
492
  IImpressionsCacheAsync | IImpressionsCacheSync,
464
493
  IImpressionCountsCacheBase,
@@ -1,6 +1,6 @@
1
1
  import { forOwn } from '../../../utils/lang';
2
2
  import { IReadinessManager } from '../../../readiness/types';
3
- import { IStorageSync } from '../../../storages/types';
3
+ import { ISplitsCacheSync } from '../../../storages/types';
4
4
  import { ISplitsParser } from '../splitsParser/types';
5
5
  import { ISplit, ISplitPartial } from '../../../dtos/types';
6
6
  import { syncTaskFactory } from '../../syncTask';
@@ -15,7 +15,7 @@ import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/c
15
15
  */
16
16
  export function fromObjectUpdaterFactory(
17
17
  splitsParser: ISplitsParser,
18
- storage: Pick<IStorageSync, 'splits' | 'validateCache'>,
18
+ storage: { splits: ISplitsCacheSync },
19
19
  readiness: IReadinessManager,
20
20
  settings: ISettings,
21
21
  ): () => Promise<boolean> {
@@ -24,7 +24,7 @@ export function fromObjectUpdaterFactory(
24
24
  let startingUp = true;
25
25
 
26
26
  return function objectUpdater() {
27
- const splits: [string, ISplit][] = [];
27
+ const splits: ISplit[] = [];
28
28
  let loadError = null;
29
29
  let splitsMock: false | Record<string, ISplitPartial> = {};
30
30
  try {
@@ -37,33 +37,31 @@ export function fromObjectUpdaterFactory(
37
37
  if (!loadError && splitsMock) {
38
38
  log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
39
39
 
40
- forOwn(splitsMock, function (val, name) {
41
- splits.push([ // @ts-ignore Split changeNumber and seed is undefined in localhost mode
42
- name, {
43
- name,
44
- status: 'ACTIVE',
45
- killed: false,
46
- trafficAllocation: 100,
47
- defaultTreatment: CONTROL,
48
- conditions: val.conditions || [],
49
- configurations: val.configurations,
50
- trafficTypeName: val.trafficTypeName
51
- }
52
- ]);
40
+ forOwn(splitsMock, (val, name) => {
41
+ // @ts-ignore Split changeNumber and seed is undefined in localhost mode
42
+ splits.push({
43
+ name,
44
+ status: 'ACTIVE',
45
+ killed: false,
46
+ trafficAllocation: 100,
47
+ defaultTreatment: CONTROL,
48
+ conditions: val.conditions || [],
49
+ configurations: val.configurations,
50
+ trafficTypeName: val.trafficTypeName
51
+ });
53
52
  });
54
53
 
55
54
  return Promise.all([
56
55
  splitsCache.clear(), // required to sync removed splits from mock
57
- splitsCache.addSplits(splits)
56
+ splitsCache.update(splits, [], Date.now())
58
57
  ]).then(() => {
59
58
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
60
59
 
61
60
  if (startingUp) {
62
61
  startingUp = false;
63
- const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
64
- Promise.resolve().then(() => {
62
+ Promise.resolve(splitsCache.checkCache()).then(cacheReady => {
65
63
  // Emits SDK_READY_FROM_CACHE
66
- if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
64
+ if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
67
65
  // Emits SDK_READY
68
66
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
69
67
  });
@@ -81,7 +79,7 @@ export function fromObjectUpdaterFactory(
81
79
  */
82
80
  export function fromObjectSyncTaskFactory(
83
81
  splitsParser: ISplitsParser,
84
- storage: Pick<IStorageSync, 'splits' | 'validateCache'>,
82
+ storage: { splits: ISplitsCacheSync },
85
83
  readiness: IReadinessManager,
86
84
  settings: ISettings
87
85
  ): ISyncTask<[], boolean> {