@splitsoftware/splitio-commons 1.17.1-rc.2 → 1.17.1-rc.4
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 +4 -3
- package/cjs/readiness/readinessManager.js +13 -2
- package/cjs/sdkClient/sdkClientMethodCS.js +0 -1
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +0 -1
- package/cjs/sdkFactory/index.js +27 -11
- package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
- package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
- package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
- package/cjs/storages/dataLoader.js +32 -65
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
- package/cjs/storages/inLocalStorage/index.js +1 -4
- package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -17
- package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/cjs/sync/offline/syncManagerOffline.js +18 -11
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +7 -2
- package/cjs/sync/polling/pollingManagerSS.js +3 -3
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
- package/cjs/sync/syncManagerOnline.js +20 -21
- package/cjs/trackers/eventTracker.js +12 -10
- package/cjs/trackers/impressionsTracker.js +16 -14
- package/cjs/trackers/uniqueKeysTracker.js +5 -3
- package/cjs/utils/settingsValidation/storage/storageCS.js +12 -1
- package/esm/readiness/readinessManager.js +13 -2
- package/esm/sdkClient/sdkClientMethodCS.js +0 -1
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +0 -1
- package/esm/sdkFactory/index.js +28 -12
- package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
- package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
- package/esm/storages/AbstractSplitsCacheSync.js +7 -0
- package/esm/storages/dataLoader.js +30 -62
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
- package/esm/storages/inLocalStorage/index.js +2 -5
- package/esm/storages/inMemory/InMemoryStorageCS.js +5 -17
- package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/esm/sync/offline/syncManagerOffline.js +18 -11
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +8 -3
- package/esm/sync/polling/pollingManagerSS.js +3 -3
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
- package/esm/sync/syncManagerOnline.js +20 -21
- package/esm/trackers/eventTracker.js +12 -10
- package/esm/trackers/impressionsTracker.js +16 -14
- package/esm/trackers/uniqueKeysTracker.js +5 -3
- package/esm/utils/settingsValidation/storage/storageCS.js +10 -0
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +12 -3
- package/src/readiness/types.ts +3 -0
- package/src/sdkClient/sdkClientMethodCS.ts +0 -2
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +0 -2
- package/src/sdkFactory/index.ts +30 -14
- package/src/sdkFactory/types.ts +2 -0
- package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
- package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
- package/src/storages/AbstractSplitsCacheSync.ts +8 -0
- package/src/storages/dataLoader.ts +32 -63
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -1
- package/src/storages/inLocalStorage/index.ts +2 -6
- package/src/storages/inMemory/InMemoryStorageCS.ts +5 -20
- package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
- package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
- package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
- package/src/storages/types.ts +9 -10
- package/src/sync/offline/syncManagerOffline.ts +21 -13
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +7 -3
- package/src/sync/polling/pollingManagerSS.ts +2 -3
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
- package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -3
- package/src/sync/syncManagerOnline.ts +17 -17
- package/src/sync/types.ts +1 -1
- package/src/trackers/eventTracker.ts +11 -8
- package/src/trackers/impressionsTracker.ts +13 -10
- package/src/trackers/types.ts +1 -0
- package/src/trackers/uniqueKeysTracker.ts +6 -4
- package/src/types.ts +8 -9
- package/src/utils/settingsValidation/storage/storageCS.ts +13 -0
- package/types/readiness/types.d.ts +3 -0
- package/types/sdkFactory/types.d.ts +1 -0
- package/types/storages/AbstractSplitsCacheAsync.d.ts +5 -0
- package/types/storages/AbstractSplitsCacheSync.d.ts +5 -0
- package/types/storages/dataLoader.d.ts +6 -17
- package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +6 -0
- package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
- package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
- package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
- package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
- package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
- package/types/storages/types.d.ts +7 -10
- package/types/sync/types.d.ts +1 -1
- package/types/trackers/eventTracker.d.ts +1 -1
- package/types/trackers/impressionsTracker.d.ts +1 -1
- package/types/trackers/types.d.ts +1 -0
- package/types/types.d.ts +8 -8
- package/types/utils/settingsValidation/storage/storageCS.d.ts +5 -0
|
@@ -1,86 +1,55 @@
|
|
|
1
1
|
import { SplitIO } from '../types';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
|
|
3
|
+
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
6
|
+
* Factory of client-side storage loader
|
|
8
7
|
*
|
|
9
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
10
|
-
*
|
|
11
|
-
* @
|
|
12
|
-
*
|
|
13
|
-
* @TODO extend to load largeSegments
|
|
14
|
-
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
15
|
-
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
16
|
-
* @TODO unit tests
|
|
8
|
+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
9
|
+
* and extended with a `mySegmentsData` property.
|
|
10
|
+
* @returns function to preload the storage
|
|
17
11
|
*/
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
16
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
17
|
+
*
|
|
18
|
+
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
19
|
+
* @param userId user key string of the provided MySegmentsCache
|
|
20
|
+
*
|
|
21
|
+
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
22
|
+
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
23
|
+
*/
|
|
24
|
+
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
|
|
25
|
+
// Do not load data if current preloadedData is empty
|
|
26
|
+
if (Object.keys(preloadedData).length === 0) return;
|
|
27
|
+
|
|
28
|
+
const { lastUpdated = -1, segmentsData = {}, since = -1, splitsData = {} } = preloadedData;
|
|
23
29
|
|
|
24
|
-
if (storage.splits) {
|
|
25
30
|
const storedSince = storage.splits.getChangeNumber();
|
|
31
|
+
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
26
32
|
|
|
27
|
-
// Do not load data if current data is more recent
|
|
28
|
-
if
|
|
33
|
+
// Do not load data if current localStorage data is more recent,
|
|
34
|
+
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
35
|
+
if (storedSince > since || lastUpdated < expirationTimestamp) return;
|
|
29
36
|
|
|
30
37
|
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
31
38
|
storage.splits.clear();
|
|
32
39
|
storage.splits.setChangeNumber(since);
|
|
33
40
|
|
|
34
41
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
35
|
-
storage.splits.addSplits(splitsData.map(
|
|
36
|
-
}
|
|
42
|
+
storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])));
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[
|
|
44
|
+
// add mySegments data
|
|
45
|
+
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
|
|
40
46
|
if (!mySegmentsData) {
|
|
41
47
|
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
|
|
42
48
|
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
|
|
43
|
-
const
|
|
44
|
-
return
|
|
49
|
+
const userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
50
|
+
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
45
51
|
});
|
|
46
52
|
}
|
|
47
53
|
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
|
|
48
|
-
} else { // add segments data (server-side)
|
|
49
|
-
Object.keys(segmentsData).filter(segmentName => {
|
|
50
|
-
const userKeys = segmentsData[segmentName];
|
|
51
|
-
storage.segments.addToSegment(segmentName, userKeys);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function getSnapshot(storage: IStorageSync, userKeys?: string[]): SplitIO.PreloadedData {
|
|
57
|
-
return {
|
|
58
|
-
// lastUpdated: Date.now(),
|
|
59
|
-
// @ts-ignore accessing private prop
|
|
60
|
-
since: storage.splits.changeNumber,
|
|
61
|
-
splitsData: storage.splits.getAll(),
|
|
62
|
-
segmentsData: userKeys ?
|
|
63
|
-
undefined : // @ts-ignore accessing private prop
|
|
64
|
-
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
|
|
65
|
-
prev[cur] = setToArray(storage.segments.segmentCache[cur] as ISet<string>);
|
|
66
|
-
return prev;
|
|
67
|
-
}, {}),
|
|
68
|
-
mySegmentsData: userKeys ?
|
|
69
|
-
userKeys.reduce((prev, userKey) => {
|
|
70
|
-
// @ts-ignore accessing private prop
|
|
71
|
-
prev[userKey] = storage.shared ?
|
|
72
|
-
// Client-side segments
|
|
73
|
-
// @ts-ignore accessing private prop
|
|
74
|
-
Object.keys(storage.shared(userKey).segments.segmentCache) :
|
|
75
|
-
// Server-side segments
|
|
76
|
-
// @ts-ignore accessing private prop
|
|
77
|
-
Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop
|
|
78
|
-
return storage.segments.segmentCache[segmentName].has(userKey) ?
|
|
79
|
-
prev.concat(segmentName) :
|
|
80
|
-
prev;
|
|
81
|
-
}, []);
|
|
82
|
-
return prev;
|
|
83
|
-
}, {}) :
|
|
84
|
-
undefined
|
|
85
54
|
};
|
|
86
55
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ILogger } from '../../logger/types';
|
|
2
2
|
import { isNaNNumber } from '../../utils/lang';
|
|
3
|
-
import {
|
|
3
|
+
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
4
4
|
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
5
5
|
import { LOG_PREFIX, DEFINED } from './constants';
|
|
6
6
|
|
|
7
|
-
export class MySegmentsCacheInLocal extends
|
|
7
|
+
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
8
8
|
|
|
9
9
|
private readonly keys: MySegmentsKeyBuilder;
|
|
10
10
|
private readonly log: ILogger;
|
|
@@ -16,7 +16,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
16
16
|
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
protected addSegment(name: string): boolean {
|
|
20
20
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
21
21
|
|
|
22
22
|
try {
|
|
@@ -29,7 +29,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
protected removeSegment(name: string): boolean {
|
|
33
33
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
34
34
|
|
|
35
35
|
try {
|
|
@@ -81,7 +81,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
81
81
|
return 1;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
setChangeNumber(
|
|
84
|
+
protected setChangeNumber(changeNumber?: number) {
|
|
85
85
|
try {
|
|
86
86
|
if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
87
87
|
else localStorage.removeItem(this.keys.buildTillKey());
|
|
@@ -217,6 +217,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Check if the splits information is already stored in browser LocalStorage.
|
|
222
|
+
* In this function we could add more code to check if the data is valid.
|
|
223
|
+
* @override
|
|
224
|
+
*/
|
|
225
|
+
checkCache(): boolean {
|
|
226
|
+
return this.getChangeNumber() > -1;
|
|
227
|
+
}
|
|
228
|
+
|
|
220
229
|
/**
|
|
221
230
|
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
222
231
|
*
|
|
@@ -241,7 +250,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
241
250
|
this.updateNewFilter = true;
|
|
242
251
|
|
|
243
252
|
// if there is cache, clear it
|
|
244
|
-
if (this.
|
|
253
|
+
if (this.checkCache()) this.clear();
|
|
245
254
|
|
|
246
255
|
} catch (e) {
|
|
247
256
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -12,7 +12,7 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
|
|
|
12
12
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
13
13
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
14
14
|
import { LOG_PREFIX } from './constants';
|
|
15
|
-
import { DEBUG,
|
|
15
|
+
import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
16
16
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
17
17
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
18
18
|
import { getMatching } from '../../utils/key';
|
|
@@ -36,7 +36,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
36
36
|
return InMemoryStorageCSFactory(params);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const {
|
|
39
|
+
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
40
40
|
const matchingKey = getMatching(settings.core.key);
|
|
41
41
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
42
42
|
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
@@ -45,10 +45,6 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
45
45
|
const segments = new MySegmentsCacheInLocal(log, keys);
|
|
46
46
|
const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
47
47
|
|
|
48
|
-
if (settings.mode === LOCALHOST_MODE || splits.getChangeNumber() > -1) {
|
|
49
|
-
Promise.resolve().then(onReadyFromCacheCb);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
48
|
return {
|
|
53
49
|
splits,
|
|
54
50
|
segments,
|
|
@@ -7,8 +7,6 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
|
7
7
|
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
9
|
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
|
|
10
|
-
import { getMatching } from '../../utils/key';
|
|
11
|
-
import { loadData } from '../dataLoader';
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
@@ -16,7 +14,7 @@ import { loadData } from '../dataLoader';
|
|
|
16
14
|
* @param params parameters required by EventsCacheSync
|
|
17
15
|
*/
|
|
18
16
|
export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
|
|
19
|
-
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation }
|
|
17
|
+
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
20
18
|
|
|
21
19
|
const splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
22
20
|
const segments = new MySegmentsCacheInMemory();
|
|
@@ -44,18 +42,11 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
44
42
|
},
|
|
45
43
|
|
|
46
44
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
47
|
-
shared(
|
|
48
|
-
const segments = new MySegmentsCacheInMemory();
|
|
49
|
-
const largeSegments = new MySegmentsCacheInMemory();
|
|
50
|
-
|
|
51
|
-
if (preloadedData) {
|
|
52
|
-
loadData(preloadedData, { segments, largeSegments }, matchingKey);
|
|
53
|
-
}
|
|
54
|
-
|
|
45
|
+
shared() {
|
|
55
46
|
return {
|
|
56
47
|
splits: this.splits,
|
|
57
|
-
segments,
|
|
58
|
-
largeSegments,
|
|
48
|
+
segments: new MySegmentsCacheInMemory(),
|
|
49
|
+
largeSegments: new MySegmentsCacheInMemory(),
|
|
59
50
|
impressions: this.impressions,
|
|
60
51
|
impressionCounts: this.impressionCounts,
|
|
61
52
|
events: this.events,
|
|
@@ -72,7 +63,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
72
63
|
};
|
|
73
64
|
|
|
74
65
|
// @TODO revisit storage logic in localhost mode
|
|
75
|
-
// No tracking
|
|
66
|
+
// No tracking in localhost mode to avoid memory leaks: https://github.com/splitio/javascript-commons/issues/181
|
|
76
67
|
if (params.settings.mode === LOCALHOST_MODE) {
|
|
77
68
|
const noopTrack = () => true;
|
|
78
69
|
storage.impressions.track = noopTrack;
|
|
@@ -81,12 +72,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
81
72
|
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
|
|
82
73
|
}
|
|
83
74
|
|
|
84
|
-
|
|
85
|
-
if (preloadedData) {
|
|
86
|
-
loadData(preloadedData, storage, getMatching(params.settings.core.key));
|
|
87
|
-
if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
75
|
return storage;
|
|
91
76
|
}
|
|
92
77
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Default MySegmentsCacheInMemory implementation that stores MySegments in memory.
|
|
5
5
|
* Supported by all JS runtimes.
|
|
6
6
|
*/
|
|
7
|
-
export class MySegmentsCacheInMemory extends
|
|
7
|
+
export class MySegmentsCacheInMemory extends AbstractMySegmentsCacheSync {
|
|
8
8
|
|
|
9
9
|
private segmentCache: Record<string, boolean> = {};
|
|
10
10
|
private cn?: number;
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
protected addSegment(name: string): boolean {
|
|
13
13
|
if (this.segmentCache[name]) return false;
|
|
14
14
|
|
|
15
15
|
this.segmentCache[name] = true;
|
|
@@ -17,7 +17,7 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
protected removeSegment(name: string): boolean {
|
|
21
21
|
if (!this.segmentCache[name]) return false;
|
|
22
22
|
|
|
23
23
|
delete this.segmentCache[name];
|
|
@@ -30,7 +30,7 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
setChangeNumber(
|
|
33
|
+
protected setChangeNumber(changeNumber?: number) {
|
|
34
34
|
this.cn = changeNumber;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -1,36 +1,25 @@
|
|
|
1
|
-
import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync';
|
|
2
1
|
import { ISet, _Set } from '../../utils/lang/sets';
|
|
3
2
|
import { isIntegerNumber } from '../../utils/lang';
|
|
3
|
+
import { ISegmentsCacheSync } from '../types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Default ISplitsCacheSync implementation that stores
|
|
7
|
-
* Supported by all JS runtimes.
|
|
6
|
+
* Default ISplitsCacheSync implementation for server-side that stores segments definitions in memory.
|
|
8
7
|
*/
|
|
9
|
-
export class SegmentsCacheInMemory
|
|
8
|
+
export class SegmentsCacheInMemory implements ISegmentsCacheSync {
|
|
10
9
|
|
|
11
10
|
private segmentCache: Record<string, ISet<string>> = {};
|
|
12
11
|
private segmentChangeNumber: Record<string, number> = {};
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const keySet = values ? values : new _Set<string>();
|
|
13
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
|
|
14
|
+
const keySet = this.segmentCache[name] || new _Set<string>();
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
addedKeys.forEach(k => keySet.add(k));
|
|
17
|
+
removedKeys.forEach(k => keySet.delete(k));
|
|
19
18
|
|
|
20
19
|
this.segmentCache[name] = keySet;
|
|
20
|
+
this.segmentChangeNumber[name] = changeNumber;
|
|
21
21
|
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
removeFromSegment(name: string, segmentKeys: string[]): boolean {
|
|
26
|
-
const values = this.segmentCache[name];
|
|
27
|
-
const keySet = values ? values : new _Set<string>();
|
|
28
|
-
|
|
29
|
-
segmentKeys.forEach(k => keySet.delete(k));
|
|
30
|
-
|
|
31
|
-
this.segmentCache[name] = keySet;
|
|
32
|
-
|
|
33
|
-
return true;
|
|
22
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
34
23
|
}
|
|
35
24
|
|
|
36
25
|
isInSegment(name: string, key: string): boolean {
|
|
@@ -74,16 +63,13 @@ export class SegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
74
63
|
}, 0);
|
|
75
64
|
}
|
|
76
65
|
|
|
77
|
-
setChangeNumber(name: string, changeNumber: number) {
|
|
78
|
-
this.segmentChangeNumber[name] = changeNumber;
|
|
79
|
-
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
66
|
getChangeNumber(name: string) {
|
|
84
67
|
const value = this.segmentChangeNumber[name];
|
|
85
68
|
|
|
86
69
|
return isIntegerNumber(value) ? value : -1;
|
|
87
70
|
}
|
|
88
71
|
|
|
72
|
+
// No-op. Not used in server-side
|
|
73
|
+
resetSegments() { return false; }
|
|
74
|
+
|
|
89
75
|
}
|
|
@@ -17,24 +17,21 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
|
|
|
17
17
|
this.keys = keys;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
|
|
22
|
+
* The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
|
|
23
|
+
* or rejected if it fails (e.g., Redis operation fails).
|
|
24
|
+
*/
|
|
25
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
|
|
21
26
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
removeFromSegment(name: string, segmentKeys: string[]) {
|
|
31
|
-
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
32
|
-
|
|
33
|
-
if (segmentKeys.length) {
|
|
34
|
-
return this.redis.srem(segmentKey, segmentKeys).then(() => true);
|
|
35
|
-
} else {
|
|
36
|
-
return Promise.resolve(true);
|
|
37
|
-
}
|
|
28
|
+
return Promise.all([
|
|
29
|
+
addedKeys.length && this.redis.sadd(segmentKey, addedKeys),
|
|
30
|
+
removedKeys.length && this.redis.srem(segmentKey, removedKeys),
|
|
31
|
+
this.redis.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
|
|
32
|
+
]).then(() => {
|
|
33
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
34
|
+
});
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
isInSegment(name: string, key: string) {
|
|
@@ -43,12 +40,6 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
|
|
|
43
40
|
).then(matches => matches !== 0);
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
setChangeNumber(name: string, changeNumber: number) {
|
|
47
|
-
return this.redis.set(
|
|
48
|
-
this.keys.buildSegmentTillKey(name), changeNumber + ''
|
|
49
|
-
).then(status => status === 'OK');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
43
|
getChangeNumber(name: string) {
|
|
53
44
|
return this.redis.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => {
|
|
54
45
|
const i = parseInt(value as string, 10);
|
|
@@ -23,33 +23,20 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
* The returned promise is resolved
|
|
28
|
-
* or rejected if wrapper operation fails.
|
|
29
|
-
*/
|
|
30
|
-
addToSegment(name: string, segmentKeys: string[]) {
|
|
31
|
-
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
32
|
-
|
|
33
|
-
if (segmentKeys.length) {
|
|
34
|
-
return this.wrapper.addItems(segmentKey, segmentKeys);
|
|
35
|
-
} else {
|
|
36
|
-
return Promise.resolve();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Remove a list of `segmentKeys` from the given segment `name`.
|
|
42
|
-
* The returned promise is resolved when the operation success
|
|
43
|
-
* or rejected if wrapper operation fails.
|
|
26
|
+
* Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
|
|
27
|
+
* The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
|
|
28
|
+
* or rejected if it fails (e.g., wrapper operation fails).
|
|
44
29
|
*/
|
|
45
|
-
|
|
30
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
|
|
46
31
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
47
32
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
return Promise.all<any>([
|
|
34
|
+
addedKeys.length && this.wrapper.addItems(segmentKey, addedKeys),
|
|
35
|
+
removedKeys.length && this.wrapper.removeItems(segmentKey, removedKeys),
|
|
36
|
+
this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
|
|
37
|
+
]).then(() => {
|
|
38
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
39
|
+
});
|
|
53
40
|
}
|
|
54
41
|
|
|
55
42
|
/**
|
|
@@ -60,17 +47,6 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
|
|
|
60
47
|
return this.wrapper.itemContains(this.keys.buildSegmentNameKey(name), key);
|
|
61
48
|
}
|
|
62
49
|
|
|
63
|
-
/**
|
|
64
|
-
* Set till number for the given segment `name`.
|
|
65
|
-
* The returned promise is resolved when the operation success,
|
|
66
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
67
|
-
*/
|
|
68
|
-
setChangeNumber(name: string, changeNumber: number) {
|
|
69
|
-
return this.wrapper.set(
|
|
70
|
-
this.keys.buildSegmentTillKey(name), changeNumber + ''
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
50
|
/**
|
|
75
51
|
* Get till number or -1 if it's not defined.
|
|
76
52
|
* The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.
|
package/src/storages/types.ts
CHANGED
|
@@ -208,6 +208,8 @@ export interface ISplitsCacheBase {
|
|
|
208
208
|
// only for Client-Side. Returns true if the storage is not synchronized yet (getChangeNumber() === -1) or contains a FF using segments or large segments
|
|
209
209
|
usesSegments(): MaybeThenable<boolean>,
|
|
210
210
|
clear(): MaybeThenable<boolean | void>,
|
|
211
|
+
// should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
|
|
212
|
+
checkCache(): MaybeThenable<boolean>,
|
|
211
213
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
|
|
212
214
|
getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>
|
|
213
215
|
}
|
|
@@ -224,6 +226,7 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
224
226
|
trafficTypeExists(trafficType: string): boolean,
|
|
225
227
|
usesSegments(): boolean,
|
|
226
228
|
clear(): void,
|
|
229
|
+
checkCache(): boolean,
|
|
227
230
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
|
|
228
231
|
getNamesByFlagSets(flagSets: string[]): ISet<string>[]
|
|
229
232
|
}
|
|
@@ -240,6 +243,7 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
240
243
|
trafficTypeExists(trafficType: string): Promise<boolean>,
|
|
241
244
|
usesSegments(): Promise<boolean>,
|
|
242
245
|
clear(): Promise<boolean | void>,
|
|
246
|
+
checkCache(): Promise<boolean>,
|
|
243
247
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
|
|
244
248
|
getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
|
|
245
249
|
}
|
|
@@ -247,38 +251,32 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
247
251
|
/** Segments cache */
|
|
248
252
|
|
|
249
253
|
export interface ISegmentsCacheBase {
|
|
250
|
-
addToSegment(name: string, segmentKeys: string[]): MaybeThenable<boolean | void> // different signature on Server and Client-Side
|
|
251
|
-
removeFromSegment(name: string, segmentKeys: string[]): MaybeThenable<boolean | void> // different signature on Server and Client-Side
|
|
252
254
|
isInSegment(name: string, key?: string): MaybeThenable<boolean> // different signature on Server and Client-Side
|
|
253
255
|
registerSegments(names: string[]): MaybeThenable<boolean | void> // only for Server-Side
|
|
254
256
|
getRegisteredSegments(): MaybeThenable<string[]> // only for Server-Side
|
|
255
|
-
setChangeNumber(name: string, changeNumber: number): MaybeThenable<boolean | void> // only for Server-Side
|
|
256
257
|
getChangeNumber(name: string): MaybeThenable<number> // only for Server-Side
|
|
258
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): MaybeThenable<boolean> // only for Server-Side
|
|
257
259
|
clear(): MaybeThenable<boolean | void>
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
// Same API for both variants: SegmentsCache and MySegmentsCache (client-side API)
|
|
261
263
|
export interface ISegmentsCacheSync extends ISegmentsCacheBase {
|
|
262
|
-
addToSegment(name: string, segmentKeys?: string[]): boolean
|
|
263
|
-
removeFromSegment(name: string, segmentKeys?: string[]): boolean
|
|
264
264
|
isInSegment(name: string, key?: string): boolean
|
|
265
265
|
registerSegments(names: string[]): boolean
|
|
266
266
|
getRegisteredSegments(): string[]
|
|
267
267
|
getKeysCount(): number // only used for telemetry
|
|
268
|
-
setChangeNumber(name: string, changeNumber: number): boolean | void
|
|
269
268
|
getChangeNumber(name?: string): number
|
|
269
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): boolean // only for Server-Side
|
|
270
270
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean // only for Sync Client-Side
|
|
271
271
|
clear(): void
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
export interface ISegmentsCacheAsync extends ISegmentsCacheBase {
|
|
275
|
-
addToSegment(name: string, segmentKeys: string[]): Promise<boolean | void>
|
|
276
|
-
removeFromSegment(name: string, segmentKeys: string[]): Promise<boolean | void>
|
|
277
275
|
isInSegment(name: string, key: string): Promise<boolean>
|
|
278
276
|
registerSegments(names: string[]): Promise<boolean | void>
|
|
279
277
|
getRegisteredSegments(): Promise<string[]>
|
|
280
|
-
setChangeNumber(name: string, changeNumber: number): Promise<boolean | void>
|
|
281
278
|
getChangeNumber(name: string): Promise<number>
|
|
279
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): Promise<boolean>
|
|
282
280
|
clear(): Promise<boolean | void>
|
|
283
281
|
}
|
|
284
282
|
|
|
@@ -491,6 +489,8 @@ export interface IStorageAsync extends IStorageBase<
|
|
|
491
489
|
|
|
492
490
|
/** StorageFactory */
|
|
493
491
|
|
|
492
|
+
export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
|
|
493
|
+
|
|
494
494
|
export interface IStorageFactoryParams {
|
|
495
495
|
settings: ISettings,
|
|
496
496
|
/**
|
|
@@ -498,7 +498,6 @@ export interface IStorageFactoryParams {
|
|
|
498
498
|
* It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
|
|
499
499
|
*/
|
|
500
500
|
onReadyCb: (error?: any) => void,
|
|
501
|
-
onReadyFromCacheCb: (error?: any) => void,
|
|
502
501
|
}
|
|
503
502
|
|
|
504
503
|
export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ISyncManagerCS } from '../types';
|
|
2
2
|
import { fromObjectSyncTaskFactory } from './syncTasks/fromObjectSyncTask';
|
|
3
3
|
import { objectAssign } from '../../utils/lang/objectAssign';
|
|
4
4
|
import { ISplitsParser } from './splitsParser/types';
|
|
@@ -29,26 +29,34 @@ export function syncManagerOfflineFactory(
|
|
|
29
29
|
storage,
|
|
30
30
|
}: ISdkFactoryContextSync): ISyncManagerCS {
|
|
31
31
|
|
|
32
|
+
const mainSyncManager = fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings);
|
|
33
|
+
const mainStart = mainSyncManager.start;
|
|
34
|
+
const sharedStarts: Array<() => void> = [];
|
|
35
|
+
|
|
32
36
|
return objectAssign(
|
|
33
|
-
|
|
37
|
+
mainSyncManager,
|
|
34
38
|
{
|
|
39
|
+
start() {
|
|
40
|
+
mainStart();
|
|
41
|
+
sharedStarts.forEach(cb => cb());
|
|
42
|
+
sharedStarts.length = 0;
|
|
43
|
+
},
|
|
35
44
|
// fake flush, that resolves immediately
|
|
36
45
|
flush,
|
|
37
46
|
|
|
38
47
|
// [Only used for client-side]
|
|
39
|
-
shared(matchingKey: string, readinessManager: IReadinessManager)
|
|
48
|
+
shared(matchingKey: string, readinessManager: IReadinessManager) {
|
|
49
|
+
// In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
|
|
50
|
+
// SDK_READY cannot be emitted directly because this will not update the readiness status
|
|
51
|
+
function emitSdkReady() {
|
|
52
|
+
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (mainSyncManager.isRunning()) setTimeout(emitSdkReady);
|
|
56
|
+
else sharedStarts.push(emitSdkReady);
|
|
57
|
+
|
|
40
58
|
return {
|
|
41
|
-
start() {
|
|
42
|
-
// In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
|
|
43
|
-
// SDK_READY cannot be emitted directly because this will not update the readiness status
|
|
44
|
-
setTimeout(() => {
|
|
45
|
-
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
|
|
46
|
-
}, 0);
|
|
47
|
-
},
|
|
48
59
|
stop() { },
|
|
49
|
-
isRunning() {
|
|
50
|
-
return true;
|
|
51
|
-
},
|
|
52
60
|
flush,
|
|
53
61
|
};
|
|
54
62
|
}
|