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