@splitsoftware/splitio-commons 2.8.1-rc.0 → 2.8.1-rc.1
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.
- package/CHANGES.txt +2 -3
- package/cjs/evaluator/index.js +13 -13
- package/cjs/sdkClient/client.js +3 -3
- package/cjs/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/cjs/storages/AbstractSplitsCacheSync.js +2 -3
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +28 -10
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +33 -22
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +29 -19
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +2 -3
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +1 -1
- package/cjs/sync/polling/syncTasks/segmentsSyncTask.js +1 -1
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +5 -16
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +3 -3
- package/cjs/utils/inputValidation/eventProperties.js +6 -1
- package/esm/evaluator/index.js +13 -13
- package/esm/sdkClient/client.js +3 -3
- package/esm/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/esm/storages/AbstractSplitsCacheSync.js +2 -3
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +29 -11
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +33 -22
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +29 -19
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +2 -3
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +1 -1
- package/esm/sync/polling/syncTasks/segmentsSyncTask.js +1 -1
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +5 -16
- package/esm/sync/polling/updaters/splitChangesUpdater.js +3 -3
- package/esm/utils/inputValidation/eventProperties.js +6 -1
- package/package.json +1 -1
- package/src/evaluator/index.ts +14 -6
- package/src/sdkClient/client.ts +3 -3
- package/src/storages/AbstractMySegmentsCacheSync.ts +20 -26
- package/src/storages/AbstractSplitsCacheSync.ts +2 -3
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +24 -9
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +27 -18
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +29 -22
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +2 -3
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +1 -1
- package/src/sync/polling/syncTasks/segmentsSyncTask.ts +0 -2
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +3 -3
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +4 -17
- package/src/sync/polling/updaters/splitChangesUpdater.ts +7 -6
- package/src/utils/inputValidation/eventProperties.ts +6 -1
- package/types/splitio.d.ts +10 -0
package/src/evaluator/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export function evaluateFeature(
|
|
|
30
30
|
splitName: string,
|
|
31
31
|
attributes: SplitIO.Attributes | undefined,
|
|
32
32
|
storage: IStorageSync | IStorageAsync,
|
|
33
|
+
options?: SplitIO.EvaluationOptions
|
|
33
34
|
): MaybeThenable<IEvaluationResult> {
|
|
34
35
|
let parsedSplit;
|
|
35
36
|
|
|
@@ -47,6 +48,7 @@ export function evaluateFeature(
|
|
|
47
48
|
split,
|
|
48
49
|
attributes,
|
|
49
50
|
storage,
|
|
51
|
+
options,
|
|
50
52
|
)).catch(
|
|
51
53
|
// Exception on async `getSplit` storage. For example, when the storage is redis or
|
|
52
54
|
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
@@ -60,6 +62,7 @@ export function evaluateFeature(
|
|
|
60
62
|
parsedSplit,
|
|
61
63
|
attributes,
|
|
62
64
|
storage,
|
|
65
|
+
options,
|
|
63
66
|
);
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -69,6 +72,7 @@ export function evaluateFeatures(
|
|
|
69
72
|
splitNames: string[],
|
|
70
73
|
attributes: SplitIO.Attributes | undefined,
|
|
71
74
|
storage: IStorageSync | IStorageAsync,
|
|
75
|
+
options?: SplitIO.EvaluationOptions,
|
|
72
76
|
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
73
77
|
let parsedSplits;
|
|
74
78
|
|
|
@@ -80,13 +84,13 @@ export function evaluateFeatures(
|
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
return thenable(parsedSplits) ?
|
|
83
|
-
parsedSplits.then(splits => getEvaluations(log, key, splitNames, splits, attributes, storage))
|
|
87
|
+
parsedSplits.then(splits => getEvaluations(log, key, splitNames, splits, attributes, storage, options))
|
|
84
88
|
.catch(() => {
|
|
85
89
|
// Exception on async `getSplits` storage. For example, when the storage is redis or
|
|
86
90
|
// pluggable and there is a connection issue and we can't retrieve the split to be evaluated
|
|
87
91
|
return treatmentsException(splitNames);
|
|
88
92
|
}) :
|
|
89
|
-
getEvaluations(log, key, splitNames, parsedSplits, attributes, storage);
|
|
93
|
+
getEvaluations(log, key, splitNames, parsedSplits, attributes, storage, options);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
export function evaluateFeaturesByFlagSets(
|
|
@@ -96,6 +100,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
96
100
|
attributes: SplitIO.Attributes | undefined,
|
|
97
101
|
storage: IStorageSync | IStorageAsync,
|
|
98
102
|
method: string,
|
|
103
|
+
options?: SplitIO.EvaluationOptions,
|
|
99
104
|
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
100
105
|
let storedFlagNames: MaybeThenable<Set<string>[]>;
|
|
101
106
|
|
|
@@ -111,7 +116,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
return featureFlags.size ?
|
|
114
|
-
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
|
|
119
|
+
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage, options) :
|
|
115
120
|
{};
|
|
116
121
|
}
|
|
117
122
|
|
|
@@ -138,6 +143,7 @@ function getEvaluation(
|
|
|
138
143
|
splitJSON: ISplit | null,
|
|
139
144
|
attributes: SplitIO.Attributes | undefined,
|
|
140
145
|
storage: IStorageSync | IStorageAsync,
|
|
146
|
+
options?: SplitIO.EvaluationOptions,
|
|
141
147
|
): MaybeThenable<IEvaluationResult> {
|
|
142
148
|
let evaluation: MaybeThenable<IEvaluationResult> = {
|
|
143
149
|
treatment: CONTROL,
|
|
@@ -154,14 +160,14 @@ function getEvaluation(
|
|
|
154
160
|
return evaluation.then(result => {
|
|
155
161
|
result.changeNumber = splitJSON.changeNumber;
|
|
156
162
|
result.config = splitJSON.configurations && splitJSON.configurations[result.treatment] || null;
|
|
157
|
-
result.impressionsDisabled = splitJSON.impressionsDisabled;
|
|
163
|
+
result.impressionsDisabled = options?.impressionsDisabled || splitJSON.impressionsDisabled;
|
|
158
164
|
|
|
159
165
|
return result;
|
|
160
166
|
});
|
|
161
167
|
} else {
|
|
162
168
|
evaluation.changeNumber = splitJSON.changeNumber;
|
|
163
169
|
evaluation.config = splitJSON.configurations && splitJSON.configurations[evaluation.treatment] || null;
|
|
164
|
-
evaluation.impressionsDisabled = splitJSON.impressionsDisabled;
|
|
170
|
+
evaluation.impressionsDisabled = options?.impressionsDisabled || splitJSON.impressionsDisabled;
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
173
|
|
|
@@ -175,6 +181,7 @@ function getEvaluations(
|
|
|
175
181
|
splits: Record<string, ISplit | null>,
|
|
176
182
|
attributes: SplitIO.Attributes | undefined,
|
|
177
183
|
storage: IStorageSync | IStorageAsync,
|
|
184
|
+
options?: SplitIO.EvaluationOptions,
|
|
178
185
|
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
179
186
|
const result: Record<string, IEvaluationResult> = {};
|
|
180
187
|
const thenables: Promise<void>[] = [];
|
|
@@ -184,7 +191,8 @@ function getEvaluations(
|
|
|
184
191
|
key,
|
|
185
192
|
splits[splitName],
|
|
186
193
|
attributes,
|
|
187
|
-
storage
|
|
194
|
+
storage,
|
|
195
|
+
options
|
|
188
196
|
);
|
|
189
197
|
if (thenable(evaluation)) {
|
|
190
198
|
thenables.push(evaluation.then(res => {
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const evaluation = readinessManager.isReadyFromCache() ?
|
|
55
|
-
evaluateFeature(log, key, featureFlagName, attributes, storage) :
|
|
55
|
+
evaluateFeature(log, key, featureFlagName, attributes, storage, options) :
|
|
56
56
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
57
57
|
Promise.resolve(treatmentNotReady) :
|
|
58
58
|
treatmentNotReady;
|
|
@@ -81,7 +81,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const evaluations = readinessManager.isReadyFromCache() ?
|
|
84
|
-
evaluateFeatures(log, key, featureFlagNames, attributes, storage) :
|
|
84
|
+
evaluateFeatures(log, key, featureFlagNames, attributes, storage, options) :
|
|
85
85
|
isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected
|
|
86
86
|
Promise.resolve(treatmentsNotReady(featureFlagNames)) :
|
|
87
87
|
treatmentsNotReady(featureFlagNames);
|
|
@@ -110,7 +110,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
const evaluations = readinessManager.isReadyFromCache() ?
|
|
113
|
-
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) :
|
|
113
|
+
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName, options) :
|
|
114
114
|
isAsync ?
|
|
115
115
|
Promise.resolve({}) :
|
|
116
116
|
{};
|
|
@@ -49,10 +49,12 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
49
49
|
* For client-side synchronizer: it resets or updates the cache.
|
|
50
50
|
*/
|
|
51
51
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
|
|
52
|
-
|
|
52
|
+
this.setChangeNumber(segmentsData.cn);
|
|
53
|
+
|
|
53
54
|
const { added, removed } = segmentsData as MySegmentsData;
|
|
54
55
|
|
|
55
56
|
if (added && removed) {
|
|
57
|
+
let isDiff = false;
|
|
56
58
|
|
|
57
59
|
added.forEach(segment => {
|
|
58
60
|
isDiff = this.addSegment(segment) || isDiff;
|
|
@@ -61,40 +63,32 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
61
63
|
removed.forEach(segment => {
|
|
62
64
|
isDiff = this.removeSegment(segment) || isDiff;
|
|
63
65
|
});
|
|
64
|
-
} else {
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
return isDiff;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
isDiff = false;
|
|
72
|
-
} else {
|
|
70
|
+
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
|
|
71
|
+
const storedSegmentKeys = this.getRegisteredSegments().sort();
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
// Extreme fast => everything is empty
|
|
74
|
+
if (!names.length && !storedSegmentKeys.length) return false;
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
let index = 0;
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
if (index === names.length && index === storedSegmentKeys.length) {
|
|
80
|
-
isDiff = false;
|
|
81
|
-
} else {
|
|
78
|
+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
86
|
-
}
|
|
80
|
+
// Quick path => no changes
|
|
81
|
+
if (index === names.length && index === storedSegmentKeys.length) return false;
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
// Slowest path => add and/or remove segments
|
|
84
|
+
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
85
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
86
|
+
}
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
88
|
+
for (let addIndex = index; addIndex < names.length; addIndex++) {
|
|
89
|
+
this.addSegment(names[addIndex]);
|
|
95
90
|
}
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
return isDiff;
|
|
92
|
+
return true;
|
|
99
93
|
}
|
|
100
94
|
}
|
|
@@ -14,10 +14,9 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
14
14
|
protected abstract setChangeNumber(changeNumber: number): boolean | void
|
|
15
15
|
|
|
16
16
|
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
|
|
17
|
-
let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
|
|
18
|
-
updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
|
|
19
17
|
this.setChangeNumber(changeNumber);
|
|
20
|
-
|
|
18
|
+
const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
|
|
19
|
+
return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
abstract getSplit(name: string): ISplit | null
|
|
@@ -2,7 +2,7 @@ import { ILogger } from '../../logger/types';
|
|
|
2
2
|
import { isNaNNumber } from '../../utils/lang';
|
|
3
3
|
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
4
4
|
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
5
|
-
import { DEFINED } from './constants';
|
|
5
|
+
import { LOG_PREFIX, DEFINED } from './constants';
|
|
6
6
|
import { StorageAdapter } from '../types';
|
|
7
7
|
|
|
8
8
|
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
@@ -16,22 +16,33 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
16
16
|
this.log = log;
|
|
17
17
|
this.keys = keys;
|
|
18
18
|
this.storage = storage;
|
|
19
|
+
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
protected addSegment(name: string): boolean {
|
|
22
23
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
try {
|
|
26
|
+
if (this.storage.getItem(segmentKey) === DEFINED) return false;
|
|
27
|
+
this.storage.setItem(segmentKey, DEFINED);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
this.log.error(LOG_PREFIX + e);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
protected removeSegment(name: string): boolean {
|
|
30
36
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
try {
|
|
39
|
+
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
|
|
40
|
+
this.storage.removeItem(segmentKey);
|
|
41
|
+
return true;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
this.log.error(LOG_PREFIX + e);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
isInSegment(name: string): boolean {
|
|
@@ -52,8 +63,12 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
protected setChangeNumber(changeNumber?: number) {
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
try {
|
|
67
|
+
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
68
|
+
else this.storage.removeItem(this.keys.buildTillKey());
|
|
69
|
+
} catch (e) {
|
|
70
|
+
this.log.error(e);
|
|
71
|
+
}
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
getChangeNumber() {
|
|
@@ -26,10 +26,9 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
|
|
29
|
-
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
30
|
-
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
31
29
|
this.setChangeNumber(changeNumber);
|
|
32
|
-
|
|
30
|
+
const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
31
|
+
return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
private setChangeNumber(changeNumber: number) {
|
|
@@ -49,30 +48,40 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
private add(rbSegment: IRBSegment): boolean {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
try {
|
|
52
|
+
const name = rbSegment.name;
|
|
53
|
+
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
|
|
54
|
+
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
|
|
55
|
+
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
let usesSegmentsDiff = 0;
|
|
60
|
+
if (previous && usesSegments(previous)) usesSegmentsDiff--;
|
|
61
|
+
if (usesSegments(rbSegment)) usesSegmentsDiff++;
|
|
62
|
+
if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
return true;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
this.log.error(LOG_PREFIX + e);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
private remove(name: string): boolean {
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
try {
|
|
73
|
+
const rbSegment = this.get(name);
|
|
74
|
+
if (!rbSegment) return false;
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
this.storage.removeItem(this.keys.buildRBSegmentKey(name));
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
return true;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
this.log.error(LOG_PREFIX + e);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
private getNames(): string[] {
|
|
@@ -80,34 +80,44 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
addSplit(split: ISplit) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
try {
|
|
84
|
+
const name = split.name;
|
|
85
|
+
const splitKey = this.keys.buildSplitKey(name);
|
|
86
|
+
const splitFromStorage = this.storage.getItem(splitKey);
|
|
87
|
+
const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
|
|
88
|
+
|
|
89
|
+
if (previousSplit) {
|
|
90
|
+
this._decrementCounts(previousSplit);
|
|
91
|
+
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
92
|
+
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
this.storage.setItem(splitKey, JSON.stringify(split));
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
this._incrementCounts(split);
|
|
97
|
+
this.addToFlagSets(split);
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
return true;
|
|
100
|
+
} catch (e) {
|
|
101
|
+
this.log.error(LOG_PREFIX + e);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
removeSplit(name: string): boolean {
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
try {
|
|
108
|
+
const split = this.getSplit(name);
|
|
109
|
+
if (!split) return false;
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
this.storage.removeItem(this.keys.buildSplitKey(name));
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
this._decrementCounts(split);
|
|
114
|
+
this.removeFromFlagSets(split.name, split.sets);
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
return true;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
this.log.error(LOG_PREFIX + e);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
getSplit(name: string): ISplit | null {
|
|
@@ -196,9 +206,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
196
206
|
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
197
207
|
|
|
198
208
|
const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
199
|
-
|
|
200
|
-
if (flagSetCache.has(featureFlag.name)) return;
|
|
201
|
-
|
|
202
209
|
flagSetCache.add(featureFlag.name);
|
|
203
210
|
|
|
204
211
|
this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
@@ -16,10 +16,9 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
|
|
19
|
-
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
20
|
-
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
21
19
|
this.changeNumber = changeNumber;
|
|
22
|
-
|
|
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;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
private add(rbSegment: IRBSegment): boolean {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ILogger } from '../../logger/types';
|
|
2
2
|
import { isNaNNumber } from '../../utils/lang';
|
|
3
|
-
import { LOG_PREFIX } from '
|
|
3
|
+
import { LOG_PREFIX } from '../inLocalStorage/constants';
|
|
4
4
|
import { KeyBuilderSS } from '../KeyBuilderSS';
|
|
5
5
|
import { ISegmentsCacheAsync } from '../types';
|
|
6
6
|
import type { RedisAdapter } from './RedisAdapter';
|
|
@@ -23,8 +23,6 @@ export function segmentsSyncTaskFactory(
|
|
|
23
23
|
segmentChangesFetcherFactory(fetchSegmentChanges),
|
|
24
24
|
storage.segments,
|
|
25
25
|
readiness,
|
|
26
|
-
settings.startup.requestTimeoutBeforeReady,
|
|
27
|
-
settings.startup.retriesOnFailureBeforeReady,
|
|
28
26
|
),
|
|
29
27
|
settings.scheduler.segmentsRefreshRate,
|
|
30
28
|
'segmentChangesUpdater'
|
|
@@ -66,10 +66,10 @@ export function mySegmentsUpdaterFactory(
|
|
|
66
66
|
new Promise((res) => { updateSegments(segmentsData); res(true); }) :
|
|
67
67
|
// If not provided, fetch mySegments
|
|
68
68
|
mySegmentsFetcher(matchingKey, noCache, till, _promiseDecorator).then(segments => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Only when we have downloaded and stored segments completely, we should not keep retrying anymore
|
|
69
|
+
// Only when we have downloaded segments completely, we should not keep retrying anymore
|
|
72
70
|
startingUp = false;
|
|
71
|
+
|
|
72
|
+
updateSegments(segments);
|
|
73
73
|
return true;
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -4,7 +4,6 @@ import { IReadinessManager } from '../../../readiness/types';
|
|
|
4
4
|
import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
5
5
|
import { ILogger } from '../../../logger/types';
|
|
6
6
|
import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
|
|
7
|
-
import { timeout } from '../../../utils/promise/timeout';
|
|
8
7
|
|
|
9
8
|
type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise<boolean>
|
|
10
9
|
|
|
@@ -24,18 +23,11 @@ export function segmentChangesUpdaterFactory(
|
|
|
24
23
|
segmentChangesFetcher: ISegmentChangesFetcher,
|
|
25
24
|
segments: ISegmentsCacheBase,
|
|
26
25
|
readiness?: IReadinessManager,
|
|
27
|
-
requestTimeoutBeforeReady?: number,
|
|
28
|
-
retriesOnFailureBeforeReady?: number,
|
|
29
26
|
): ISegmentChangesUpdater {
|
|
30
27
|
|
|
31
28
|
let readyOnAlreadyExistentState = true;
|
|
32
29
|
|
|
33
|
-
function
|
|
34
|
-
if (readyOnAlreadyExistentState && requestTimeoutBeforeReady) promise = timeout(requestTimeoutBeforeReady, promise);
|
|
35
|
-
return promise;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean, retries?: number): Promise<boolean> {
|
|
30
|
+
function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean): Promise<boolean> {
|
|
39
31
|
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
|
|
40
32
|
let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
41
33
|
|
|
@@ -43,19 +35,13 @@ export function segmentChangesUpdaterFactory(
|
|
|
43
35
|
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
44
36
|
return fetchOnlyNew && since !== undefined ?
|
|
45
37
|
false :
|
|
46
|
-
segmentChangesFetcher(since || -1, segmentName, noCache, till
|
|
38
|
+
segmentChangesFetcher(since || -1, segmentName, noCache, till).then((changes) => {
|
|
47
39
|
return Promise.all(changes.map(x => {
|
|
48
40
|
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
49
41
|
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
50
42
|
})).then((updates) => {
|
|
51
43
|
return updates.some(update => update);
|
|
52
44
|
});
|
|
53
|
-
}).catch(error => {
|
|
54
|
-
if (retries) {
|
|
55
|
-
log.warn(`${LOG_PREFIX_SYNC_SEGMENTS}Retrying fetch of segment ${segmentName} (attempt #${retries}). Reason: ${error}`);
|
|
56
|
-
return updateSegment(segmentName, noCache, till, fetchOnlyNew, retries - 1);
|
|
57
|
-
}
|
|
58
|
-
throw error;
|
|
59
45
|
});
|
|
60
46
|
});
|
|
61
47
|
}
|
|
@@ -77,7 +63,8 @@ export function segmentChangesUpdaterFactory(
|
|
|
77
63
|
let segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
|
|
78
64
|
|
|
79
65
|
return segmentsPromise.then(segmentNames => {
|
|
80
|
-
|
|
66
|
+
// Async fetchers
|
|
67
|
+
const updaters = segmentNames.map(segmentName => updateSegment(segmentName, noCache, till, fetchOnlyNew));
|
|
81
68
|
|
|
82
69
|
return Promise.all(updaters).then(shouldUpdateFlags => {
|
|
83
70
|
// if at least one segment fetch succeeded, mark segments ready
|
|
@@ -120,8 +120,8 @@ export function splitChangesUpdaterFactory(
|
|
|
120
120
|
storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments' | 'save'>,
|
|
121
121
|
splitFiltersValidation: ISplitFiltersValidation,
|
|
122
122
|
splitsEventEmitter?: ISplitsEventEmitter,
|
|
123
|
-
requestTimeoutBeforeReady = 0,
|
|
124
|
-
retriesOnFailureBeforeReady = 0,
|
|
123
|
+
requestTimeoutBeforeReady: number = 0,
|
|
124
|
+
retriesOnFailureBeforeReady: number = 0,
|
|
125
125
|
isClientSide?: boolean
|
|
126
126
|
): SplitChangesUpdater {
|
|
127
127
|
const { splits, rbSegments, segments } = storage;
|
|
@@ -163,6 +163,8 @@ export function splitChangesUpdaterFactory(
|
|
|
163
163
|
splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator)
|
|
164
164
|
)
|
|
165
165
|
.then((splitChanges: ISplitChangesResponse) => {
|
|
166
|
+
startingUp = false;
|
|
167
|
+
|
|
166
168
|
const usedSegments = new Set<string>();
|
|
167
169
|
|
|
168
170
|
let ffUpdate: MaybeThenable<boolean> = false;
|
|
@@ -185,8 +187,6 @@ export function splitChangesUpdaterFactory(
|
|
|
185
187
|
]).then(([ffChanged, rbsChanged]) => {
|
|
186
188
|
if (storage.save) storage.save();
|
|
187
189
|
|
|
188
|
-
startingUp = false;
|
|
189
|
-
|
|
190
190
|
if (splitsEventEmitter) {
|
|
191
191
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
192
192
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
|
@@ -201,13 +201,14 @@ export function splitChangesUpdaterFactory(
|
|
|
201
201
|
});
|
|
202
202
|
})
|
|
203
203
|
.catch(error => {
|
|
204
|
+
log.warn(SYNC_SPLITS_FETCH_FAILS, [error]);
|
|
205
|
+
|
|
204
206
|
if (startingUp && retriesOnFailureBeforeReady > retry) {
|
|
205
207
|
retry += 1;
|
|
206
|
-
log.
|
|
208
|
+
log.info(SYNC_SPLITS_FETCH_RETRY, [retry, error]);
|
|
207
209
|
return _splitChangesUpdater(sinces, retry);
|
|
208
210
|
} else {
|
|
209
211
|
startingUp = false;
|
|
210
|
-
log.warn(SYNC_SPLITS_FETCH_FAILS, [error]);
|
|
211
212
|
}
|
|
212
213
|
return false;
|
|
213
214
|
});
|
|
@@ -70,7 +70,12 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth
|
|
|
70
70
|
export function validateEvaluationOptions(log: ILogger, maybeOptions: any, method: string): SplitIO.EvaluationOptions | undefined {
|
|
71
71
|
if (isObject(maybeOptions)) {
|
|
72
72
|
const properties = validateEventProperties(log, maybeOptions.properties, method).properties;
|
|
73
|
-
|
|
73
|
+
let options = properties && Object.keys(properties).length > 0 ? { properties } : undefined;
|
|
74
|
+
|
|
75
|
+
const impressionsDisabled = maybeOptions.impressionsDisabled;
|
|
76
|
+
if (!impressionsDisabled) return options;
|
|
77
|
+
|
|
78
|
+
return options ? { ...options, impressionsDisabled } : { impressionsDisabled };
|
|
74
79
|
} else if (maybeOptions) {
|
|
75
80
|
log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'evaluation options']);
|
|
76
81
|
}
|
package/types/splitio.d.ts
CHANGED
|
@@ -918,8 +918,16 @@ declare namespace SplitIO {
|
|
|
918
918
|
* Evaluation options object for getTreatment methods.
|
|
919
919
|
*/
|
|
920
920
|
type EvaluationOptions = {
|
|
921
|
+
/**
|
|
922
|
+
* Whether the evaluation/s will track impressions or not.
|
|
923
|
+
*
|
|
924
|
+
* @defaultValue `false`
|
|
925
|
+
*/
|
|
926
|
+
impressionsDisabled?: boolean;
|
|
921
927
|
/**
|
|
922
928
|
* Optional properties to append to the generated impression object sent to Split backend.
|
|
929
|
+
*
|
|
930
|
+
* @defaultValue `undefined`
|
|
923
931
|
*/
|
|
924
932
|
properties?: Properties;
|
|
925
933
|
}
|
|
@@ -1335,6 +1343,8 @@ declare namespace SplitIO {
|
|
|
1335
1343
|
/**
|
|
1336
1344
|
* Defines the factory function to instantiate the storage. If not provided, the default in-memory storage is used.
|
|
1337
1345
|
*
|
|
1346
|
+
* NOTE: Currently, there is no persistent storage option available for the React Native SDK; only `InLocalStorage` for the Browser SDK.
|
|
1347
|
+
*
|
|
1338
1348
|
* Example:
|
|
1339
1349
|
* ```
|
|
1340
1350
|
* SplitFactory({
|