@splitsoftware/splitio-commons 2.3.0 → 2.3.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 (157) hide show
  1. package/CHANGES.txt +4 -0
  2. package/README.md +1 -0
  3. package/cjs/evaluator/Engine.js +40 -61
  4. package/cjs/evaluator/combiners/and.js +2 -6
  5. package/cjs/evaluator/combiners/ifelseif.js +6 -6
  6. package/cjs/evaluator/condition/index.js +6 -5
  7. package/cjs/evaluator/index.js +11 -11
  8. package/cjs/evaluator/matchers/index.js +3 -1
  9. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  10. package/cjs/evaluator/matchers/prerequisites.js +22 -0
  11. package/cjs/evaluator/matchers/rbsegment.js +56 -0
  12. package/cjs/evaluator/matchersTransform/index.js +4 -0
  13. package/cjs/evaluator/parser/index.js +2 -2
  14. package/cjs/evaluator/value/sanitize.js +1 -0
  15. package/cjs/logger/constants.js +5 -3
  16. package/cjs/logger/messages/debug.js +4 -2
  17. package/cjs/logger/messages/warn.js +1 -1
  18. package/cjs/sdkManager/index.js +2 -1
  19. package/cjs/services/splitApi.js +3 -4
  20. package/cjs/services/splitHttpClient.js +1 -1
  21. package/cjs/storages/AbstractSplitsCacheSync.js +5 -2
  22. package/cjs/storages/KeyBuilder.js +9 -0
  23. package/cjs/storages/KeyBuilderCS.js +3 -0
  24. package/cjs/storages/KeyBuilderSS.js +3 -0
  25. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
  26. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -14
  27. package/cjs/storages/inLocalStorage/index.js +5 -1
  28. package/cjs/storages/inLocalStorage/validateCache.js +2 -1
  29. package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
  30. package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
  31. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
  32. package/cjs/storages/inMemory/SplitsCacheInMemory.js +1 -0
  33. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
  34. package/cjs/storages/inRedis/index.js +2 -0
  35. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
  36. package/cjs/storages/pluggable/index.js +2 -0
  37. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +54 -4
  38. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  39. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  40. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  41. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  42. package/cjs/sync/polling/updaters/splitChangesUpdater.js +59 -33
  43. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  44. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
  45. package/cjs/sync/streaming/constants.js +2 -1
  46. package/cjs/sync/streaming/pushManager.js +3 -16
  47. package/cjs/sync/syncManagerOnline.js +2 -2
  48. package/cjs/utils/constants/index.js +6 -2
  49. package/cjs/utils/labels/index.js +2 -1
  50. package/esm/evaluator/Engine.js +40 -62
  51. package/esm/evaluator/combiners/and.js +2 -6
  52. package/esm/evaluator/combiners/ifelseif.js +7 -7
  53. package/esm/evaluator/condition/index.js +6 -5
  54. package/esm/evaluator/index.js +12 -12
  55. package/esm/evaluator/matchers/index.js +3 -1
  56. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  57. package/esm/evaluator/matchers/prerequisites.js +18 -0
  58. package/esm/evaluator/matchers/rbsegment.js +52 -0
  59. package/esm/evaluator/matchersTransform/index.js +4 -0
  60. package/esm/evaluator/parser/index.js +2 -2
  61. package/esm/evaluator/value/sanitize.js +1 -0
  62. package/esm/logger/constants.js +2 -0
  63. package/esm/logger/messages/debug.js +4 -2
  64. package/esm/logger/messages/warn.js +1 -1
  65. package/esm/sdkManager/index.js +2 -1
  66. package/esm/services/splitApi.js +3 -4
  67. package/esm/services/splitHttpClient.js +1 -1
  68. package/esm/storages/AbstractSplitsCacheSync.js +5 -2
  69. package/esm/storages/KeyBuilder.js +9 -0
  70. package/esm/storages/KeyBuilderCS.js +3 -0
  71. package/esm/storages/KeyBuilderSS.js +3 -0
  72. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
  73. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -14
  74. package/esm/storages/inLocalStorage/index.js +5 -1
  75. package/esm/storages/inLocalStorage/validateCache.js +2 -1
  76. package/esm/storages/inMemory/InMemoryStorage.js +3 -0
  77. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
  78. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
  79. package/esm/storages/inMemory/SplitsCacheInMemory.js +1 -0
  80. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
  81. package/esm/storages/inRedis/index.js +2 -0
  82. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
  83. package/esm/storages/pluggable/index.js +2 -0
  84. package/esm/sync/polling/fetchers/splitChangesFetcher.js +54 -4
  85. package/esm/sync/polling/pollingManagerCS.js +7 -7
  86. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  87. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  88. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  89. package/esm/sync/polling/updaters/splitChangesUpdater.js +59 -33
  90. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  91. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
  92. package/esm/sync/streaming/constants.js +1 -0
  93. package/esm/sync/streaming/pushManager.js +6 -19
  94. package/esm/sync/syncManagerOnline.js +2 -2
  95. package/esm/utils/constants/index.js +5 -1
  96. package/esm/utils/labels/index.js +1 -0
  97. package/package.json +1 -1
  98. package/src/dtos/types.ts +41 -8
  99. package/src/evaluator/Engine.ts +42 -69
  100. package/src/evaluator/combiners/and.ts +5 -4
  101. package/src/evaluator/combiners/ifelseif.ts +7 -9
  102. package/src/evaluator/condition/engineUtils.ts +1 -1
  103. package/src/evaluator/condition/index.ts +12 -12
  104. package/src/evaluator/index.ts +12 -14
  105. package/src/evaluator/matchers/index.ts +3 -1
  106. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  107. package/src/evaluator/matchers/prerequisites.ts +24 -0
  108. package/src/evaluator/matchers/rbsegment.ts +74 -0
  109. package/src/evaluator/matchersTransform/index.ts +3 -0
  110. package/src/evaluator/parser/index.ts +3 -3
  111. package/src/evaluator/types.ts +2 -2
  112. package/src/evaluator/value/index.ts +2 -2
  113. package/src/evaluator/value/sanitize.ts +5 -4
  114. package/src/logger/constants.ts +2 -0
  115. package/src/logger/messages/debug.ts +4 -2
  116. package/src/logger/messages/warn.ts +1 -1
  117. package/src/sdkManager/index.ts +3 -2
  118. package/src/services/splitApi.ts +3 -4
  119. package/src/services/splitHttpClient.ts +1 -1
  120. package/src/services/types.ts +1 -1
  121. package/src/storages/AbstractSplitsCacheSync.ts +6 -3
  122. package/src/storages/KeyBuilder.ts +12 -0
  123. package/src/storages/KeyBuilderCS.ts +4 -0
  124. package/src/storages/KeyBuilderSS.ts +4 -0
  125. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
  126. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -14
  127. package/src/storages/inLocalStorage/index.ts +5 -1
  128. package/src/storages/inLocalStorage/validateCache.ts +3 -1
  129. package/src/storages/inMemory/InMemoryStorage.ts +3 -0
  130. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
  131. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
  132. package/src/storages/inMemory/SplitsCacheInMemory.ts +1 -0
  133. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
  134. package/src/storages/inRedis/index.ts +2 -0
  135. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
  136. package/src/storages/pluggable/index.ts +2 -0
  137. package/src/storages/types.ts +33 -1
  138. package/src/sync/polling/fetchers/splitChangesFetcher.ts +65 -4
  139. package/src/sync/polling/fetchers/types.ts +1 -0
  140. package/src/sync/polling/pollingManagerCS.ts +7 -7
  141. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -1
  142. package/src/sync/polling/types.ts +2 -2
  143. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
  144. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  145. package/src/sync/polling/updaters/splitChangesUpdater.ts +70 -43
  146. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  147. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  148. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
  149. package/src/sync/streaming/constants.ts +1 -0
  150. package/src/sync/streaming/parseUtils.ts +2 -2
  151. package/src/sync/streaming/pushManager.ts +6 -18
  152. package/src/sync/streaming/types.ts +3 -2
  153. package/src/sync/syncManagerOnline.ts +2 -2
  154. package/src/utils/constants/index.ts +6 -1
  155. package/src/utils/labels/index.ts +1 -0
  156. package/src/utils/lang/index.ts +1 -1
  157. package/types/splitio.d.ts +5 -1
@@ -0,0 +1,136 @@
1
+ import { IRBSegment } from '../../dtos/types';
2
+ import { ILogger } from '../../logger/types';
3
+ import { ISettings } from '../../types';
4
+ import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
5
+ import { setToArray } from '../../utils/lang/sets';
6
+ import { usesSegments } from '../AbstractSplitsCacheSync';
7
+ import { KeyBuilderCS } from '../KeyBuilderCS';
8
+ import { IRBSegmentsCacheSync } from '../types';
9
+ import { LOG_PREFIX } from './constants';
10
+
11
+ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
12
+
13
+ private readonly keys: KeyBuilderCS;
14
+ private readonly log: ILogger;
15
+
16
+ constructor(settings: ISettings, keys: KeyBuilderCS) {
17
+ this.keys = keys;
18
+ this.log = settings.log;
19
+ }
20
+
21
+ clear() {
22
+ this.getNames().forEach(name => this.remove(name));
23
+ localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
24
+ }
25
+
26
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
27
+ this.setChangeNumber(changeNumber);
28
+ const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
29
+ return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
30
+ }
31
+
32
+ private setChangeNumber(changeNumber: number) {
33
+ try {
34
+ localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
35
+ localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
36
+ } catch (e) {
37
+ this.log.error(LOG_PREFIX + e);
38
+ }
39
+ }
40
+
41
+ private updateSegmentCount(diff: number) {
42
+ const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
43
+ const count = toNumber(localStorage.getItem(segmentsCountKey)) + diff;
44
+ // @ts-expect-error
45
+ if (count > 0) localStorage.setItem(segmentsCountKey, count);
46
+ else localStorage.removeItem(segmentsCountKey);
47
+ }
48
+
49
+ private add(rbSegment: IRBSegment): boolean {
50
+ try {
51
+ const name = rbSegment.name;
52
+ const rbSegmentKey = this.keys.buildRBSegmentKey(name);
53
+ const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
54
+ const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
55
+
56
+ localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57
+
58
+ let usesSegmentsDiff = 0;
59
+ if (previous && usesSegments(previous)) usesSegmentsDiff--;
60
+ if (usesSegments(rbSegment)) usesSegmentsDiff++;
61
+ if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);
62
+
63
+ return true;
64
+ } catch (e) {
65
+ this.log.error(LOG_PREFIX + e);
66
+ return false;
67
+ }
68
+ }
69
+
70
+ private remove(name: string): boolean {
71
+ try {
72
+ const rbSegment = this.get(name);
73
+ if (!rbSegment) return false;
74
+
75
+ localStorage.removeItem(this.keys.buildRBSegmentKey(name));
76
+
77
+ if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
78
+
79
+ return true;
80
+ } catch (e) {
81
+ this.log.error(LOG_PREFIX + e);
82
+ return false;
83
+ }
84
+ }
85
+
86
+ private getNames(): string[] {
87
+ const len = localStorage.length;
88
+ const accum = [];
89
+
90
+ let cur = 0;
91
+
92
+ while (cur < len) {
93
+ const key = localStorage.key(cur);
94
+
95
+ if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
96
+
97
+ cur++;
98
+ }
99
+
100
+ return accum;
101
+ }
102
+
103
+ get(name: string): IRBSegment | null {
104
+ const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
105
+ return item && JSON.parse(item);
106
+ }
107
+
108
+ contains(names: Set<string>): boolean {
109
+ const namesArray = setToArray(names);
110
+ const namesInStorage = this.getNames();
111
+ return namesArray.every(name => namesInStorage.indexOf(name) !== -1);
112
+ }
113
+
114
+ getChangeNumber(): number {
115
+ const n = -1;
116
+ let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
117
+
118
+ if (value !== null) {
119
+ value = parseInt(value, 10);
120
+
121
+ return isNaNNumber(value) ? n : value;
122
+ }
123
+
124
+ return n;
125
+ }
126
+
127
+ usesSegments(): boolean {
128
+ const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
129
+ const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
130
+
131
+ return isFiniteNumber(splitsWithSegmentsCount) ?
132
+ splitsWithSegmentsCount > 0 :
133
+ true;
134
+ }
135
+
136
+ }
@@ -47,16 +47,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
47
47
 
48
48
  private _incrementCounts(split: ISplit) {
49
49
  try {
50
- if (split) {
51
- const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
52
- // @ts-expect-error
53
- localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
50
+ const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
51
+ // @ts-expect-error
52
+ localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
54
53
 
55
- if (usesSegments(split)) {
56
- const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
57
- // @ts-expect-error
58
- localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
59
- }
54
+ if (usesSegments(split)) {
55
+ const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
56
+ // @ts-expect-error
57
+ localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
60
58
  }
61
59
  } catch (e) {
62
60
  this.log.error(LOG_PREFIX + e);
@@ -185,11 +183,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
185
183
  const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
186
184
  const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
187
185
 
188
- if (isFiniteNumber(splitsWithSegmentsCount)) {
189
- return splitsWithSegmentsCount > 0;
190
- } else {
191
- return true;
192
- }
186
+ return isFiniteNumber(splitsWithSegmentsCount) ?
187
+ splitsWithSegmentsCount > 0 :
188
+ true;
193
189
  }
194
190
 
195
191
  getNamesByFlagSets(flagSets: string[]): Set<string>[] {
@@ -6,6 +6,7 @@ import { validatePrefix } from '../KeyBuilder';
6
6
  import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
7
7
  import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
8
8
  import { SplitsCacheInLocal } from './SplitsCacheInLocal';
9
+ import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
9
10
  import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
10
11
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
11
12
  import { LOG_PREFIX } from './constants';
@@ -36,11 +37,13 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
36
37
  const keys = new KeyBuilderCS(prefix, matchingKey);
37
38
 
38
39
  const splits = new SplitsCacheInLocal(settings, keys);
40
+ const rbSegments = new RBSegmentsCacheInLocal(settings, keys);
39
41
  const segments = new MySegmentsCacheInLocal(log, keys);
40
42
  const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
41
43
 
42
44
  return {
43
45
  splits,
46
+ rbSegments,
44
47
  segments,
45
48
  largeSegments,
46
49
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
@@ -50,7 +53,7 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
50
53
  uniqueKeys: new UniqueKeysCacheInMemoryCS(),
51
54
 
52
55
  validateCache() {
53
- return validateCache(options, settings, keys, splits, segments, largeSegments);
56
+ return validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments);
54
57
  },
55
58
 
56
59
  destroy() { },
@@ -60,6 +63,7 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
60
63
 
61
64
  return {
62
65
  splits: this.splits,
66
+ rbSegments: this.rbSegments,
63
67
  segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
64
68
  largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
65
69
  impressions: this.impressions,
@@ -3,6 +3,7 @@ import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
3
3
  import { getStorageHash } from '../KeyBuilder';
4
4
  import { LOG_PREFIX } from './constants';
5
5
  import type { SplitsCacheInLocal } from './SplitsCacheInLocal';
6
+ import type { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
6
7
  import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
7
8
  import { KeyBuilderCS } from '../KeyBuilderCS';
8
9
  import SplitIO from '../../../types/splitio';
@@ -66,13 +67,14 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: IS
66
67
  *
67
68
  * @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
68
69
  */
69
- export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean {
70
+ export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean {
70
71
 
71
72
  const currentTimestamp = Date.now();
72
73
  const isThereCache = splits.getChangeNumber() > -1;
73
74
 
74
75
  if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
75
76
  splits.clear();
77
+ rbSegments.clear();
76
78
  segments.clear();
77
79
  largeSegments.clear();
78
80
 
@@ -7,6 +7,7 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
10
+ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
10
11
 
11
12
  /**
12
13
  * InMemory storage factory for standalone server-side SplitFactory
@@ -17,10 +18,12 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
17
18
  const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { __splitFiltersValidation } } } = params;
18
19
 
19
20
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
21
+ const rbSegments = new RBSegmentsCacheInMemory();
20
22
  const segments = new SegmentsCacheInMemory();
21
23
 
22
24
  const storage = {
23
25
  splits,
26
+ rbSegments,
24
27
  segments,
25
28
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
29
  impressionCounts: new ImpressionCountsCacheInMemory(),
@@ -7,6 +7,7 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
+ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
10
11
 
11
12
  /**
12
13
  * InMemory storage factory for standalone client-side SplitFactory
@@ -17,11 +18,13 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
17
18
  const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation } } } = params;
18
19
 
19
20
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
21
+ const rbSegments = new RBSegmentsCacheInMemory();
20
22
  const segments = new MySegmentsCacheInMemory();
21
23
  const largeSegments = new MySegmentsCacheInMemory();
22
24
 
23
25
  const storage = {
24
26
  splits,
27
+ rbSegments,
25
28
  segments,
26
29
  largeSegments,
27
30
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
@@ -36,6 +39,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
36
39
  shared() {
37
40
  return {
38
41
  splits: this.splits,
42
+ rbSegments: this.rbSegments,
39
43
  segments: new MySegmentsCacheInMemory(),
40
44
  largeSegments: new MySegmentsCacheInMemory(),
41
45
  impressions: this.impressions,
@@ -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
+ }
@@ -24,6 +24,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
24
24
  this.ttCache = {};
25
25
  this.changeNumber = -1;
26
26
  this.segmentsCount = 0;
27
+ this.flagSetsCache = {};
27
28
  }
28
29
 
29
30
  addSplit(split: ISplit): boolean {
@@ -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
+ }
@@ -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
@@ -59,6 +60,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
59
60
 
60
61
  return {
61
62
  splits: new SplitsCacheInRedis(log, keys, redisClient, settings.sync.__splitFiltersValidation),
63
+ rbSegments: new RBSegmentsCacheInRedis(log, keys, redisClient),
62
64
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
63
65
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
64
66
  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
+ }
@@ -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.';
@@ -117,6 +118,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
117
118
 
118
119
  return {
119
120
  splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
121
+ rbSegments: new RBSegmentsCachePluggable(log, keys, wrapper),
120
122
  segments: new SegmentsCachePluggable(log, keys, wrapper),
121
123
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
122
124
  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';
@@ -221,6 +221,34 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
221
221
  getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
222
222
  }
223
223
 
224
+ /** Rule-Based Segments cache */
225
+
226
+ export interface IRBSegmentsCacheBase {
227
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): MaybeThenable<boolean>,
228
+ get(name: string): MaybeThenable<IRBSegment | null>,
229
+ getChangeNumber(): MaybeThenable<number>,
230
+ clear(): MaybeThenable<boolean | void>,
231
+ contains(names: Set<string>): MaybeThenable<boolean>,
232
+ }
233
+
234
+ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
235
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean,
236
+ get(name: string): IRBSegment | null,
237
+ getChangeNumber(): number,
238
+ clear(): void,
239
+ contains(names: Set<string>): boolean,
240
+ // Used only for smart pausing in client-side standalone. Returns true if the storage contains a RBSegment using segments or large segments matchers
241
+ usesSegments(): boolean,
242
+ }
243
+
244
+ export interface IRBSegmentsCacheAsync extends IRBSegmentsCacheBase {
245
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean>,
246
+ get(name: string): Promise<IRBSegment | null>,
247
+ getChangeNumber(): Promise<number>,
248
+ clear(): Promise<boolean | void>,
249
+ contains(names: Set<string>): Promise<boolean>,
250
+ }
251
+
224
252
  /** Segments cache */
225
253
 
226
254
  export interface ISegmentsCacheBase {
@@ -419,6 +447,7 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync,
419
447
 
420
448
  export interface IStorageBase<
421
449
  TSplitsCache extends ISplitsCacheBase = ISplitsCacheBase,
450
+ TRBSegmentsCache extends IRBSegmentsCacheBase = IRBSegmentsCacheBase,
422
451
  TSegmentsCache extends ISegmentsCacheBase = ISegmentsCacheBase,
423
452
  TImpressionsCache extends IImpressionsCacheBase = IImpressionsCacheBase,
424
453
  TImpressionsCountCache extends IImpressionCountsCacheBase = IImpressionCountsCacheBase,
@@ -427,6 +456,7 @@ export interface IStorageBase<
427
456
  TUniqueKeysCache extends IUniqueKeysCacheBase = IUniqueKeysCacheBase
428
457
  > {
429
458
  splits: TSplitsCache,
459
+ rbSegments: TRBSegmentsCache,
430
460
  segments: TSegmentsCache,
431
461
  impressions: TImpressionsCache,
432
462
  impressionCounts: TImpressionsCountCache,
@@ -439,6 +469,7 @@ export interface IStorageBase<
439
469
 
440
470
  export interface IStorageSync extends IStorageBase<
441
471
  ISplitsCacheSync,
472
+ IRBSegmentsCacheSync,
442
473
  ISegmentsCacheSync,
443
474
  IImpressionsCacheSync,
444
475
  IImpressionCountsCacheSync,
@@ -453,6 +484,7 @@ export interface IStorageSync extends IStorageBase<
453
484
 
454
485
  export interface IStorageAsync extends IStorageBase<
455
486
  ISplitsCacheAsync,
487
+ IRBSegmentsCacheAsync,
456
488
  ISegmentsCacheAsync,
457
489
  IImpressionsCacheAsync | IImpressionsCacheSync,
458
490
  IImpressionCountsCacheBase,