@splitsoftware/splitio-commons 2.4.2-rc.2 → 2.5.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +2 -10
- package/cjs/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/cjs/storages/AbstractSplitsCacheSync.js +2 -3
- package/cjs/storages/dataLoader.js +102 -43
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +23 -20
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/cjs/storages/inLocalStorage/index.js +13 -28
- package/cjs/storages/inLocalStorage/validateCache.js +23 -25
- package/cjs/storages/inMemory/InMemoryStorageCS.js +32 -14
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/cjs/sync/syncManagerOnline.js +24 -28
- package/cjs/utils/env/isLocalStorageAvailable.js +5 -28
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/esm/storages/AbstractSplitsCacheSync.js +2 -3
- package/esm/storages/dataLoader.js +99 -41
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +23 -20
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/esm/storages/inLocalStorage/index.js +14 -29
- package/esm/storages/inLocalStorage/validateCache.js +23 -25
- package/esm/storages/inMemory/InMemoryStorageCS.js +32 -14
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/esm/sync/syncManagerOnline.js +24 -28
- package/esm/utils/env/isLocalStorageAvailable.js +3 -24
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/sdkFactory/index.ts +3 -2
- package/src/storages/AbstractMySegmentsCacheSync.ts +20 -26
- package/src/storages/AbstractSplitsCacheSync.ts +2 -3
- package/src/storages/dataLoader.ts +107 -49
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +17 -18
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +24 -22
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +37 -34
- package/src/storages/inLocalStorage/index.ts +16 -33
- package/src/storages/inLocalStorage/validateCache.ts +23 -26
- package/src/storages/inMemory/InMemoryStorageCS.ts +37 -14
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -0
- package/src/storages/types.ts +6 -20
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +2 -1
- package/src/sync/syncManagerOnline.ts +22 -27
- package/src/types.ts +0 -35
- package/src/utils/env/isLocalStorageAvailable.ts +3 -24
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +46 -27
- package/cjs/storages/inLocalStorage/storageAdapter.js +0 -48
- package/esm/storages/inLocalStorage/storageAdapter.js +0 -44
- package/src/storages/inLocalStorage/storageAdapter.ts +0 -50
|
@@ -8,10 +8,10 @@ var MILLIS_IN_A_DAY = 86400000;
|
|
|
8
8
|
*
|
|
9
9
|
* @returns `true` if cache should be cleared, `false` otherwise
|
|
10
10
|
*/
|
|
11
|
-
function validateExpiration(options,
|
|
11
|
+
function validateExpiration(options, settings, keys, currentTimestamp, isThereCache) {
|
|
12
12
|
var log = settings.log;
|
|
13
13
|
// Check expiration
|
|
14
|
-
var lastUpdatedTimestamp = parseInt(
|
|
14
|
+
var lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()), 10);
|
|
15
15
|
if (!isNaNNumber(lastUpdatedTimestamp)) {
|
|
16
16
|
var cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
17
17
|
var expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
@@ -22,11 +22,11 @@ function validateExpiration(options, storage, settings, keys, currentTimestamp,
|
|
|
22
22
|
}
|
|
23
23
|
// Check hash
|
|
24
24
|
var storageHashKey = keys.buildHashKey();
|
|
25
|
-
var storageHash =
|
|
25
|
+
var storageHash = localStorage.getItem(storageHashKey);
|
|
26
26
|
var currentStorageHash = getStorageHash(settings);
|
|
27
27
|
if (storageHash !== currentStorageHash) {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
localStorage.setItem(storageHashKey, currentStorageHash);
|
|
30
30
|
}
|
|
31
31
|
catch (e) {
|
|
32
32
|
log.error(LOG_PREFIX + e);
|
|
@@ -39,7 +39,7 @@ function validateExpiration(options, storage, settings, keys, currentTimestamp,
|
|
|
39
39
|
}
|
|
40
40
|
// Clear on init
|
|
41
41
|
if (options.clearOnInit) {
|
|
42
|
-
var lastClearTimestamp = parseInt(
|
|
42
|
+
var lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()), 10);
|
|
43
43
|
if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
44
44
|
log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
45
45
|
return true;
|
|
@@ -54,25 +54,23 @@ function validateExpiration(options, storage, settings, keys, currentTimestamp,
|
|
|
54
54
|
*
|
|
55
55
|
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
56
56
|
*/
|
|
57
|
-
export function validateCache(options,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
settings.log.error(LOG_PREFIX + e);
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
57
|
+
export function validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments) {
|
|
58
|
+
var currentTimestamp = Date.now();
|
|
59
|
+
var isThereCache = splits.getChangeNumber() > -1;
|
|
60
|
+
if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
|
|
61
|
+
splits.clear();
|
|
62
|
+
rbSegments.clear();
|
|
63
|
+
segments.clear();
|
|
64
|
+
largeSegments.clear();
|
|
65
|
+
// Update last clear timestamp
|
|
66
|
+
try {
|
|
67
|
+
localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
settings.log.error(LOG_PREFIX + e);
|
|
74
71
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Check if ready from cache
|
|
75
|
+
return isThereCache;
|
|
78
76
|
}
|
|
@@ -6,6 +6,8 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
|
6
6
|
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
7
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
8
8
|
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
|
|
9
|
+
import { getMatching } from '../../utils/key';
|
|
10
|
+
import { setCache } from '../dataLoader';
|
|
9
11
|
import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
|
|
10
12
|
/**
|
|
11
13
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
@@ -13,7 +15,8 @@ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
|
|
|
13
15
|
* @param params - parameters required by EventsCacheSync
|
|
14
16
|
*/
|
|
15
17
|
export function InMemoryStorageCSFactory(params) {
|
|
16
|
-
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, __splitFiltersValidation = _a.sync.__splitFiltersValidation;
|
|
18
|
+
var _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, __splitFiltersValidation = _a.sync.__splitFiltersValidation, preloadedData = _a.preloadedData, onReadyFromCacheCb = params.onReadyFromCacheCb;
|
|
19
|
+
var storages = {};
|
|
17
20
|
var splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
18
21
|
var rbSegments = new RBSegmentsCacheInMemory();
|
|
19
22
|
var segments = new MySegmentsCacheInMemory();
|
|
@@ -30,19 +33,27 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
30
33
|
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
31
34
|
destroy: function () { },
|
|
32
35
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
33
|
-
shared: function () {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
shared: function (matchingKey) {
|
|
37
|
+
if (!storages[matchingKey]) {
|
|
38
|
+
var segments_1 = new MySegmentsCacheInMemory();
|
|
39
|
+
var largeSegments_1 = new MySegmentsCacheInMemory();
|
|
40
|
+
if (preloadedData) {
|
|
41
|
+
setCache(log, preloadedData, { segments: segments_1, largeSegments: largeSegments_1 }, matchingKey);
|
|
42
|
+
}
|
|
43
|
+
storages[matchingKey] = {
|
|
44
|
+
splits: this.splits,
|
|
45
|
+
rbSegments: this.rbSegments,
|
|
46
|
+
segments: segments_1,
|
|
47
|
+
largeSegments: largeSegments_1,
|
|
48
|
+
impressions: this.impressions,
|
|
49
|
+
impressionCounts: this.impressionCounts,
|
|
50
|
+
events: this.events,
|
|
51
|
+
telemetry: this.telemetry,
|
|
52
|
+
uniqueKeys: this.uniqueKeys,
|
|
53
|
+
destroy: function () { }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return storages[matchingKey];
|
|
46
57
|
},
|
|
47
58
|
};
|
|
48
59
|
// @TODO revisit storage logic in localhost mode
|
|
@@ -54,6 +65,13 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
54
65
|
storage.impressionCounts.track = noopTrack;
|
|
55
66
|
storage.uniqueKeys.track = noopTrack;
|
|
56
67
|
}
|
|
68
|
+
var matchingKey = getMatching(params.settings.core.key);
|
|
69
|
+
storages[matchingKey] = storage;
|
|
70
|
+
if (preloadedData) {
|
|
71
|
+
setCache(log, preloadedData, storage, matchingKey);
|
|
72
|
+
if (splits.getChangeNumber() > -1)
|
|
73
|
+
onReadyFromCacheCb();
|
|
74
|
+
}
|
|
57
75
|
return storage;
|
|
58
76
|
}
|
|
59
77
|
InMemoryStorageCSFactory.type = STORAGE_MEMORY;
|
|
@@ -42,6 +42,10 @@ var RBSegmentsCacheInMemory = /** @class */ (function () {
|
|
|
42
42
|
RBSegmentsCacheInMemory.prototype.get = function (name) {
|
|
43
43
|
return this.cache[name] || null;
|
|
44
44
|
};
|
|
45
|
+
RBSegmentsCacheInMemory.prototype.getAll = function () {
|
|
46
|
+
var _this = this;
|
|
47
|
+
return this.getNames().map(function (key) { return _this.get(key); });
|
|
48
|
+
};
|
|
45
49
|
RBSegmentsCacheInMemory.prototype.contains = function (names) {
|
|
46
50
|
var namesArray = setToArray(names);
|
|
47
51
|
var namesInStorage = this.getNames();
|
|
@@ -42,9 +42,10 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
42
42
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
43
43
|
if (startingUp) {
|
|
44
44
|
startingUp = false;
|
|
45
|
-
|
|
45
|
+
var isCacheLoaded_1 = storage.validateCache ? storage.validateCache() : false;
|
|
46
|
+
Promise.resolve().then(function () {
|
|
46
47
|
// Emits SDK_READY_FROM_CACHE
|
|
47
|
-
if (
|
|
48
|
+
if (isCacheLoaded_1)
|
|
48
49
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
49
50
|
// Emits SDK_READY
|
|
50
51
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
@@ -66,39 +66,35 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
66
66
|
*/
|
|
67
67
|
start: function () {
|
|
68
68
|
running = true;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (pollingManager) {
|
|
81
|
-
// If synchronization is disabled pushManager and pollingManager should not start
|
|
82
|
-
if (syncEnabled) {
|
|
83
|
-
if (pushManager) {
|
|
84
|
-
// Doesn't call `syncAll` when the syncManager is resuming
|
|
85
|
-
if (startFirstTime) {
|
|
86
|
-
pollingManager.syncAll();
|
|
87
|
-
}
|
|
88
|
-
pushManager.start();
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
pollingManager.start();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
69
|
+
if (startFirstTime) {
|
|
70
|
+
var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
71
|
+
if (isCacheLoaded)
|
|
72
|
+
Promise.resolve().then(function () { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
73
|
+
}
|
|
74
|
+
// start syncing splits and segments
|
|
75
|
+
if (pollingManager) {
|
|
76
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
77
|
+
if (syncEnabled) {
|
|
78
|
+
if (pushManager) {
|
|
79
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
95
80
|
if (startFirstTime) {
|
|
96
81
|
pollingManager.syncAll();
|
|
97
82
|
}
|
|
83
|
+
pushManager.start();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
pollingManager.start();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
if (startFirstTime) {
|
|
91
|
+
pollingManager.syncAll();
|
|
98
92
|
}
|
|
99
93
|
}
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
}
|
|
95
|
+
// start periodic data recording (events, impressions, telemetry).
|
|
96
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
97
|
+
startFirstTime = false;
|
|
102
98
|
},
|
|
103
99
|
/**
|
|
104
100
|
* Method used to stop/pause the syncManager.
|
|
@@ -1,33 +1,12 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
1
2
|
export function isLocalStorageAvailable() {
|
|
2
|
-
try {
|
|
3
|
-
// eslint-disable-next-line no-undef
|
|
4
|
-
return isValidStorageWrapper(localStorage);
|
|
5
|
-
}
|
|
6
|
-
catch (e) {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export function isValidStorageWrapper(wrapper) {
|
|
11
3
|
var mod = '__SPLITSOFTWARE__';
|
|
12
4
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
wrapper.removeItem(mod);
|
|
5
|
+
localStorage.setItem(mod, mod);
|
|
6
|
+
localStorage.removeItem(mod);
|
|
16
7
|
return true;
|
|
17
8
|
}
|
|
18
9
|
catch (e) {
|
|
19
10
|
return false;
|
|
20
11
|
}
|
|
21
12
|
}
|
|
22
|
-
export function isWebStorage(wrapper) {
|
|
23
|
-
if (typeof wrapper.length === 'number') {
|
|
24
|
-
try {
|
|
25
|
-
wrapper.key(0);
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
catch (e) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
@@ -3,7 +3,7 @@ import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
|
|
|
3
3
|
import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
|
|
4
4
|
export function __InLocalStorageMockFactory(params) {
|
|
5
5
|
var result = InMemoryStorageCSFactory(params);
|
|
6
|
-
result.validateCache = function () { return
|
|
6
|
+
result.validateCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
9
9
|
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
package/package.json
CHANGED
package/src/sdkFactory/index.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
43
43
|
|
|
44
44
|
const storage = storageFactory({
|
|
45
45
|
settings,
|
|
46
|
-
onReadyCb
|
|
46
|
+
onReadyCb(error) {
|
|
47
47
|
if (error) {
|
|
48
48
|
// If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
|
|
49
49
|
readiness.timeout();
|
|
@@ -52,10 +52,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
52
52
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
53
53
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
54
54
|
},
|
|
55
|
-
onReadyFromCacheCb
|
|
55
|
+
onReadyFromCacheCb() {
|
|
56
56
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
|
+
|
|
59
60
|
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
60
61
|
const clients: Record<string, SplitIO.IBasicClient> = {};
|
|
61
62
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
@@ -49,10 +49,12 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
49
49
|
* For client-side synchronizer: it resets or updates the cache.
|
|
50
50
|
*/
|
|
51
51
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
|
|
52
|
+
this.setChangeNumber(segmentsData.cn);
|
|
53
|
+
|
|
52
54
|
const { added, removed } = segmentsData as MySegmentsData;
|
|
53
|
-
let isDiff = false;
|
|
54
55
|
|
|
55
56
|
if (added && removed) {
|
|
57
|
+
let isDiff = false;
|
|
56
58
|
|
|
57
59
|
added.forEach(segment => {
|
|
58
60
|
isDiff = this.addSegment(segment) || isDiff;
|
|
@@ -61,40 +63,32 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
61
63
|
removed.forEach(segment => {
|
|
62
64
|
isDiff = this.removeSegment(segment) || isDiff;
|
|
63
65
|
});
|
|
64
|
-
} else {
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
return isDiff;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
isDiff = false;
|
|
72
|
-
} else {
|
|
70
|
+
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
|
|
71
|
+
const storedSegmentKeys = this.getRegisteredSegments().sort();
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
// Extreme fast => everything is empty
|
|
74
|
+
if (!names.length && !storedSegmentKeys.length) return false;
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
let index = 0;
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
if (index === names.length && index === storedSegmentKeys.length) {
|
|
80
|
-
isDiff = false;
|
|
81
|
-
} else {
|
|
78
|
+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
86
|
-
}
|
|
80
|
+
// Quick path => no changes
|
|
81
|
+
if (index === names.length && index === storedSegmentKeys.length) return false;
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
// Slowest path => add and/or remove segments
|
|
84
|
+
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
85
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
86
|
+
}
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
88
|
+
for (let addIndex = index; addIndex < names.length; addIndex++) {
|
|
89
|
+
this.addSegment(names[addIndex]);
|
|
95
90
|
}
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
return isDiff;
|
|
92
|
+
return true;
|
|
99
93
|
}
|
|
100
94
|
}
|
|
@@ -14,10 +14,9 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
14
14
|
protected abstract setChangeNumber(changeNumber: number): boolean | void
|
|
15
15
|
|
|
16
16
|
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
|
|
17
|
-
let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
|
|
18
|
-
updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
|
|
19
17
|
this.setChangeNumber(changeNumber);
|
|
20
|
-
|
|
18
|
+
const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
|
|
19
|
+
return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
abstract getSplit(name: string): ISplit | null
|
|
@@ -1,55 +1,113 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import SplitIO from '../../types/splitio';
|
|
2
|
+
import { IRBSegmentsCacheSync, ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
|
|
3
|
+
import { setToArray } from '../utils/lang/sets';
|
|
4
|
+
import { getMatching } from '../utils/key';
|
|
5
|
+
import { IMembershipsResponse, IMySegmentsResponse, IRBSegment, ISplit } from '../dtos/types';
|
|
6
|
+
import { ILogger } from '../logger/types';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* and extended with a `mySegmentsData` property.
|
|
12
|
-
* @returns function to preload the storage
|
|
9
|
+
* Sets the given synchronous storage with the provided preloaded data snapshot.
|
|
10
|
+
* If `matchingKey` is provided, the storage is handled as a client-side storage (segments and largeSegments are instances of MySegmentsCache).
|
|
11
|
+
* Otherwise, the storage is handled as a server-side storage (segments is an instance of SegmentsCache).
|
|
13
12
|
*/
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
50
|
-
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
51
|
-
});
|
|
13
|
+
export function setCache(log: ILogger, preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, rbSegments?: IRBSegmentsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
|
|
14
|
+
// Do not load data if current preloadedData is empty
|
|
15
|
+
if (Object.keys(preloadedData).length === 0) return;
|
|
16
|
+
|
|
17
|
+
const { splits, rbSegments, segments, largeSegments } = storage;
|
|
18
|
+
|
|
19
|
+
log.debug(`set cache${matchingKey ? ` for key ${matchingKey}` : ''}`);
|
|
20
|
+
|
|
21
|
+
if (splits) {
|
|
22
|
+
splits.clear();
|
|
23
|
+
splits.update(preloadedData.flags as ISplit[] || [], [], preloadedData.since || -1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (rbSegments) {
|
|
27
|
+
rbSegments.clear();
|
|
28
|
+
rbSegments.update(preloadedData.rbSegments as IRBSegment[] || [], [], preloadedData.rbSince || -1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const segmentsData = preloadedData.segments || {};
|
|
32
|
+
if (matchingKey) { // add memberships data (client-side)
|
|
33
|
+
let memberships = preloadedData.memberships && preloadedData.memberships[matchingKey];
|
|
34
|
+
if (!memberships && segmentsData) {
|
|
35
|
+
memberships = {
|
|
36
|
+
ms: {
|
|
37
|
+
k: Object.keys(segmentsData).filter(segmentName => {
|
|
38
|
+
const segmentKeys = segmentsData[segmentName];
|
|
39
|
+
return segmentKeys.indexOf(matchingKey) > -1;
|
|
40
|
+
}).map(segmentName => ({ n: segmentName }))
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (memberships) {
|
|
46
|
+
if ((memberships as IMembershipsResponse).ms) segments.resetSegments((memberships as IMembershipsResponse).ms!);
|
|
47
|
+
if ((memberships as IMembershipsResponse).ls && largeSegments) largeSegments.resetSegments((memberships as IMembershipsResponse).ls!);
|
|
52
48
|
}
|
|
53
|
-
|
|
49
|
+
} else { // add segments data (server-side)
|
|
50
|
+
Object.keys(segmentsData).forEach(segmentName => {
|
|
51
|
+
const segmentKeys = segmentsData[segmentName];
|
|
52
|
+
segments.update(segmentName, segmentKeys, [], -1);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets the preloaded data snapshot from the given synchronous storage.
|
|
59
|
+
* If `keys` are provided, the memberships for those keys is returned, to protect segments data.
|
|
60
|
+
* Otherwise, the segments data is returned.
|
|
61
|
+
*/
|
|
62
|
+
export function getCache(log: ILogger, storage: IStorageSync, keys?: SplitIO.SplitKey[]): SplitIO.PreloadedData {
|
|
63
|
+
|
|
64
|
+
log.debug(`get cache${keys ? ` for keys ${keys}` : ''}`);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
since: storage.splits.getChangeNumber(),
|
|
68
|
+
flags: storage.splits.getAll(),
|
|
69
|
+
rbSince: storage.rbSegments.getChangeNumber(),
|
|
70
|
+
rbSegments: storage.rbSegments.getAll(),
|
|
71
|
+
segments: keys ?
|
|
72
|
+
undefined : // @ts-ignore accessing private prop
|
|
73
|
+
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
|
|
74
|
+
prev[cur] = setToArray(storage.segments.segmentCache[cur] as Set<string>);
|
|
75
|
+
return prev;
|
|
76
|
+
}, {}),
|
|
77
|
+
memberships: keys ?
|
|
78
|
+
keys.reduce<Record<string, IMembershipsResponse>>((prev, key) => {
|
|
79
|
+
if (storage.shared) {
|
|
80
|
+
// Client-side segments
|
|
81
|
+
// @ts-ignore accessing private prop
|
|
82
|
+
const sharedStorage = storage.shared(key);
|
|
83
|
+
prev[getMatching(key)] = {
|
|
84
|
+
ms: {
|
|
85
|
+
// @ts-ignore accessing private prop
|
|
86
|
+
k: Object.keys(sharedStorage.segments.segmentCache).map(segmentName => ({ n: segmentName })),
|
|
87
|
+
},
|
|
88
|
+
ls: sharedStorage.largeSegments ? {
|
|
89
|
+
// @ts-ignore accessing private prop
|
|
90
|
+
k: Object.keys(sharedStorage.largeSegments.segmentCache).map(segmentName => ({ n: segmentName })),
|
|
91
|
+
} : undefined
|
|
92
|
+
};
|
|
93
|
+
} else {
|
|
94
|
+
prev[getMatching(key)] = {
|
|
95
|
+
ms: {
|
|
96
|
+
// Server-side segments
|
|
97
|
+
// @ts-ignore accessing private prop
|
|
98
|
+
k: Object.keys(storage.segments.segmentCache).reduce<IMySegmentsResponse['k']>((prev, segmentName) => { // @ts-ignore accessing private prop
|
|
99
|
+
return storage.segments.segmentCache[segmentName].has(key) ?
|
|
100
|
+
prev!.concat({ n: segmentName }) :
|
|
101
|
+
prev;
|
|
102
|
+
}, [])
|
|
103
|
+
},
|
|
104
|
+
ls: {
|
|
105
|
+
k: []
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return prev;
|
|
110
|
+
}, {}) :
|
|
111
|
+
undefined
|
|
54
112
|
};
|
|
55
113
|
}
|