@splitsoftware/splitio-commons 2.2.1-rc.3 → 2.2.1-rc.5
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 +5 -2
- package/README.md +1 -0
- package/cjs/consent/sdkUserConsent.js +5 -3
- package/cjs/evaluator/combiners/and.js +2 -6
- package/cjs/evaluator/combiners/ifelseif.js +6 -6
- package/cjs/evaluator/condition/index.js +6 -5
- package/cjs/evaluator/index.js +7 -7
- package/cjs/evaluator/matchers/index.js +3 -1
- package/cjs/evaluator/matchers/matcherTypes.js +1 -0
- package/cjs/evaluator/matchers/rbsegment.js +56 -0
- package/cjs/evaluator/matchersTransform/index.js +4 -0
- package/cjs/evaluator/parser/index.js +2 -2
- package/cjs/evaluator/value/sanitize.js +1 -0
- package/cjs/listeners/browser.js +2 -5
- package/cjs/logger/constants.js +4 -3
- package/cjs/logger/messages/debug.js +3 -2
- package/cjs/logger/messages/warn.js +1 -1
- package/cjs/services/splitApi.js +3 -4
- package/cjs/services/splitHttpClient.js +3 -1
- package/cjs/storages/AbstractSplitsCacheSync.js +5 -2
- package/cjs/storages/KeyBuilder.js +9 -0
- package/cjs/storages/KeyBuilderCS.js +3 -0
- package/cjs/storages/KeyBuilderSS.js +3 -0
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -14
- package/cjs/storages/inLocalStorage/index.js +5 -1
- package/cjs/storages/inLocalStorage/validateCache.js +2 -1
- package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
- package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +1 -0
- package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
- package/cjs/storages/inRedis/index.js +5 -3
- package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
- package/cjs/storages/pluggable/index.js +2 -0
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +54 -4
- package/cjs/sync/polling/pollingManagerCS.js +7 -7
- package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +59 -33
- package/cjs/sync/streaming/SSEHandler/index.js +1 -0
- package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
- package/cjs/sync/streaming/constants.js +2 -1
- package/cjs/sync/streaming/pushManager.js +3 -16
- package/cjs/sync/syncManagerOnline.js +2 -2
- package/cjs/utils/constants/index.js +6 -2
- package/esm/consent/sdkUserConsent.js +5 -3
- package/esm/evaluator/combiners/and.js +2 -6
- package/esm/evaluator/combiners/ifelseif.js +7 -7
- package/esm/evaluator/condition/index.js +6 -5
- package/esm/evaluator/index.js +7 -7
- package/esm/evaluator/matchers/index.js +3 -1
- package/esm/evaluator/matchers/matcherTypes.js +1 -0
- package/esm/evaluator/matchers/rbsegment.js +52 -0
- package/esm/evaluator/matchersTransform/index.js +4 -0
- package/esm/evaluator/parser/index.js +2 -2
- package/esm/evaluator/value/sanitize.js +1 -0
- package/esm/listeners/browser.js +2 -5
- package/esm/logger/constants.js +1 -0
- package/esm/logger/messages/debug.js +3 -2
- package/esm/logger/messages/warn.js +1 -1
- package/esm/services/splitApi.js +3 -4
- package/esm/services/splitHttpClient.js +3 -1
- package/esm/storages/AbstractSplitsCacheSync.js +5 -2
- package/esm/storages/KeyBuilder.js +9 -0
- package/esm/storages/KeyBuilderCS.js +3 -0
- package/esm/storages/KeyBuilderSS.js +3 -0
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -14
- package/esm/storages/inLocalStorage/index.js +5 -1
- package/esm/storages/inLocalStorage/validateCache.js +2 -1
- package/esm/storages/inMemory/InMemoryStorage.js +3 -0
- package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
- package/esm/storages/inMemory/SplitsCacheInMemory.js +1 -0
- package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
- package/esm/storages/inRedis/index.js +5 -3
- package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
- package/esm/storages/pluggable/index.js +2 -0
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +54 -4
- package/esm/sync/polling/pollingManagerCS.js +7 -7
- package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
- package/esm/sync/polling/updaters/splitChangesUpdater.js +59 -33
- package/esm/sync/streaming/SSEHandler/index.js +2 -1
- package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
- package/esm/sync/streaming/constants.js +1 -0
- package/esm/sync/streaming/pushManager.js +6 -19
- package/esm/sync/syncManagerOnline.js +2 -2
- package/esm/utils/constants/index.js +5 -1
- package/package.json +1 -1
- package/src/consent/sdkUserConsent.ts +3 -2
- package/src/dtos/types.ts +37 -8
- package/src/evaluator/Engine.ts +1 -1
- package/src/evaluator/combiners/and.ts +5 -4
- package/src/evaluator/combiners/ifelseif.ts +7 -9
- package/src/evaluator/condition/engineUtils.ts +1 -1
- package/src/evaluator/condition/index.ts +12 -12
- package/src/evaluator/index.ts +7 -7
- package/src/evaluator/matchers/index.ts +3 -1
- package/src/evaluator/matchers/matcherTypes.ts +1 -0
- package/src/evaluator/matchers/rbsegment.ts +74 -0
- package/src/evaluator/matchersTransform/index.ts +3 -0
- package/src/evaluator/parser/index.ts +3 -3
- package/src/evaluator/types.ts +2 -2
- package/src/evaluator/value/index.ts +2 -2
- package/src/evaluator/value/sanitize.ts +5 -4
- package/src/listeners/browser.ts +2 -3
- package/src/logger/constants.ts +1 -0
- package/src/logger/messages/debug.ts +3 -2
- package/src/logger/messages/warn.ts +1 -1
- package/src/sdkManager/index.ts +1 -1
- package/src/services/splitApi.ts +3 -4
- package/src/services/splitHttpClient.ts +3 -1
- package/src/services/types.ts +1 -1
- package/src/storages/AbstractSplitsCacheSync.ts +6 -3
- package/src/storages/KeyBuilder.ts +12 -0
- package/src/storages/KeyBuilderCS.ts +4 -0
- package/src/storages/KeyBuilderSS.ts +4 -0
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -14
- package/src/storages/inLocalStorage/index.ts +5 -1
- package/src/storages/inLocalStorage/validateCache.ts +3 -1
- package/src/storages/inMemory/InMemoryStorage.ts +3 -0
- package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
- package/src/storages/inMemory/SplitsCacheInMemory.ts +1 -0
- package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
- package/src/storages/inRedis/index.ts +5 -3
- package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
- package/src/storages/pluggable/index.ts +2 -0
- package/src/storages/types.ts +33 -1
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +65 -4
- package/src/sync/polling/fetchers/types.ts +1 -0
- package/src/sync/polling/pollingManagerCS.ts +7 -7
- package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -1
- package/src/sync/polling/types.ts +2 -2
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
- package/src/sync/polling/updaters/splitChangesUpdater.ts +70 -43
- package/src/sync/streaming/SSEHandler/index.ts +2 -1
- package/src/sync/streaming/SSEHandler/types.ts +2 -2
- package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
- package/src/sync/streaming/constants.ts +1 -0
- package/src/sync/streaming/parseUtils.ts +2 -2
- package/src/sync/streaming/pushManager.ts +6 -18
- package/src/sync/streaming/types.ts +3 -2
- package/src/sync/syncManagerOnline.ts +2 -2
- package/src/utils/constants/index.ts +6 -1
- package/src/utils/lang/index.ts +1 -1
|
@@ -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
|
+
}
|
|
@@ -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
|
|
@@ -34,7 +35,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
|
|
|
34
35
|
const prefix = validatePrefix(options.prefix);
|
|
35
36
|
|
|
36
37
|
function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync {
|
|
37
|
-
if (!RD) throw new Error('The SDK Redis storage is
|
|
38
|
+
if (!RD) throw new Error('The SDK Redis storage is unavailable. Make sure your runtime environment supports CommonJS (`require`) so the `ioredis` dependency can be imported.');
|
|
38
39
|
|
|
39
40
|
const { onReadyFromCacheCb, onReadyCb, settings, settings: { log } } = params;
|
|
40
41
|
const metadata = metadataBuilder(settings);
|
|
@@ -44,10 +45,10 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
|
|
|
44
45
|
const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient);
|
|
45
46
|
const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient);
|
|
46
47
|
|
|
47
|
-
// RedisAdapter
|
|
48
|
+
// RedisAdapter lets queue operations before connected
|
|
48
49
|
onReadyFromCacheCb();
|
|
49
50
|
|
|
50
|
-
//
|
|
51
|
+
// Subscription to Redis connect event in order to emit SDK_READY event on consumer mode
|
|
51
52
|
redisClient.on('connect', () => {
|
|
52
53
|
onReadyCb();
|
|
53
54
|
impressionCountsCache.start();
|
|
@@ -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,
|
package/src/storages/types.ts
CHANGED
|
@@ -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,
|
|
@@ -1,24 +1,85 @@
|
|
|
1
|
+
import { ISettings } from '../../../types';
|
|
2
|
+
import { ISplitChangesResponse } from '../../../dtos/types';
|
|
1
3
|
import { IFetchSplitChanges, IResponse } from '../../../services/types';
|
|
4
|
+
import { IStorageBase } from '../../../storages/types';
|
|
5
|
+
import { FLAG_SPEC_VERSION } from '../../../utils/constants';
|
|
6
|
+
import { base } from '../../../utils/settingsValidation';
|
|
2
7
|
import { ISplitChangesFetcher } from './types';
|
|
8
|
+
import { LOG_PREFIX_SYNC_SPLITS } from '../../../logger/constants';
|
|
9
|
+
|
|
10
|
+
const PROXY_CHECK_INTERVAL_MILLIS_CS = 60 * 60 * 1000; // 1 hour in Client Side
|
|
11
|
+
const PROXY_CHECK_INTERVAL_MILLIS_SS = 24 * PROXY_CHECK_INTERVAL_MILLIS_CS; // 24 hours in Server Side
|
|
12
|
+
|
|
13
|
+
function sdkEndpointOverridden(settings: ISettings) {
|
|
14
|
+
return settings.urls.sdk !== base.urls.sdk;
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
/**
|
|
5
18
|
* Factory of SplitChanges fetcher.
|
|
6
19
|
* SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors.
|
|
7
20
|
*/
|
|
8
|
-
|
|
21
|
+
// @TODO breaking: drop support for Split Proxy below v5.10.0 and simplify the implementation
|
|
22
|
+
export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges, settings: ISettings, storage: Pick<IStorageBase, 'splits' | 'rbSegments'>): ISplitChangesFetcher {
|
|
23
|
+
|
|
24
|
+
const log = settings.log;
|
|
25
|
+
const PROXY_CHECK_INTERVAL_MILLIS = settings.core.key !== undefined ? PROXY_CHECK_INTERVAL_MILLIS_CS : PROXY_CHECK_INTERVAL_MILLIS_SS;
|
|
26
|
+
let lastProxyCheckTimestamp: number | undefined;
|
|
9
27
|
|
|
10
28
|
return function splitChangesFetcher(
|
|
11
29
|
since: number,
|
|
12
30
|
noCache?: boolean,
|
|
13
31
|
till?: number,
|
|
32
|
+
rbSince?: number,
|
|
14
33
|
// Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
|
|
15
34
|
decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
|
|
16
|
-
) {
|
|
35
|
+
): Promise<ISplitChangesResponse> {
|
|
36
|
+
|
|
37
|
+
// Recheck proxy
|
|
38
|
+
if (lastProxyCheckTimestamp && (Date.now() - lastProxyCheckTimestamp) > PROXY_CHECK_INTERVAL_MILLIS) {
|
|
39
|
+
settings.sync.flagSpecVersion = FLAG_SPEC_VERSION;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let splitsPromise = fetchSplitChanges(since, noCache, till, settings.sync.flagSpecVersion === FLAG_SPEC_VERSION ? rbSince : undefined)
|
|
43
|
+
// Handle proxy error with spec 1.3
|
|
44
|
+
.catch((err) => {
|
|
45
|
+
if (err.statusCode === 400 && sdkEndpointOverridden(settings) && settings.sync.flagSpecVersion === FLAG_SPEC_VERSION) {
|
|
46
|
+
log.error(LOG_PREFIX_SYNC_SPLITS + 'Proxy error detected. Retrying with spec 1.2. If you are using Split Proxy, please upgrade to latest version');
|
|
47
|
+
lastProxyCheckTimestamp = Date.now();
|
|
48
|
+
settings.sync.flagSpecVersion = '1.2'; // fallback to 1.2 spec
|
|
49
|
+
return fetchSplitChanges(since, noCache, till); // retry request without rbSince
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
});
|
|
17
53
|
|
|
18
|
-
let splitsPromise = fetchSplitChanges(since, noCache, till);
|
|
19
54
|
if (decorator) splitsPromise = decorator(splitsPromise);
|
|
20
55
|
|
|
21
|
-
return splitsPromise
|
|
56
|
+
return splitsPromise
|
|
57
|
+
.then(resp => resp.json())
|
|
58
|
+
.then(data => {
|
|
59
|
+
// Using flag spec version 1.2
|
|
60
|
+
if (data.splits) {
|
|
61
|
+
return {
|
|
62
|
+
ff: {
|
|
63
|
+
d: data.splits,
|
|
64
|
+
s: data.since,
|
|
65
|
+
t: data.till
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Proxy recovery
|
|
71
|
+
if (lastProxyCheckTimestamp) {
|
|
72
|
+
log.info(LOG_PREFIX_SYNC_SPLITS + 'Proxy error recovered');
|
|
73
|
+
lastProxyCheckTimestamp = undefined;
|
|
74
|
+
return splitChangesFetcher(-1, undefined, undefined, -1)
|
|
75
|
+
.then((splitChangesResponse: ISplitChangesResponse) =>
|
|
76
|
+
Promise.all([storage.splits.clear(), storage.rbSegments.clear()])
|
|
77
|
+
.then(() => splitChangesResponse)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return data;
|
|
82
|
+
});
|
|
22
83
|
};
|
|
23
84
|
|
|
24
85
|
}
|
|
@@ -43,10 +43,10 @@ export function pollingManagerCSFactory(
|
|
|
43
43
|
// smart pausing
|
|
44
44
|
readiness.splits.on(SDK_SPLITS_ARRIVED, () => {
|
|
45
45
|
if (!splitsSyncTask.isRunning()) return; // noop if not doing polling
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
log.info(POLLING_SMART_PAUSING, [
|
|
49
|
-
if (
|
|
46
|
+
const usingSegments = storage.splits.usesSegments() || storage.rbSegments.usesSegments();
|
|
47
|
+
if (usingSegments !== mySegmentsSyncTask.isRunning()) {
|
|
48
|
+
log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
|
|
49
|
+
if (usingSegments) {
|
|
50
50
|
startMySegmentsSyncTasks();
|
|
51
51
|
} else {
|
|
52
52
|
stopMySegmentsSyncTasks();
|
|
@@ -59,9 +59,9 @@ export function pollingManagerCSFactory(
|
|
|
59
59
|
|
|
60
60
|
// smart ready
|
|
61
61
|
function smartReady() {
|
|
62
|
-
if (!readiness.isReady() && !storage.splits.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
62
|
+
if (!readiness.isReady() && !storage.splits.usesSegments() && !storage.rbSegments.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
63
63
|
}
|
|
64
|
-
if (!storage.splits.usesSegments()) setTimeout(smartReady, 0);
|
|
64
|
+
if (!storage.splits.usesSegments() && !storage.rbSegments.usesSegments()) setTimeout(smartReady, 0);
|
|
65
65
|
else readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
|
|
66
66
|
|
|
67
67
|
mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
|
|
@@ -77,7 +77,7 @@ export function pollingManagerCSFactory(
|
|
|
77
77
|
log.info(POLLING_START);
|
|
78
78
|
|
|
79
79
|
splitsSyncTask.start();
|
|
80
|
-
if (storage.splits.usesSegments()) startMySegmentsSyncTasks();
|
|
80
|
+
if (storage.splits.usesSegments() || storage.rbSegments.usesSegments()) startMySegmentsSyncTasks();
|
|
81
81
|
},
|
|
82
82
|
|
|
83
83
|
// Stop periodic fetching (polling)
|
|
@@ -21,7 +21,7 @@ export function splitsSyncTaskFactory(
|
|
|
21
21
|
settings.log,
|
|
22
22
|
splitChangesUpdaterFactory(
|
|
23
23
|
settings.log,
|
|
24
|
-
splitChangesFetcherFactory(fetchSplitChanges),
|
|
24
|
+
splitChangesFetcherFactory(fetchSplitChanges, settings, storage),
|
|
25
25
|
storage,
|
|
26
26
|
settings.sync.__splitFiltersValidation,
|
|
27
27
|
readiness.splits,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ISplit } from '../../dtos/types';
|
|
1
|
+
import { IRBSegment, ISplit } from '../../dtos/types';
|
|
2
2
|
import { IReadinessManager } from '../../readiness/types';
|
|
3
3
|
import { IStorageSync } from '../../storages/types';
|
|
4
4
|
import { MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../streaming/types';
|
|
5
5
|
import { ITask, ISyncTask } from '../types';
|
|
6
6
|
|
|
7
|
-
export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }], boolean> { }
|
|
7
|
+
export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit | IRBSegment, changeNumber: number }], boolean> { }
|
|
8
8
|
|
|
9
9
|
export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
|
|
10
10
|
|
|
@@ -27,7 +27,7 @@ export function mySegmentsUpdaterFactory(
|
|
|
27
27
|
matchingKey: string
|
|
28
28
|
): IMySegmentsUpdater {
|
|
29
29
|
|
|
30
|
-
const { splits, segments, largeSegments } = storage;
|
|
30
|
+
const { splits, rbSegments, segments, largeSegments } = storage;
|
|
31
31
|
let readyOnAlreadyExistentState = true;
|
|
32
32
|
let startingUp = true;
|
|
33
33
|
|
|
@@ -51,7 +51,7 @@ export function mySegmentsUpdaterFactory(
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Notify update if required
|
|
54
|
-
if (splits.usesSegments() && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
54
|
+
if ((splits.usesSegments() || rbSegments.usesSegments()) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
55
55
|
readyOnAlreadyExistentState = false;
|
|
56
56
|
segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
|
|
57
57
|
}
|
|
@@ -51,7 +51,7 @@ export function segmentChangesUpdaterFactory(
|
|
|
51
51
|
* Returned promise will not be rejected.
|
|
52
52
|
*
|
|
53
53
|
* @param fetchOnlyNew - if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
|
|
54
|
-
* This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
|
|
54
|
+
* This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE or RB_SEGMENT_UPDATE notifications.
|
|
55
55
|
* @param segmentName - segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
|
|
56
56
|
* @param noCache - true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
|
|
57
57
|
* @param till - till target for the provided segmentName, for CDN bypass.
|