@splitsoftware/splitio-commons 1.17.1-rc.1 → 1.17.1-rc.2
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 +1 -0
- package/cjs/sdkFactory/index.js +3 -1
- package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
- package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
- package/cjs/storages/dataLoader.js +65 -32
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
- package/cjs/storages/inLocalStorage/index.js +4 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -7
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -12
- package/esm/sdkFactory/index.js +4 -2
- package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
- package/esm/storages/AbstractSplitsCacheSync.js +0 -7
- package/esm/storages/dataLoader.js +62 -30
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
- package/esm/storages/inLocalStorage/index.js +5 -2
- package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -8
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
- package/esm/utils/settingsValidation/storage/storageCS.js +0 -10
- package/package.json +1 -1
- package/src/sdkFactory/index.ts +5 -2
- package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
- package/src/storages/AbstractSplitsCacheSync.ts +0 -8
- package/src/storages/dataLoader.ts +63 -32
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -10
- package/src/storages/inLocalStorage/index.ts +6 -2
- package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
- package/src/storages/types.ts +1 -6
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +3 -7
- package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -11
- package/src/types.ts +9 -8
- package/src/utils/settingsValidation/storage/storageCS.ts +0 -13
- package/types/storages/AbstractSplitsCacheAsync.d.ts +0 -5
- package/types/storages/AbstractSplitsCacheSync.d.ts +0 -5
- package/types/storages/dataLoader.d.ts +17 -6
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -6
- package/types/storages/types.d.ts +1 -4
- package/types/types.d.ts +8 -8
- package/types/utils/settingsValidation/storage/storageCS.d.ts +0 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _Set, setToArray } from '../../../utils/lang/sets';
|
|
2
2
|
import { timeout } from '../../../utils/promise/timeout';
|
|
3
|
-
import { SDK_SPLITS_ARRIVED
|
|
3
|
+
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
4
4
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
5
5
|
import { startsWith } from '../../../utils/lang';
|
|
6
6
|
import { IN_SEGMENT } from '../../../utils/constants';
|
|
@@ -121,7 +121,7 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
|
|
|
121
121
|
function _splitChangesUpdater(since, retry) {
|
|
122
122
|
if (retry === void 0) { retry = 0; }
|
|
123
123
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
124
|
-
|
|
124
|
+
return Promise.resolve(splitUpdateNotification ?
|
|
125
125
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
126
126
|
splitChangesFetcher(since, noCache, till, _promiseDecorator))
|
|
127
127
|
.then(function (splitChanges) {
|
|
@@ -165,15 +165,6 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
|
|
|
165
165
|
}
|
|
166
166
|
return false;
|
|
167
167
|
});
|
|
168
|
-
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
169
|
-
// Wrapping in a promise since checkCache can be async.
|
|
170
|
-
if (splitsEventEmitter && startingUp) {
|
|
171
|
-
Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
|
|
172
|
-
if (isCacheReady)
|
|
173
|
-
splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
return fetcherPromise;
|
|
177
168
|
}
|
|
178
169
|
var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
179
170
|
return sincePromise.then(_splitChangesUpdater);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS';
|
|
2
2
|
import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
|
|
3
3
|
import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
|
|
4
|
-
export function __InLocalStorageMockFactory(params) {
|
|
5
|
-
var result = InMemoryStorageCSFactory(params);
|
|
6
|
-
result.splits.checkCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
|
|
7
|
-
return result;
|
|
8
|
-
}
|
|
9
|
-
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
|
10
4
|
/**
|
|
11
5
|
* This function validates `settings.storage` object
|
|
12
6
|
*
|
|
@@ -23,10 +17,6 @@ export function validateStorageCS(settings) {
|
|
|
23
17
|
storage = InMemoryStorageCSFactory;
|
|
24
18
|
log.error(ERROR_STORAGE_INVALID);
|
|
25
19
|
}
|
|
26
|
-
// In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
|
|
27
|
-
if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
|
|
28
|
-
return __InLocalStorageMockFactory;
|
|
29
|
-
}
|
|
30
20
|
if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
|
|
31
21
|
// Consumer modes require an async storage
|
|
32
22
|
if (storage.type !== STORAGE_PLUGGABLE)
|
package/package.json
CHANGED
package/src/sdkFactory/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { IBasicClient, SplitIO } from '../types';
|
|
|
7
7
|
import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
|
|
8
8
|
import { createLoggerAPI } from '../logger/sdkLogger';
|
|
9
9
|
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
|
|
10
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
10
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
11
11
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
12
12
|
import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
|
|
13
13
|
import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
|
|
@@ -46,8 +46,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
46
46
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
47
47
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
48
48
|
},
|
|
49
|
+
onReadyFromCacheCb: () => {
|
|
50
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
51
|
+
}
|
|
49
52
|
});
|
|
50
|
-
|
|
53
|
+
|
|
51
54
|
const clients: Record<string, IBasicClient> = {};
|
|
52
55
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
53
56
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -28,14 +28,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
|
|
|
28
28
|
return Promise.resolve(true);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* Check if the splits information is already stored in cache.
|
|
33
|
-
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
34
|
-
*/
|
|
35
|
-
checkCache(): Promise<boolean> {
|
|
36
|
-
return Promise.resolve(false);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
31
|
/**
|
|
40
32
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
41
33
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -48,14 +48,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
48
48
|
|
|
49
49
|
abstract clear(): void
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
53
|
-
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
54
|
-
*/
|
|
55
|
-
checkCache(): boolean {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
51
|
/**
|
|
60
52
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
61
53
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -1,55 +1,86 @@
|
|
|
1
1
|
import { SplitIO } from '../types';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
|
|
3
|
+
import { setToArray, ISet } from '../utils/lang/sets';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
7
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
7
8
|
*
|
|
8
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
9
|
+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
|
|
10
|
+
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
11
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
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
|
|
11
17
|
*/
|
|
12
|
-
export function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
|
18
|
+
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, userKey?: string) {
|
|
19
|
+
// Do not load data if current preloadedData is empty
|
|
20
|
+
if (Object.keys(preloadedData).length === 0) return;
|
|
21
|
+
|
|
22
|
+
const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
|
|
29
23
|
|
|
24
|
+
if (storage.splits) {
|
|
30
25
|
const storedSince = storage.splits.getChangeNumber();
|
|
31
|
-
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
32
26
|
|
|
33
|
-
// Do not load data if current
|
|
34
|
-
|
|
35
|
-
if (storedSince > since || lastUpdated < expirationTimestamp) return;
|
|
27
|
+
// Do not load data if current data is more recent
|
|
28
|
+
if (storedSince > since) return;
|
|
36
29
|
|
|
37
30
|
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
38
31
|
storage.splits.clear();
|
|
39
32
|
storage.splits.setChangeNumber(since);
|
|
40
33
|
|
|
41
34
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
42
|
-
storage.splits.addSplits(
|
|
35
|
+
storage.splits.addSplits(splitsData.map(split => ([split.name, split])));
|
|
36
|
+
}
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[
|
|
38
|
+
if (userKey) { // add mySegments data (client-side)
|
|
39
|
+
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
|
|
46
40
|
if (!mySegmentsData) {
|
|
47
41
|
// 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
|
|
48
42
|
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
|
|
49
|
-
const
|
|
50
|
-
return
|
|
43
|
+
const userKeys = segmentsData[segmentName];
|
|
44
|
+
return userKeys.indexOf(userKey) > -1;
|
|
51
45
|
});
|
|
52
46
|
}
|
|
53
47
|
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
|
|
54
85
|
};
|
|
55
86
|
}
|
|
@@ -217,15 +217,6 @@ 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
|
-
|
|
229
220
|
/**
|
|
230
221
|
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
231
222
|
*
|
|
@@ -250,7 +241,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
250
241
|
this.updateNewFilter = true;
|
|
251
242
|
|
|
252
243
|
// if there is cache, clear it
|
|
253
|
-
if (this.
|
|
244
|
+
if (this.getChangeNumber() > -1) this.clear();
|
|
254
245
|
|
|
255
246
|
} catch (e) {
|
|
256
247
|
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, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
15
|
+
import { DEBUG, LOCALHOST_MODE, 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 { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
39
|
+
const { onReadyFromCacheCb, 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,6 +45,10 @@ 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
|
+
|
|
48
52
|
return {
|
|
49
53
|
splits,
|
|
50
54
|
segments,
|
|
@@ -7,6 +7,8 @@ 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';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
@@ -14,7 +16,7 @@ import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
|
|
|
14
16
|
* @param params parameters required by EventsCacheSync
|
|
15
17
|
*/
|
|
16
18
|
export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
|
|
17
|
-
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
|
|
19
|
+
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation }, preloadedData }, onReadyFromCacheCb } = params;
|
|
18
20
|
|
|
19
21
|
const splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
20
22
|
const segments = new MySegmentsCacheInMemory();
|
|
@@ -42,11 +44,18 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
42
44
|
},
|
|
43
45
|
|
|
44
46
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
45
|
-
shared() {
|
|
47
|
+
shared(matchingKey: string) {
|
|
48
|
+
const segments = new MySegmentsCacheInMemory();
|
|
49
|
+
const largeSegments = new MySegmentsCacheInMemory();
|
|
50
|
+
|
|
51
|
+
if (preloadedData) {
|
|
52
|
+
loadData(preloadedData, { segments, largeSegments }, matchingKey);
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
return {
|
|
47
56
|
splits: this.splits,
|
|
48
|
-
segments
|
|
49
|
-
largeSegments
|
|
57
|
+
segments,
|
|
58
|
+
largeSegments,
|
|
50
59
|
impressions: this.impressions,
|
|
51
60
|
impressionCounts: this.impressionCounts,
|
|
52
61
|
events: this.events,
|
|
@@ -72,6 +81,12 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
72
81
|
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
|
|
73
82
|
}
|
|
74
83
|
|
|
84
|
+
|
|
85
|
+
if (preloadedData) {
|
|
86
|
+
loadData(preloadedData, storage, getMatching(params.settings.core.key));
|
|
87
|
+
if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
return storage;
|
|
76
91
|
}
|
|
77
92
|
|
package/src/storages/types.ts
CHANGED
|
@@ -208,8 +208,6 @@ 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>,
|
|
213
211
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
|
|
214
212
|
getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>
|
|
215
213
|
}
|
|
@@ -226,7 +224,6 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
226
224
|
trafficTypeExists(trafficType: string): boolean,
|
|
227
225
|
usesSegments(): boolean,
|
|
228
226
|
clear(): void,
|
|
229
|
-
checkCache(): boolean,
|
|
230
227
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
|
|
231
228
|
getNamesByFlagSets(flagSets: string[]): ISet<string>[]
|
|
232
229
|
}
|
|
@@ -243,7 +240,6 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
243
240
|
trafficTypeExists(trafficType: string): Promise<boolean>,
|
|
244
241
|
usesSegments(): Promise<boolean>,
|
|
245
242
|
clear(): Promise<boolean | void>,
|
|
246
|
-
checkCache(): Promise<boolean>,
|
|
247
243
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
|
|
248
244
|
getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
|
|
249
245
|
}
|
|
@@ -495,8 +491,6 @@ export interface IStorageAsync extends IStorageBase<
|
|
|
495
491
|
|
|
496
492
|
/** StorageFactory */
|
|
497
493
|
|
|
498
|
-
export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
|
|
499
|
-
|
|
500
494
|
export interface IStorageFactoryParams {
|
|
501
495
|
settings: ISettings,
|
|
502
496
|
/**
|
|
@@ -504,6 +498,7 @@ export interface IStorageFactoryParams {
|
|
|
504
498
|
* It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
|
|
505
499
|
*/
|
|
506
500
|
onReadyCb: (error?: any) => void,
|
|
501
|
+
onReadyFromCacheCb: (error?: any) => void,
|
|
507
502
|
}
|
|
508
503
|
|
|
509
504
|
export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
|
|
@@ -7,7 +7,7 @@ import { syncTaskFactory } from '../../syncTask';
|
|
|
7
7
|
import { ISyncTask } from '../../types';
|
|
8
8
|
import { ISettings } from '../../../types';
|
|
9
9
|
import { CONTROL } from '../../../utils/constants';
|
|
10
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED
|
|
10
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
11
11
|
import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -60,12 +60,8 @@ export function fromObjectUpdaterFactory(
|
|
|
60
60
|
|
|
61
61
|
if (startingUp) {
|
|
62
62
|
startingUp = false;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
66
|
-
// Emits SDK_READY
|
|
67
|
-
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
68
|
-
});
|
|
63
|
+
// Emits SDK_READY
|
|
64
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
69
65
|
}
|
|
70
66
|
return true;
|
|
71
67
|
});
|
|
@@ -4,7 +4,7 @@ import { ISplitChangesFetcher } from '../fetchers/types';
|
|
|
4
4
|
import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
|
|
5
5
|
import { ISplitsEventEmitter } from '../../../readiness/types';
|
|
6
6
|
import { timeout } from '../../../utils/promise/timeout';
|
|
7
|
-
import { SDK_SPLITS_ARRIVED
|
|
7
|
+
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
8
8
|
import { ILogger } from '../../../logger/types';
|
|
9
9
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
10
10
|
import { startsWith } from '../../../utils/lang';
|
|
@@ -153,7 +153,8 @@ export function splitChangesUpdaterFactory(
|
|
|
153
153
|
*/
|
|
154
154
|
function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
|
|
155
155
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
156
|
-
|
|
156
|
+
|
|
157
|
+
return Promise.resolve(splitUpdateNotification ?
|
|
157
158
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
158
159
|
splitChangesFetcher(since, noCache, till, _promiseDecorator)
|
|
159
160
|
)
|
|
@@ -200,15 +201,6 @@ export function splitChangesUpdaterFactory(
|
|
|
200
201
|
}
|
|
201
202
|
return false;
|
|
202
203
|
});
|
|
203
|
-
|
|
204
|
-
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
205
|
-
// Wrapping in a promise since checkCache can be async.
|
|
206
|
-
if (splitsEventEmitter && startingUp) {
|
|
207
|
-
Promise.resolve(splits.checkCache()).then(isCacheReady => {
|
|
208
|
-
if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return fetcherPromise;
|
|
212
204
|
}
|
|
213
205
|
|
|
214
206
|
let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISplitFiltersValidation } from './dtos/types';
|
|
1
|
+
import { ISplit, ISplitFiltersValidation } from './dtos/types';
|
|
2
2
|
import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
|
|
3
3
|
import { ILogger } from './logger/types';
|
|
4
4
|
import { ISdkFactoryContext } from './sdkFactory/types';
|
|
@@ -98,6 +98,7 @@ export interface ISettings {
|
|
|
98
98
|
eventsFirstPushWindow: number
|
|
99
99
|
},
|
|
100
100
|
readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
|
|
101
|
+
readonly preloadedData?: SplitIO.PreloadedData,
|
|
101
102
|
readonly integrations: Array<{
|
|
102
103
|
readonly type: string,
|
|
103
104
|
(params: IIntegrationFactoryParams): IIntegration | void
|
|
@@ -771,21 +772,20 @@ export namespace SplitIO {
|
|
|
771
772
|
* If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
|
|
772
773
|
* @TODO configurable expiration time policy?
|
|
773
774
|
*/
|
|
774
|
-
lastUpdated: number,
|
|
775
|
+
// lastUpdated: number,
|
|
775
776
|
/**
|
|
776
777
|
* Change number of the preloaded data.
|
|
777
778
|
* If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
|
|
778
779
|
*/
|
|
779
780
|
since: number,
|
|
780
781
|
/**
|
|
781
|
-
*
|
|
782
|
+
* List of feature flag definitions.
|
|
783
|
+
* @TODO rename to flags
|
|
782
784
|
*/
|
|
783
|
-
splitsData:
|
|
784
|
-
[splitName: string]: string
|
|
785
|
-
},
|
|
785
|
+
splitsData: ISplit[],
|
|
786
786
|
/**
|
|
787
787
|
* Optional map of user keys to their list of segments.
|
|
788
|
-
* @TODO
|
|
788
|
+
* @TODO rename to memberships
|
|
789
789
|
*/
|
|
790
790
|
mySegmentsData?: {
|
|
791
791
|
[key: string]: string[]
|
|
@@ -793,9 +793,10 @@ export namespace SplitIO {
|
|
|
793
793
|
/**
|
|
794
794
|
* Optional map of segments to their stringified definitions.
|
|
795
795
|
* This property is ignored if `mySegmentsData` was provided.
|
|
796
|
+
* @TODO rename to segments
|
|
796
797
|
*/
|
|
797
798
|
segmentsData?: {
|
|
798
|
-
[segmentName: string]: string
|
|
799
|
+
[segmentName: string]: string[]
|
|
799
800
|
},
|
|
800
801
|
}
|
|
801
802
|
/**
|
|
@@ -3,14 +3,6 @@ import { ISettings, SDKMode } from '../../../types';
|
|
|
3
3
|
import { ILogger } from '../../../logger/types';
|
|
4
4
|
import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
|
|
5
5
|
import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
|
|
6
|
-
import { IStorageFactoryParams, IStorageSync } from '../../../storages/types';
|
|
7
|
-
|
|
8
|
-
export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSync {
|
|
9
|
-
const result = InMemoryStorageCSFactory(params);
|
|
10
|
-
result.splits.checkCache = () => true; // to emit SDK_READY_FROM_CACHE
|
|
11
|
-
return result;
|
|
12
|
-
}
|
|
13
|
-
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
|
14
6
|
|
|
15
7
|
/**
|
|
16
8
|
* This function validates `settings.storage` object
|
|
@@ -30,11 +22,6 @@ export function validateStorageCS(settings: { log: ILogger, storage?: any, mode:
|
|
|
30
22
|
log.error(ERROR_STORAGE_INVALID);
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
// In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
|
|
34
|
-
if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
|
|
35
|
-
return __InLocalStorageMockFactory;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
25
|
if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
|
|
39
26
|
// Consumer modes require an async storage
|
|
40
27
|
if (storage.type !== STORAGE_PLUGGABLE) throw new Error('A PluggableStorage instance is required on consumer mode');
|
|
@@ -19,11 +19,6 @@ export declare abstract class AbstractSplitsCacheAsync implements ISplitsCacheAs
|
|
|
19
19
|
abstract trafficTypeExists(trafficType: string): Promise<boolean>;
|
|
20
20
|
abstract clear(): Promise<boolean | void>;
|
|
21
21
|
usesSegments(): Promise<boolean>;
|
|
22
|
-
/**
|
|
23
|
-
* Check if the splits information is already stored in cache.
|
|
24
|
-
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
25
|
-
*/
|
|
26
|
-
checkCache(): Promise<boolean>;
|
|
27
22
|
/**
|
|
28
23
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
29
24
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -19,11 +19,6 @@ export declare abstract class AbstractSplitsCacheSync implements ISplitsCacheSyn
|
|
|
19
19
|
abstract trafficTypeExists(trafficType: string): boolean;
|
|
20
20
|
abstract usesSegments(): boolean;
|
|
21
21
|
abstract clear(): void;
|
|
22
|
-
/**
|
|
23
|
-
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
24
|
-
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
25
|
-
*/
|
|
26
|
-
checkCache(): boolean;
|
|
27
22
|
/**
|
|
28
23
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
29
24
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { SplitIO } from '../types';
|
|
2
|
-
import {
|
|
2
|
+
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
5
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
5
6
|
*
|
|
6
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
7
|
-
*
|
|
8
|
-
* @
|
|
7
|
+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
|
|
8
|
+
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
9
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
10
|
+
*
|
|
11
|
+
* @TODO extend to load largeSegments
|
|
12
|
+
* @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.
|
|
13
|
+
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
14
|
+
* @TODO unit tests
|
|
9
15
|
*/
|
|
10
|
-
export declare function
|
|
16
|
+
export declare function loadData(preloadedData: SplitIO.PreloadedData, storage: {
|
|
17
|
+
splits?: ISplitsCacheSync;
|
|
18
|
+
segments: ISegmentsCacheSync;
|
|
19
|
+
largeSegments?: ISegmentsCacheSync;
|
|
20
|
+
}, userKey?: string): void;
|
|
21
|
+
export declare function getSnapshot(storage: IStorageSync, userKeys?: string[]): SplitIO.PreloadedData;
|
|
@@ -35,12 +35,6 @@ export declare class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
35
35
|
getSplitNames(): string[];
|
|
36
36
|
trafficTypeExists(trafficType: string): boolean;
|
|
37
37
|
usesSegments(): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Check if the splits information is already stored in browser LocalStorage.
|
|
40
|
-
* In this function we could add more code to check if the data is valid.
|
|
41
|
-
* @override
|
|
42
|
-
*/
|
|
43
|
-
checkCache(): boolean;
|
|
44
38
|
/**
|
|
45
39
|
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
46
40
|
*
|
|
@@ -192,7 +192,6 @@ export interface ISplitsCacheBase {
|
|
|
192
192
|
trafficTypeExists(trafficType: string): MaybeThenable<boolean>;
|
|
193
193
|
usesSegments(): MaybeThenable<boolean>;
|
|
194
194
|
clear(): MaybeThenable<boolean | void>;
|
|
195
|
-
checkCache(): MaybeThenable<boolean>;
|
|
196
195
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>;
|
|
197
196
|
getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>;
|
|
198
197
|
}
|
|
@@ -208,7 +207,6 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
208
207
|
trafficTypeExists(trafficType: string): boolean;
|
|
209
208
|
usesSegments(): boolean;
|
|
210
209
|
clear(): void;
|
|
211
|
-
checkCache(): boolean;
|
|
212
210
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean;
|
|
213
211
|
getNamesByFlagSets(flagSets: string[]): ISet<string>[];
|
|
214
212
|
}
|
|
@@ -224,7 +222,6 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
224
222
|
trafficTypeExists(trafficType: string): Promise<boolean>;
|
|
225
223
|
usesSegments(): Promise<boolean>;
|
|
226
224
|
clear(): Promise<boolean | void>;
|
|
227
|
-
checkCache(): Promise<boolean>;
|
|
228
225
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>;
|
|
229
226
|
getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>;
|
|
230
227
|
}
|
|
@@ -394,7 +391,6 @@ export interface IStorageSync extends IStorageBase<ISplitsCacheSync, ISegmentsCa
|
|
|
394
391
|
export interface IStorageAsync extends IStorageBase<ISplitsCacheAsync, ISegmentsCacheAsync, IImpressionsCacheAsync | IImpressionsCacheSync, IImpressionCountsCacheBase, IEventsCacheAsync | IEventsCacheSync, ITelemetryCacheAsync | ITelemetryCacheSync, IUniqueKeysCacheBase> {
|
|
395
392
|
}
|
|
396
393
|
/** StorageFactory */
|
|
397
|
-
export declare type DataLoader = (storage: IStorageSync, matchingKey: string) => void;
|
|
398
394
|
export interface IStorageFactoryParams {
|
|
399
395
|
settings: ISettings;
|
|
400
396
|
/**
|
|
@@ -402,6 +398,7 @@ export interface IStorageFactoryParams {
|
|
|
402
398
|
* It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
|
|
403
399
|
*/
|
|
404
400
|
onReadyCb: (error?: any) => void;
|
|
401
|
+
onReadyFromCacheCb: (error?: any) => void;
|
|
405
402
|
}
|
|
406
403
|
export declare type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
|
|
407
404
|
export declare type IStorageSyncFactory = {
|