@splitsoftware/splitio-commons 2.1.0 → 2.1.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 +3 -0
- package/cjs/logger/constants.js +4 -6
- package/cjs/logger/messages/debug.js +1 -3
- package/cjs/logger/messages/error.js +1 -1
- package/cjs/logger/messages/warn.js +1 -1
- package/cjs/sdkClient/client.js +29 -19
- package/cjs/sdkClient/clientAttributesDecoration.js +19 -25
- package/cjs/sdkClient/clientInputValidation.js +28 -26
- package/cjs/storages/AbstractSplitsCacheAsync.js +12 -1
- package/cjs/storages/AbstractSplitsCacheSync.js +5 -7
- package/cjs/storages/KeyBuilder.js +0 -16
- package/cjs/storages/KeyBuilderCS.js +8 -2
- package/cjs/storages/dataLoader.js +1 -2
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +5 -2
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +24 -31
- package/cjs/storages/inRedis/SplitsCacheInRedis.js +4 -21
- package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -19
- package/cjs/storages/utils.js +1 -0
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +12 -13
- package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +9 -23
- package/cjs/sync/submitters/impressionsSubmitter.js +3 -2
- package/cjs/trackers/strategy/strategyOptimized.js +3 -0
- package/cjs/utils/inputValidation/eventProperties.js +12 -1
- package/cjs/utils/inputValidation/index.js +3 -1
- package/esm/logger/constants.js +1 -3
- package/esm/logger/messages/debug.js +1 -3
- package/esm/logger/messages/error.js +1 -1
- package/esm/logger/messages/warn.js +1 -1
- package/esm/sdkClient/client.js +29 -19
- package/esm/sdkClient/clientAttributesDecoration.js +19 -25
- package/esm/sdkClient/clientInputValidation.js +29 -27
- package/esm/storages/AbstractSplitsCacheAsync.js +12 -1
- package/esm/storages/AbstractSplitsCacheSync.js +5 -7
- package/esm/storages/KeyBuilder.js +0 -16
- package/esm/storages/KeyBuilderCS.js +8 -2
- package/esm/storages/dataLoader.js +1 -2
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +5 -2
- package/esm/storages/inMemory/SplitsCacheInMemory.js +24 -31
- package/esm/storages/inRedis/SplitsCacheInRedis.js +4 -21
- package/esm/storages/pluggable/SplitsCachePluggable.js +2 -19
- package/esm/storages/utils.js +1 -0
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +12 -13
- package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
- package/esm/sync/polling/updaters/splitChangesUpdater.js +10 -24
- package/esm/sync/submitters/impressionsSubmitter.js +3 -2
- package/esm/trackers/strategy/strategyOptimized.js +3 -0
- package/esm/utils/inputValidation/eventProperties.js +10 -0
- package/esm/utils/inputValidation/index.js +1 -0
- package/package.json +1 -1
- package/src/logger/constants.ts +1 -3
- package/src/logger/messages/debug.ts +1 -3
- package/src/logger/messages/error.ts +1 -1
- package/src/logger/messages/warn.ts +1 -1
- package/src/sdkClient/client.ts +31 -21
- package/src/sdkClient/clientAttributesDecoration.ts +20 -27
- package/src/sdkClient/clientInputValidation.ts +30 -27
- package/src/storages/AbstractSplitsCacheAsync.ts +15 -5
- package/src/storages/AbstractSplitsCacheSync.ts +9 -13
- package/src/storages/KeyBuilder.ts +0 -20
- package/src/storages/KeyBuilderCS.ts +10 -3
- package/src/storages/dataLoader.ts +1 -2
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +5 -2
- package/src/storages/inMemory/SplitsCacheInMemory.ts +22 -27
- package/src/storages/inRedis/SplitsCacheInRedis.ts +4 -21
- package/src/storages/pluggable/SplitsCachePluggable.ts +2 -19
- package/src/storages/types.ts +10 -16
- package/src/storages/utils.ts +1 -0
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +14 -15
- package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -2
- package/src/sync/polling/updaters/splitChangesUpdater.ts +12 -27
- package/src/sync/submitters/impressionsSubmitter.ts +3 -2
- package/src/sync/submitters/types.ts +23 -33
- package/src/trackers/strategy/strategyOptimized.ts +3 -0
- package/src/utils/inputValidation/eventProperties.ts +10 -0
- package/src/utils/inputValidation/index.ts +1 -0
- package/types/splitio.d.ts +100 -35
|
@@ -26,7 +26,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
|
|
|
26
26
|
this.segmentsCount = 0;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
addSplit(
|
|
29
|
+
addSplit(split: ISplit): boolean {
|
|
30
|
+
const name = split.name;
|
|
30
31
|
const previousSplit = this.getSplit(name);
|
|
31
32
|
if (previousSplit) { // We had this Split already
|
|
32
33
|
|
|
@@ -40,41 +41,35 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
|
|
|
40
41
|
if (usesSegments(previousSplit)) this.segmentsCount--;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.addToFlagSets(split);
|
|
44
|
+
// Store the Split.
|
|
45
|
+
this.splitsCache[name] = split;
|
|
46
|
+
// Update TT cache
|
|
47
|
+
const ttName = split.trafficTypeName;
|
|
48
|
+
this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
|
|
49
|
+
this.addToFlagSets(split);
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Add to segments count for the new version of the Split
|
|
52
|
+
if (usesSegments(split)) this.segmentsCount++;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
54
|
+
return true;
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
removeSplit(name: string): boolean {
|
|
61
58
|
const split = this.getSplit(name);
|
|
62
|
-
if (split)
|
|
63
|
-
// Delete the Split
|
|
64
|
-
delete this.splitsCache[name];
|
|
59
|
+
if (!split) return false;
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!this.ttCache[ttName]) delete this.ttCache[ttName];
|
|
69
|
-
this.removeFromFlagSets(split.name, split.sets);
|
|
61
|
+
// Delete the Split
|
|
62
|
+
delete this.splitsCache[name];
|
|
70
63
|
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
const ttName = split.trafficTypeName;
|
|
65
|
+
this.ttCache[ttName]--; // Update tt cache
|
|
66
|
+
if (!this.ttCache[ttName]) delete this.ttCache[ttName];
|
|
67
|
+
this.removeFromFlagSets(split.name, split.sets);
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
// Update the segments count.
|
|
70
|
+
if (usesSegments(split)) this.segmentsCount--;
|
|
71
|
+
|
|
72
|
+
return true;
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
getSplit(name: string): ISplit | null {
|
|
@@ -82,7 +82,8 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
|
|
|
82
82
|
* The returned promise is resolved when the operation success
|
|
83
83
|
* or rejected if it fails (e.g., redis operation fails)
|
|
84
84
|
*/
|
|
85
|
-
addSplit(
|
|
85
|
+
addSplit(split: ISplit): Promise<boolean> {
|
|
86
|
+
const name = split.name;
|
|
86
87
|
const splitKey = this.keys.buildSplitKey(name);
|
|
87
88
|
return this.redis.get(splitKey).then(splitFromStorage => {
|
|
88
89
|
|
|
@@ -107,18 +108,9 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
|
|
|
107
108
|
}).then(() => true);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
/**
|
|
111
|
-
* Add a list of splits.
|
|
112
|
-
* The returned promise is resolved when the operation success
|
|
113
|
-
* or rejected if it fails (e.g., redis operation fails)
|
|
114
|
-
*/
|
|
115
|
-
addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
|
|
116
|
-
return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
111
|
/**
|
|
120
112
|
* Remove a given split.
|
|
121
|
-
* The returned promise is resolved when the operation success, with
|
|
113
|
+
* The returned promise is resolved when the operation success, with true or false indicating if the split existed (and was removed) or not.
|
|
122
114
|
* or rejected if it fails (e.g., redis operation fails).
|
|
123
115
|
*/
|
|
124
116
|
removeSplit(name: string) {
|
|
@@ -127,19 +119,10 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
|
|
|
127
119
|
return this._decrementCounts(split).then(() => this._updateFlagSets(name, split.sets));
|
|
128
120
|
}
|
|
129
121
|
}).then(() => {
|
|
130
|
-
return this.redis.del(this.keys.buildSplitKey(name));
|
|
122
|
+
return this.redis.del(this.keys.buildSplitKey(name)).then(status => status === 1);
|
|
131
123
|
});
|
|
132
124
|
}
|
|
133
125
|
|
|
134
|
-
/**
|
|
135
|
-
* Remove a list of splits.
|
|
136
|
-
* The returned promise is resolved when the operation success,
|
|
137
|
-
* or rejected if it fails (e.g., redis operation fails).
|
|
138
|
-
*/
|
|
139
|
-
removeSplits(names: string[]): Promise<any> {
|
|
140
|
-
return Promise.all(names.map(name => this.removeSplit(name)));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
126
|
/**
|
|
144
127
|
* Get split definition or null if it's not defined.
|
|
145
128
|
* Returned promise is rejected if redis operation fails.
|
|
@@ -66,7 +66,8 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
66
66
|
* The returned promise is resolved when the operation success
|
|
67
67
|
* or rejected if it fails (e.g., wrapper operation fails)
|
|
68
68
|
*/
|
|
69
|
-
addSplit(
|
|
69
|
+
addSplit(split: ISplit): Promise<boolean> {
|
|
70
|
+
const name = split.name;
|
|
70
71
|
const splitKey = this.keys.buildSplitKey(name);
|
|
71
72
|
return this.wrapper.get(splitKey).then(splitFromStorage => {
|
|
72
73
|
|
|
@@ -91,15 +92,6 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
91
92
|
}).then(() => true);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
/**
|
|
95
|
-
* Add a list of splits.
|
|
96
|
-
* The returned promise is resolved when the operation success
|
|
97
|
-
* or rejected if it fails (e.g., wrapper operation fails)
|
|
98
|
-
*/
|
|
99
|
-
addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
|
|
100
|
-
return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
95
|
/**
|
|
104
96
|
* Remove a given split.
|
|
105
97
|
* The returned promise is resolved when the operation success, with a boolean indicating if the split existed or not.
|
|
@@ -115,15 +107,6 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
115
107
|
});
|
|
116
108
|
}
|
|
117
109
|
|
|
118
|
-
/**
|
|
119
|
-
* Remove a list of splits.
|
|
120
|
-
* The returned promise is resolved when the operation success, with a boolean array indicating if the splits existed or not.
|
|
121
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
122
|
-
*/
|
|
123
|
-
removeSplits(names: string[]): Promise<void> { // @ts-ignore
|
|
124
|
-
return Promise.all(names.map(name => this.removeSplit(name)));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
110
|
/**
|
|
128
111
|
* Get split.
|
|
129
112
|
* The returned promise is resolved with the split definition or null if it's not defined,
|
package/src/storages/types.ts
CHANGED
|
@@ -177,11 +177,9 @@ export interface IPluggableStorageWrapper {
|
|
|
177
177
|
/** Splits cache */
|
|
178
178
|
|
|
179
179
|
export interface ISplitsCacheBase {
|
|
180
|
-
|
|
181
|
-
removeSplits(names: string[]): MaybeThenable<boolean[] | void>,
|
|
180
|
+
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): MaybeThenable<boolean>,
|
|
182
181
|
getSplit(name: string): MaybeThenable<ISplit | null>,
|
|
183
182
|
getSplits(names: string[]): MaybeThenable<Record<string, ISplit | null>>, // `fetchMany` in spec
|
|
184
|
-
setChangeNumber(changeNumber: number): MaybeThenable<boolean | void>,
|
|
185
183
|
// should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
|
|
186
184
|
getChangeNumber(): MaybeThenable<number>,
|
|
187
185
|
getAll(): MaybeThenable<ISplit[]>,
|
|
@@ -198,11 +196,9 @@ export interface ISplitsCacheBase {
|
|
|
198
196
|
}
|
|
199
197
|
|
|
200
198
|
export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
201
|
-
|
|
202
|
-
removeSplits(names: string[]): boolean[],
|
|
199
|
+
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean,
|
|
203
200
|
getSplit(name: string): ISplit | null,
|
|
204
201
|
getSplits(names: string[]): Record<string, ISplit | null>,
|
|
205
|
-
setChangeNumber(changeNumber: number): boolean | void,
|
|
206
202
|
getChangeNumber(): number,
|
|
207
203
|
getAll(): ISplit[],
|
|
208
204
|
getSplitNames(): string[],
|
|
@@ -215,11 +211,9 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
215
211
|
}
|
|
216
212
|
|
|
217
213
|
export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
218
|
-
|
|
219
|
-
removeSplits(names: string[]): Promise<boolean[] | void>,
|
|
214
|
+
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): Promise<boolean>,
|
|
220
215
|
getSplit(name: string): Promise<ISplit | null>,
|
|
221
216
|
getSplits(names: string[]): Promise<Record<string, ISplit | null>>,
|
|
222
|
-
setChangeNumber(changeNumber: number): Promise<boolean | void>,
|
|
223
217
|
getChangeNumber(): Promise<number>,
|
|
224
218
|
getAll(): Promise<ISplit[]>,
|
|
225
219
|
getSplitNames(): Promise<string[]>,
|
|
@@ -428,13 +422,13 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync,
|
|
|
428
422
|
*/
|
|
429
423
|
|
|
430
424
|
export interface IStorageBase<
|
|
431
|
-
TSplitsCache extends ISplitsCacheBase,
|
|
432
|
-
TSegmentsCache extends ISegmentsCacheBase,
|
|
433
|
-
TImpressionsCache extends IImpressionsCacheBase,
|
|
434
|
-
TImpressionsCountCache extends IImpressionCountsCacheBase,
|
|
435
|
-
TEventsCache extends IEventsCacheBase,
|
|
436
|
-
TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
|
|
437
|
-
TUniqueKeysCache extends IUniqueKeysCacheBase
|
|
425
|
+
TSplitsCache extends ISplitsCacheBase = ISplitsCacheBase,
|
|
426
|
+
TSegmentsCache extends ISegmentsCacheBase = ISegmentsCacheBase,
|
|
427
|
+
TImpressionsCache extends IImpressionsCacheBase = IImpressionsCacheBase,
|
|
428
|
+
TImpressionsCountCache extends IImpressionCountsCacheBase = IImpressionCountsCacheBase,
|
|
429
|
+
TEventsCache extends IEventsCacheBase = IEventsCacheBase,
|
|
430
|
+
TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync = ITelemetryCacheSync | ITelemetryCacheAsync,
|
|
431
|
+
TUniqueKeysCache extends IUniqueKeysCacheBase = IUniqueKeysCacheBase
|
|
438
432
|
> {
|
|
439
433
|
splits: TSplitsCache,
|
|
440
434
|
segments: TSegmentsCache,
|
package/src/storages/utils.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function fromObjectUpdaterFactory(
|
|
|
24
24
|
let startingUp = true;
|
|
25
25
|
|
|
26
26
|
return function objectUpdater() {
|
|
27
|
-
const splits:
|
|
27
|
+
const splits: ISplit[] = [];
|
|
28
28
|
let loadError = null;
|
|
29
29
|
let splitsMock: false | Record<string, ISplitPartial> = {};
|
|
30
30
|
try {
|
|
@@ -37,24 +37,23 @@ export function fromObjectUpdaterFactory(
|
|
|
37
37
|
if (!loadError && splitsMock) {
|
|
38
38
|
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
|
|
39
39
|
|
|
40
|
-
forOwn(splitsMock,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
]);
|
|
40
|
+
forOwn(splitsMock, (val, name) => {
|
|
41
|
+
// @ts-ignore Split changeNumber and seed is undefined in localhost mode
|
|
42
|
+
splits.push({
|
|
43
|
+
name,
|
|
44
|
+
status: 'ACTIVE',
|
|
45
|
+
killed: false,
|
|
46
|
+
trafficAllocation: 100,
|
|
47
|
+
defaultTreatment: CONTROL,
|
|
48
|
+
conditions: val.conditions || [],
|
|
49
|
+
configurations: val.configurations,
|
|
50
|
+
trafficTypeName: val.trafficTypeName
|
|
51
|
+
});
|
|
53
52
|
});
|
|
54
53
|
|
|
55
54
|
return Promise.all([
|
|
56
55
|
splitsCache.clear(), // required to sync removed splits from mock
|
|
57
|
-
splitsCache.
|
|
56
|
+
splitsCache.update(splits, [], Date.now())
|
|
58
57
|
]).then(() => {
|
|
59
58
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
60
59
|
|
|
@@ -22,8 +22,7 @@ export function splitsSyncTaskFactory(
|
|
|
22
22
|
splitChangesUpdaterFactory(
|
|
23
23
|
settings.log,
|
|
24
24
|
splitChangesFetcherFactory(fetchSplitChanges),
|
|
25
|
-
storage
|
|
26
|
-
storage.segments,
|
|
25
|
+
storage,
|
|
27
26
|
settings.sync.__splitFiltersValidation,
|
|
28
27
|
readiness.splits,
|
|
29
28
|
settings.startup.requestTimeoutBeforeReady,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ISegmentsCacheBase,
|
|
1
|
+
import { ISegmentsCacheBase, IStorageBase } from '../../../storages/types';
|
|
2
2
|
import { ISplitChangesFetcher } from '../fetchers/types';
|
|
3
3
|
import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
|
|
4
4
|
import { ISplitsEventEmitter } from '../../../readiness/types';
|
|
5
5
|
import { timeout } from '../../../utils/promise/timeout';
|
|
6
6
|
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
|
|
7
7
|
import { ILogger } from '../../../logger/types';
|
|
8
|
-
import { SYNC_SPLITS_FETCH,
|
|
8
|
+
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_UPDATE, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
9
9
|
import { startsWith } from '../../../utils/lang';
|
|
10
10
|
import { IN_SEGMENT } from '../../../utils/constants';
|
|
11
11
|
import { setToArray } from '../../../utils/lang/sets';
|
|
@@ -42,8 +42,8 @@ export function parseSegments({ conditions }: ISplit): Set<string> {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface ISplitMutations {
|
|
45
|
-
added:
|
|
46
|
-
removed:
|
|
45
|
+
added: ISplit[],
|
|
46
|
+
removed: ISplit[],
|
|
47
47
|
segments: string[]
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -77,13 +77,13 @@ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersV
|
|
|
77
77
|
const segments = new Set<string>();
|
|
78
78
|
const computed = entries.reduce((accum, split) => {
|
|
79
79
|
if (split.status === 'ACTIVE' && matchFilters(split, filters)) {
|
|
80
|
-
accum.added.push(
|
|
80
|
+
accum.added.push(split);
|
|
81
81
|
|
|
82
82
|
parseSegments(split).forEach((segmentName: string) => {
|
|
83
83
|
segments.add(segmentName);
|
|
84
84
|
});
|
|
85
85
|
} else {
|
|
86
|
-
accum.removed.push(split
|
|
86
|
+
accum.removed.push(split);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
return accum;
|
|
@@ -111,14 +111,14 @@ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersV
|
|
|
111
111
|
export function splitChangesUpdaterFactory(
|
|
112
112
|
log: ILogger,
|
|
113
113
|
splitChangesFetcher: ISplitChangesFetcher,
|
|
114
|
-
|
|
115
|
-
segments: ISegmentsCacheBase,
|
|
114
|
+
storage: Pick<IStorageBase, 'splits' | 'segments'>,
|
|
116
115
|
splitFiltersValidation: ISplitFiltersValidation,
|
|
117
116
|
splitsEventEmitter?: ISplitsEventEmitter,
|
|
118
117
|
requestTimeoutBeforeReady: number = 0,
|
|
119
118
|
retriesOnFailureBeforeReady: number = 0,
|
|
120
119
|
isClientSide?: boolean
|
|
121
120
|
): ISplitChangesUpdater {
|
|
121
|
+
const { splits, segments } = storage;
|
|
122
122
|
|
|
123
123
|
let startingUp = true;
|
|
124
124
|
|
|
@@ -128,16 +128,6 @@ export function splitChangesUpdaterFactory(
|
|
|
128
128
|
return promise;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/** Returns true if at least one split was updated */
|
|
132
|
-
function isThereUpdate(flagsChange: [boolean | void, void | boolean[], void | boolean[], boolean | void] | [any, any, any]) {
|
|
133
|
-
const [, added, removed] = flagsChange;
|
|
134
|
-
// There is at least one added or modified feature flag
|
|
135
|
-
if (added && added.some((update: boolean) => update)) return true;
|
|
136
|
-
// There is at least one removed feature flag
|
|
137
|
-
if (removed && removed.some((update: boolean) => update)) return true;
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
131
|
/**
|
|
142
132
|
* SplitChanges updater returns a promise that resolves with a `false` boolean value if it fails to fetch splits or synchronize them with the storage.
|
|
143
133
|
* Returned promise will not be rejected.
|
|
@@ -162,22 +152,17 @@ export function splitChangesUpdaterFactory(
|
|
|
162
152
|
|
|
163
153
|
const mutation = computeSplitsMutation(splitChanges.splits, splitFiltersValidation);
|
|
164
154
|
|
|
165
|
-
log.debug(
|
|
166
|
-
log.debug(SYNC_SPLITS_REMOVED, [mutation.removed.length]);
|
|
167
|
-
log.debug(SYNC_SPLITS_SEGMENTS, [mutation.segments.length]);
|
|
155
|
+
log.debug(SYNC_SPLITS_UPDATE, [mutation.added.length, mutation.removed.length, mutation.segments.length]);
|
|
168
156
|
|
|
169
157
|
// Write into storage
|
|
170
158
|
// @TODO call `setChangeNumber` only if the other storage operations have succeeded, in order to keep storage consistency
|
|
171
159
|
return Promise.all([
|
|
172
|
-
|
|
173
|
-
splits.setChangeNumber(splitChanges.till),
|
|
174
|
-
splits.addSplits(mutation.added),
|
|
175
|
-
splits.removeSplits(mutation.removed),
|
|
160
|
+
splits.update(mutation.added, mutation.removed, splitChanges.till),
|
|
176
161
|
segments.registerSegments(mutation.segments)
|
|
177
|
-
]).then((
|
|
162
|
+
]).then(([isThereUpdate]) => {
|
|
178
163
|
if (splitsEventEmitter) {
|
|
179
164
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
180
|
-
return Promise.resolve(!splitsEventEmitter.splitsArrived || (since !== splitChanges.till && isThereUpdate
|
|
165
|
+
return Promise.resolve(!splitsEventEmitter.splitsArrived || (since !== splitChanges.till && isThereUpdate && (isClientSide || checkAllSegmentsExist(segments))))
|
|
181
166
|
.catch(() => false /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
|
|
182
167
|
.then(emitSplitsArrivedEvent => {
|
|
183
168
|
// emit SDK events
|
|
@@ -25,8 +25,9 @@ export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.Impr
|
|
|
25
25
|
m: entry.time, // Timestamp
|
|
26
26
|
c: entry.changeNumber, // ChangeNumber
|
|
27
27
|
r: sendLabels ? entry.label : undefined, // Rule
|
|
28
|
-
b: entry.bucketingKey
|
|
29
|
-
pt: entry.pt
|
|
28
|
+
b: entry.bucketingKey, // Bucketing Key
|
|
29
|
+
pt: entry.pt, // Previous time
|
|
30
|
+
properties: entry.properties // Properties
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
return keyImpression;
|
|
@@ -3,26 +3,30 @@ import { IMetadata } from '../../dtos/types';
|
|
|
3
3
|
import SplitIO from '../../../types/splitio';
|
|
4
4
|
import { ISyncTask } from '../types';
|
|
5
5
|
|
|
6
|
+
type ImpressionPayload = {
|
|
7
|
+
/** Matching Key */
|
|
8
|
+
k: string;
|
|
9
|
+
/** Bucketing Key */
|
|
10
|
+
b?: string;
|
|
11
|
+
/** Treatment */
|
|
12
|
+
t: string;
|
|
13
|
+
/** Timestamp */
|
|
14
|
+
m: number;
|
|
15
|
+
/** Change number */
|
|
16
|
+
c: number;
|
|
17
|
+
/** Rule label */
|
|
18
|
+
r?: string;
|
|
19
|
+
/** Previous time */
|
|
20
|
+
pt?: number;
|
|
21
|
+
/** Stringified JSON object with properties */
|
|
22
|
+
properties?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
6
25
|
export type ImpressionsPayload = {
|
|
7
26
|
/** Split name */
|
|
8
27
|
f: string,
|
|
9
28
|
/** Key Impressions */
|
|
10
|
-
i:
|
|
11
|
-
/** User Key */
|
|
12
|
-
k: string;
|
|
13
|
-
/** Treatment */
|
|
14
|
-
t: string;
|
|
15
|
-
/** Timestamp */
|
|
16
|
-
m: number;
|
|
17
|
-
/** ChangeNumber */
|
|
18
|
-
c: number;
|
|
19
|
-
/** Rule label */
|
|
20
|
-
r?: string;
|
|
21
|
-
/** Bucketing Key */
|
|
22
|
-
b?: string;
|
|
23
|
-
/** Previous time */
|
|
24
|
-
pt?: number;
|
|
25
|
-
}[]
|
|
29
|
+
i: ImpressionPayload[]
|
|
26
30
|
}[]
|
|
27
31
|
|
|
28
32
|
export type ImpressionCountsPayload = {
|
|
@@ -60,23 +64,9 @@ export type StoredImpressionWithMetadata = {
|
|
|
60
64
|
/** Metadata */
|
|
61
65
|
m: IMetadata,
|
|
62
66
|
/** Stored impression */
|
|
63
|
-
i: {
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
/** bucketingKey */
|
|
67
|
-
b?: string,
|
|
68
|
-
/** Split name */
|
|
69
|
-
f: string,
|
|
70
|
-
/** treatment */
|
|
71
|
-
t: string,
|
|
72
|
-
/** label */
|
|
73
|
-
r: string,
|
|
74
|
-
/** changeNumber */
|
|
75
|
-
c: number,
|
|
76
|
-
/** time */
|
|
77
|
-
m: number
|
|
78
|
-
/** previous time */
|
|
79
|
-
pt?: number
|
|
67
|
+
i: ImpressionPayload & {
|
|
68
|
+
/** Feature flag name */
|
|
69
|
+
f: string
|
|
80
70
|
}
|
|
81
71
|
}
|
|
82
72
|
|
|
@@ -18,6 +18,9 @@ export function strategyOptimizedFactory(
|
|
|
18
18
|
|
|
19
19
|
return {
|
|
20
20
|
process(impression: SplitIO.ImpressionDTO) {
|
|
21
|
+
// DEBUG mode without previous time, for impressions with properties
|
|
22
|
+
if (impression.properties) return true;
|
|
23
|
+
|
|
21
24
|
impression.pt = impressionsObserver.testAndSet(impression);
|
|
22
25
|
|
|
23
26
|
const now = Date.now();
|
|
@@ -66,3 +66,13 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth
|
|
|
66
66
|
|
|
67
67
|
return output;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
export function validateEvaluationOptions(log: ILogger, maybeOptions: any, method: string): SplitIO.EvaluationOptions | undefined {
|
|
71
|
+
if (isObject(maybeOptions)) {
|
|
72
|
+
const properties = validateEventProperties(log, maybeOptions.properties, method).properties;
|
|
73
|
+
return properties && Object.keys(properties).length > 0 ? { properties } : undefined;
|
|
74
|
+
} else if (maybeOptions) {
|
|
75
|
+
log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'evaluation options']);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
@@ -11,3 +11,4 @@ export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
|
11
11
|
export { validateSplitExistence } from './splitExistence';
|
|
12
12
|
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
13
|
export { validatePreloadedData } from './preloadedData';
|
|
14
|
+
export { validateEvaluationOptions } from './eventProperties';
|