@splitsoftware/splitio-commons 2.0.0-rc.1 → 2.0.0-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 +3 -0
- package/cjs/sdkFactory/index.js +3 -1
- package/cjs/storages/dataLoader.js +99 -37
- package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
- package/esm/sdkFactory/index.js +4 -2
- package/esm/storages/dataLoader.js +96 -35
- package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
- package/package.json +1 -1
- package/src/sdkFactory/index.ts +6 -3
- package/src/storages/dataLoader.ts +97 -38
- package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
- package/src/storages/types.ts +4 -2
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
- package/src/types.ts +14 -13
- package/types/storages/dataLoader.d.ts +17 -6
- package/types/storages/types.d.ts +4 -1
- package/types/types.d.ts +13 -13
package/CHANGES.txt
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
2.0.0 (October XX, 2024)
|
|
2
2
|
- Added support for targeting rules based on large segments.
|
|
3
3
|
- Added `factory.destroy()` method, which invokes the `destroy` method on all SDK clients created by the factory.
|
|
4
|
+
- Added `factory.getState()` method for standalone server-side SDKs, which returns the rollout plan snapshot from the storage.
|
|
5
|
+
- Added `preloadedData` configuration option for standalone client-side SDKs, which allows preloading the SDK storage with a snapshot of the rollout plan.
|
|
6
|
+
- Updated internal storage factory to emit the SDK_READY_FROM_CACHE event when it corresponds, to clean up the initialization flow.
|
|
4
7
|
- Updated the handling of timers and async operations inside an `init` factory method to enable lazy initialization of the SDK in standalone mode. This update is intended for the React SDK.
|
|
5
8
|
- Bugfixing - Fixed an issue with the server-side polling manager that caused dangling timers when the SDK was destroyed before it was ready.
|
|
6
9
|
- BREAKING CHANGES:
|
package/cjs/sdkFactory/index.js
CHANGED
|
@@ -45,8 +45,10 @@ function sdkFactory(params) {
|
|
|
45
45
|
readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
|
|
46
46
|
readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
|
|
47
47
|
},
|
|
48
|
+
onReadyFromCacheCb: function () {
|
|
49
|
+
readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
|
|
50
|
+
}
|
|
48
51
|
});
|
|
49
|
-
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
50
52
|
var clients = {};
|
|
51
53
|
var telemetryTracker = (0, telemetryTracker_1.telemetryTrackerFactory)(storage.telemetry, platform.now);
|
|
52
54
|
var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage, telemetryTracker: telemetryTracker });
|
|
@@ -1,51 +1,113 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
var
|
|
3
|
+
exports.getSnapshot = exports.loadData = void 0;
|
|
4
|
+
var sets_1 = require("../utils/lang/sets");
|
|
5
|
+
var key_1 = require("../utils/key");
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
8
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
7
9
|
*
|
|
8
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
10
|
+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
|
|
11
|
+
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
12
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
13
|
+
*
|
|
14
|
+
* @TODO extend to load largeSegments
|
|
15
|
+
* @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.
|
|
16
|
+
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
17
|
+
* @TODO unit tests
|
|
11
18
|
*/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @param userId user key string of the provided MySegmentsCache
|
|
19
|
-
*
|
|
20
|
-
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
21
|
-
* @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.
|
|
22
|
-
*/
|
|
23
|
-
return function loadData(storage, userId) {
|
|
24
|
-
// Do not load data if current preloadedData is empty
|
|
25
|
-
if (Object.keys(preloadedData).length === 0)
|
|
26
|
-
return;
|
|
27
|
-
var _a = preloadedData.lastUpdated, lastUpdated = _a === void 0 ? -1 : _a, _b = preloadedData.segmentsData, segmentsData = _b === void 0 ? {} : _b, _c = preloadedData.since, since = _c === void 0 ? -1 : _c, _d = preloadedData.splitsData, splitsData = _d === void 0 ? {} : _d;
|
|
19
|
+
function loadData(preloadedData, storage, matchingKey) {
|
|
20
|
+
// Do not load data if current preloadedData is empty
|
|
21
|
+
if (Object.keys(preloadedData).length === 0)
|
|
22
|
+
return;
|
|
23
|
+
var _a = preloadedData.segmentsData, segmentsData = _a === void 0 ? {} : _a, _b = preloadedData.since, since = _b === void 0 ? -1 : _b, _c = preloadedData.splitsData, splitsData = _c === void 0 ? [] : _c;
|
|
24
|
+
if (storage.splits) {
|
|
28
25
|
var storedSince = storage.splits.getChangeNumber();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
32
|
-
if (storedSince > since || lastUpdated < expirationTimestamp)
|
|
26
|
+
// Do not load data if current data is more recent
|
|
27
|
+
if (storedSince > since)
|
|
33
28
|
return;
|
|
34
29
|
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
35
30
|
storage.splits.clear();
|
|
36
31
|
storage.splits.setChangeNumber(since);
|
|
37
32
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
38
|
-
storage.splits.addSplits(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
storage.splits.addSplits(splitsData.map(function (split) { return ([split.name, split]); }));
|
|
34
|
+
}
|
|
35
|
+
if (matchingKey) { // add mySegments data (client-side)
|
|
36
|
+
var membershipsData = preloadedData.membershipsData && preloadedData.membershipsData[matchingKey];
|
|
37
|
+
if (!membershipsData && segmentsData) {
|
|
38
|
+
membershipsData = {
|
|
39
|
+
ms: {
|
|
40
|
+
k: Object.keys(segmentsData).filter(function (segmentName) {
|
|
41
|
+
var segmentKeys = segmentsData[segmentName];
|
|
42
|
+
return segmentKeys.indexOf(matchingKey) > -1;
|
|
43
|
+
}).map(function (segmentName) { return ({ n: segmentName }); })
|
|
44
|
+
}
|
|
45
|
+
};
|
|
47
46
|
}
|
|
48
|
-
|
|
47
|
+
if (membershipsData) {
|
|
48
|
+
if (membershipsData.ms)
|
|
49
|
+
storage.segments.resetSegments(membershipsData.ms);
|
|
50
|
+
if (membershipsData.ls && storage.largeSegments)
|
|
51
|
+
storage.largeSegments.resetSegments(membershipsData.ls);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else { // add segments data (server-side)
|
|
55
|
+
Object.keys(segmentsData).forEach(function (segmentName) {
|
|
56
|
+
var segmentKeys = segmentsData[segmentName];
|
|
57
|
+
storage.segments.update(segmentName, segmentKeys, [], -1);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.loadData = loadData;
|
|
62
|
+
function getSnapshot(storage, userKeys) {
|
|
63
|
+
return {
|
|
64
|
+
// lastUpdated: Date.now(),
|
|
65
|
+
since: storage.splits.getChangeNumber(),
|
|
66
|
+
splitsData: storage.splits.getAll(),
|
|
67
|
+
segmentsData: userKeys ?
|
|
68
|
+
undefined : // @ts-ignore accessing private prop
|
|
69
|
+
Object.keys(storage.segments.segmentCache).reduce(function (prev, cur) {
|
|
70
|
+
prev[cur] = (0, sets_1.setToArray)(storage.segments.segmentCache[cur]);
|
|
71
|
+
return prev;
|
|
72
|
+
}, {}),
|
|
73
|
+
membershipsData: userKeys ?
|
|
74
|
+
userKeys.reduce(function (prev, userKey) {
|
|
75
|
+
if (storage.shared) {
|
|
76
|
+
// Client-side segments
|
|
77
|
+
// @ts-ignore accessing private prop
|
|
78
|
+
var sharedStorage = storage.shared(userKey);
|
|
79
|
+
prev[(0, key_1.getMatching)(userKey)] = {
|
|
80
|
+
ms: {
|
|
81
|
+
// @ts-ignore accessing private prop
|
|
82
|
+
k: Object.keys(sharedStorage.segments.segmentCache).map(function (segmentName) { return ({ n: segmentName }); }),
|
|
83
|
+
// cn: sharedStorage.segments.getChangeNumber()
|
|
84
|
+
},
|
|
85
|
+
ls: sharedStorage.largeSegments ? {
|
|
86
|
+
// @ts-ignore accessing private prop
|
|
87
|
+
k: Object.keys(sharedStorage.largeSegments.segmentCache).map(function (segmentName) { return ({ n: segmentName }); }),
|
|
88
|
+
// cn: sharedStorage.largeSegments.getChangeNumber()
|
|
89
|
+
} : undefined
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
prev[(0, key_1.getMatching)(userKey)] = {
|
|
94
|
+
ms: {
|
|
95
|
+
// Server-side segments
|
|
96
|
+
// @ts-ignore accessing private prop
|
|
97
|
+
k: Object.keys(storage.segments.segmentCache).reduce(function (prev, segmentName) {
|
|
98
|
+
return storage.segments.segmentCache[segmentName].has(userKey) ?
|
|
99
|
+
prev.concat({ n: segmentName }) :
|
|
100
|
+
prev;
|
|
101
|
+
}, [])
|
|
102
|
+
},
|
|
103
|
+
ls: {
|
|
104
|
+
k: []
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return prev;
|
|
109
|
+
}, {}) :
|
|
110
|
+
undefined
|
|
49
111
|
};
|
|
50
112
|
}
|
|
51
|
-
exports.
|
|
113
|
+
exports.getSnapshot = getSnapshot;
|
|
@@ -9,13 +9,15 @@ var ImpressionCountsCacheInMemory_1 = require("./ImpressionCountsCacheInMemory")
|
|
|
9
9
|
var constants_1 = require("../../utils/constants");
|
|
10
10
|
var TelemetryCacheInMemory_1 = require("./TelemetryCacheInMemory");
|
|
11
11
|
var UniqueKeysCacheInMemoryCS_1 = require("./UniqueKeysCacheInMemoryCS");
|
|
12
|
+
var key_1 = require("../../utils/key");
|
|
13
|
+
var dataLoader_1 = require("../dataLoader");
|
|
12
14
|
/**
|
|
13
15
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
14
16
|
*
|
|
15
17
|
* @param params parameters required by EventsCacheSync
|
|
16
18
|
*/
|
|
17
19
|
function InMemoryStorageCSFactory(params) {
|
|
18
|
-
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
|
|
20
|
+
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation, preloadedData = _a.preloadedData, onReadyFromCacheCb = params.onReadyFromCacheCb;
|
|
19
21
|
var splits = new SplitsCacheInMemory_1.SplitsCacheInMemory(__splitFiltersValidation);
|
|
20
22
|
var segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
21
23
|
var largeSegments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
@@ -39,11 +41,16 @@ function InMemoryStorageCSFactory(params) {
|
|
|
39
41
|
this.uniqueKeys && this.uniqueKeys.clear();
|
|
40
42
|
},
|
|
41
43
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
42
|
-
shared: function () {
|
|
44
|
+
shared: function (matchingKey) {
|
|
45
|
+
var segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
46
|
+
var largeSegments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
47
|
+
if (preloadedData) {
|
|
48
|
+
(0, dataLoader_1.loadData)(preloadedData, { segments: segments, largeSegments: largeSegments }, matchingKey);
|
|
49
|
+
}
|
|
43
50
|
return {
|
|
44
51
|
splits: this.splits,
|
|
45
|
-
segments:
|
|
46
|
-
largeSegments:
|
|
52
|
+
segments: segments,
|
|
53
|
+
largeSegments: largeSegments,
|
|
47
54
|
impressions: this.impressions,
|
|
48
55
|
impressionCounts: this.impressionCounts,
|
|
49
56
|
events: this.events,
|
|
@@ -68,6 +75,11 @@ function InMemoryStorageCSFactory(params) {
|
|
|
68
75
|
if (storage.uniqueKeys)
|
|
69
76
|
storage.uniqueKeys.track = noopTrack;
|
|
70
77
|
}
|
|
78
|
+
if (preloadedData) {
|
|
79
|
+
(0, dataLoader_1.loadData)(preloadedData, storage, (0, key_1.getMatching)(params.settings.core.key));
|
|
80
|
+
if (splits.getChangeNumber() > -1)
|
|
81
|
+
onReadyFromCacheCb();
|
|
82
|
+
}
|
|
71
83
|
return storage;
|
|
72
84
|
}
|
|
73
85
|
exports.InMemoryStorageCSFactory = InMemoryStorageCSFactory;
|
|
@@ -26,7 +26,7 @@ function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, read
|
|
|
26
26
|
segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
27
27
|
return Promise.all(changes.map(function (x) {
|
|
28
28
|
log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
29
|
-
return segments.update(
|
|
29
|
+
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
30
30
|
})).then(function (updates) {
|
|
31
31
|
return updates.some(function (update) { return update; });
|
|
32
32
|
});
|
package/esm/sdkFactory/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
|
|
|
5
5
|
import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
|
|
6
6
|
import { createLoggerAPI } from '../logger/sdkLogger';
|
|
7
7
|
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
|
|
8
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
8
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
9
9
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
10
10
|
import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
|
|
11
11
|
import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
|
|
@@ -42,8 +42,10 @@ export function sdkFactory(params) {
|
|
|
42
42
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
43
43
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
44
44
|
},
|
|
45
|
+
onReadyFromCacheCb: function () {
|
|
46
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
47
|
+
}
|
|
45
48
|
});
|
|
46
|
-
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
47
49
|
var clients = {};
|
|
48
50
|
var telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
49
51
|
var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage, telemetryTracker: telemetryTracker });
|
|
@@ -1,47 +1,108 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setToArray } from '../utils/lang/sets';
|
|
2
|
+
import { getMatching } from '../utils/key';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
5
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
4
6
|
*
|
|
5
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
6
|
-
*
|
|
7
|
-
* @
|
|
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
|
|
8
15
|
*/
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* @param userId user key string of the provided MySegmentsCache
|
|
16
|
-
*
|
|
17
|
-
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
18
|
-
* @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.
|
|
19
|
-
*/
|
|
20
|
-
return function loadData(storage, userId) {
|
|
21
|
-
// Do not load data if current preloadedData is empty
|
|
22
|
-
if (Object.keys(preloadedData).length === 0)
|
|
23
|
-
return;
|
|
24
|
-
var _a = preloadedData.lastUpdated, lastUpdated = _a === void 0 ? -1 : _a, _b = preloadedData.segmentsData, segmentsData = _b === void 0 ? {} : _b, _c = preloadedData.since, since = _c === void 0 ? -1 : _c, _d = preloadedData.splitsData, splitsData = _d === void 0 ? {} : _d;
|
|
16
|
+
export function loadData(preloadedData, storage, matchingKey) {
|
|
17
|
+
// Do not load data if current preloadedData is empty
|
|
18
|
+
if (Object.keys(preloadedData).length === 0)
|
|
19
|
+
return;
|
|
20
|
+
var _a = preloadedData.segmentsData, segmentsData = _a === void 0 ? {} : _a, _b = preloadedData.since, since = _b === void 0 ? -1 : _b, _c = preloadedData.splitsData, splitsData = _c === void 0 ? [] : _c;
|
|
21
|
+
if (storage.splits) {
|
|
25
22
|
var storedSince = storage.splits.getChangeNumber();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
29
|
-
if (storedSince > since || lastUpdated < expirationTimestamp)
|
|
23
|
+
// Do not load data if current data is more recent
|
|
24
|
+
if (storedSince > since)
|
|
30
25
|
return;
|
|
31
26
|
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
32
27
|
storage.splits.clear();
|
|
33
28
|
storage.splits.setChangeNumber(since);
|
|
34
29
|
// 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(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
storage.splits.addSplits(splitsData.map(function (split) { return ([split.name, split]); }));
|
|
31
|
+
}
|
|
32
|
+
if (matchingKey) { // add mySegments data (client-side)
|
|
33
|
+
var membershipsData = preloadedData.membershipsData && preloadedData.membershipsData[matchingKey];
|
|
34
|
+
if (!membershipsData && segmentsData) {
|
|
35
|
+
membershipsData = {
|
|
36
|
+
ms: {
|
|
37
|
+
k: Object.keys(segmentsData).filter(function (segmentName) {
|
|
38
|
+
var segmentKeys = segmentsData[segmentName];
|
|
39
|
+
return segmentKeys.indexOf(matchingKey) > -1;
|
|
40
|
+
}).map(function (segmentName) { return ({ n: segmentName }); })
|
|
41
|
+
}
|
|
42
|
+
};
|
|
44
43
|
}
|
|
45
|
-
|
|
44
|
+
if (membershipsData) {
|
|
45
|
+
if (membershipsData.ms)
|
|
46
|
+
storage.segments.resetSegments(membershipsData.ms);
|
|
47
|
+
if (membershipsData.ls && storage.largeSegments)
|
|
48
|
+
storage.largeSegments.resetSegments(membershipsData.ls);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else { // add segments data (server-side)
|
|
52
|
+
Object.keys(segmentsData).forEach(function (segmentName) {
|
|
53
|
+
var segmentKeys = segmentsData[segmentName];
|
|
54
|
+
storage.segments.update(segmentName, segmentKeys, [], -1);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function getSnapshot(storage, userKeys) {
|
|
59
|
+
return {
|
|
60
|
+
// lastUpdated: Date.now(),
|
|
61
|
+
since: storage.splits.getChangeNumber(),
|
|
62
|
+
splitsData: storage.splits.getAll(),
|
|
63
|
+
segmentsData: userKeys ?
|
|
64
|
+
undefined : // @ts-ignore accessing private prop
|
|
65
|
+
Object.keys(storage.segments.segmentCache).reduce(function (prev, cur) {
|
|
66
|
+
prev[cur] = setToArray(storage.segments.segmentCache[cur]);
|
|
67
|
+
return prev;
|
|
68
|
+
}, {}),
|
|
69
|
+
membershipsData: userKeys ?
|
|
70
|
+
userKeys.reduce(function (prev, userKey) {
|
|
71
|
+
if (storage.shared) {
|
|
72
|
+
// Client-side segments
|
|
73
|
+
// @ts-ignore accessing private prop
|
|
74
|
+
var sharedStorage = storage.shared(userKey);
|
|
75
|
+
prev[getMatching(userKey)] = {
|
|
76
|
+
ms: {
|
|
77
|
+
// @ts-ignore accessing private prop
|
|
78
|
+
k: Object.keys(sharedStorage.segments.segmentCache).map(function (segmentName) { return ({ n: segmentName }); }),
|
|
79
|
+
// cn: sharedStorage.segments.getChangeNumber()
|
|
80
|
+
},
|
|
81
|
+
ls: sharedStorage.largeSegments ? {
|
|
82
|
+
// @ts-ignore accessing private prop
|
|
83
|
+
k: Object.keys(sharedStorage.largeSegments.segmentCache).map(function (segmentName) { return ({ n: segmentName }); }),
|
|
84
|
+
// cn: sharedStorage.largeSegments.getChangeNumber()
|
|
85
|
+
} : undefined
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
prev[getMatching(userKey)] = {
|
|
90
|
+
ms: {
|
|
91
|
+
// Server-side segments
|
|
92
|
+
// @ts-ignore accessing private prop
|
|
93
|
+
k: Object.keys(storage.segments.segmentCache).reduce(function (prev, segmentName) {
|
|
94
|
+
return storage.segments.segmentCache[segmentName].has(userKey) ?
|
|
95
|
+
prev.concat({ n: segmentName }) :
|
|
96
|
+
prev;
|
|
97
|
+
}, [])
|
|
98
|
+
},
|
|
99
|
+
ls: {
|
|
100
|
+
k: []
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return prev;
|
|
105
|
+
}, {}) :
|
|
106
|
+
undefined
|
|
46
107
|
};
|
|
47
108
|
}
|
|
@@ -6,13 +6,15 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
|
6
6
|
import { DEBUG, LOCALHOST_MODE, NONE, 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 { loadData } from '../dataLoader';
|
|
9
11
|
/**
|
|
10
12
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
11
13
|
*
|
|
12
14
|
* @param params parameters required by EventsCacheSync
|
|
13
15
|
*/
|
|
14
16
|
export function InMemoryStorageCSFactory(params) {
|
|
15
|
-
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
|
|
17
|
+
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation, preloadedData = _a.preloadedData, onReadyFromCacheCb = params.onReadyFromCacheCb;
|
|
16
18
|
var splits = new SplitsCacheInMemory(__splitFiltersValidation);
|
|
17
19
|
var segments = new MySegmentsCacheInMemory();
|
|
18
20
|
var largeSegments = new MySegmentsCacheInMemory();
|
|
@@ -36,11 +38,16 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
36
38
|
this.uniqueKeys && this.uniqueKeys.clear();
|
|
37
39
|
},
|
|
38
40
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
39
|
-
shared: function () {
|
|
41
|
+
shared: function (matchingKey) {
|
|
42
|
+
var segments = new MySegmentsCacheInMemory();
|
|
43
|
+
var largeSegments = new MySegmentsCacheInMemory();
|
|
44
|
+
if (preloadedData) {
|
|
45
|
+
loadData(preloadedData, { segments: segments, largeSegments: largeSegments }, matchingKey);
|
|
46
|
+
}
|
|
40
47
|
return {
|
|
41
48
|
splits: this.splits,
|
|
42
|
-
segments:
|
|
43
|
-
largeSegments:
|
|
49
|
+
segments: segments,
|
|
50
|
+
largeSegments: largeSegments,
|
|
44
51
|
impressions: this.impressions,
|
|
45
52
|
impressionCounts: this.impressionCounts,
|
|
46
53
|
events: this.events,
|
|
@@ -65,6 +72,11 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
65
72
|
if (storage.uniqueKeys)
|
|
66
73
|
storage.uniqueKeys.track = noopTrack;
|
|
67
74
|
}
|
|
75
|
+
if (preloadedData) {
|
|
76
|
+
loadData(preloadedData, storage, getMatching(params.settings.core.key));
|
|
77
|
+
if (splits.getChangeNumber() > -1)
|
|
78
|
+
onReadyFromCacheCb();
|
|
79
|
+
}
|
|
68
80
|
return storage;
|
|
69
81
|
}
|
|
70
82
|
InMemoryStorageCSFactory.type = STORAGE_MEMORY;
|
|
@@ -23,7 +23,7 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
|
|
|
23
23
|
segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
24
24
|
return Promise.all(changes.map(function (x) {
|
|
25
25
|
log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
26
|
-
return segments.update(
|
|
26
|
+
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
27
27
|
})).then(function (updates) {
|
|
28
28
|
return updates.some(function (update) { return update; });
|
|
29
29
|
});
|
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';
|
|
@@ -43,7 +43,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
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,8 +52,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
52
52
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
53
53
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
54
54
|
},
|
|
55
|
+
onReadyFromCacheCb() {
|
|
56
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
57
|
+
}
|
|
55
58
|
});
|
|
56
|
-
|
|
59
|
+
|
|
57
60
|
const clients: Record<string, IBasicClient> = {};
|
|
58
61
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
59
62
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -1,55 +1,114 @@
|
|
|
1
1
|
import { SplitIO } from '../types';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
|
|
3
|
+
import { setToArray } from '../utils/lang/sets';
|
|
4
|
+
import { getMatching } from '../utils/key';
|
|
5
|
+
import { IMembershipsResponse, IMySegmentsResponse } from '../dtos/types';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
8
|
+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
9
|
+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
7
10
|
*
|
|
8
|
-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
|
|
12
|
+
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
13
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
14
|
+
*
|
|
15
|
+
* @TODO extend to load largeSegments
|
|
16
|
+
* @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.
|
|
17
|
+
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
18
|
+
* @TODO unit tests
|
|
11
19
|
*/
|
|
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;
|
|
20
|
+
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
|
|
21
|
+
// Do not load data if current preloadedData is empty
|
|
22
|
+
if (Object.keys(preloadedData).length === 0) return;
|
|
23
|
+
|
|
24
|
+
const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
|
|
29
25
|
|
|
26
|
+
if (storage.splits) {
|
|
30
27
|
const storedSince = storage.splits.getChangeNumber();
|
|
31
|
-
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
32
28
|
|
|
33
|
-
// Do not load data if current
|
|
34
|
-
|
|
35
|
-
if (storedSince > since || lastUpdated < expirationTimestamp) return;
|
|
29
|
+
// Do not load data if current data is more recent
|
|
30
|
+
if (storedSince > since) return;
|
|
36
31
|
|
|
37
32
|
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
|
|
38
33
|
storage.splits.clear();
|
|
39
34
|
storage.splits.setChangeNumber(since);
|
|
40
35
|
|
|
41
36
|
// 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(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
storage.splits.addSplits(splitsData.map(split => ([split.name, split])));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (matchingKey) { // add mySegments data (client-side)
|
|
41
|
+
let membershipsData = preloadedData.membershipsData && preloadedData.membershipsData[matchingKey];
|
|
42
|
+
if (!membershipsData && segmentsData) {
|
|
43
|
+
membershipsData = {
|
|
44
|
+
ms: {
|
|
45
|
+
k: Object.keys(segmentsData).filter(segmentName => {
|
|
46
|
+
const segmentKeys = segmentsData[segmentName];
|
|
47
|
+
return segmentKeys.indexOf(matchingKey) > -1;
|
|
48
|
+
}).map(segmentName => ({ n: segmentName }))
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (membershipsData) {
|
|
53
|
+
if (membershipsData.ms) storage.segments.resetSegments(membershipsData.ms);
|
|
54
|
+
if (membershipsData.ls && storage.largeSegments) storage.largeSegments.resetSegments(membershipsData.ls);
|
|
52
55
|
}
|
|
53
|
-
|
|
56
|
+
|
|
57
|
+
} else { // add segments data (server-side)
|
|
58
|
+
Object.keys(segmentsData).forEach(segmentName => {
|
|
59
|
+
const segmentKeys = segmentsData[segmentName];
|
|
60
|
+
storage.segments.update(segmentName, segmentKeys, [], -1);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData {
|
|
66
|
+
return {
|
|
67
|
+
// lastUpdated: Date.now(),
|
|
68
|
+
since: storage.splits.getChangeNumber(),
|
|
69
|
+
splitsData: storage.splits.getAll(),
|
|
70
|
+
segmentsData: userKeys ?
|
|
71
|
+
undefined : // @ts-ignore accessing private prop
|
|
72
|
+
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
|
|
73
|
+
prev[cur] = setToArray(storage.segments.segmentCache[cur] as Set<string>);
|
|
74
|
+
return prev;
|
|
75
|
+
}, {}),
|
|
76
|
+
membershipsData: userKeys ?
|
|
77
|
+
userKeys.reduce<Record<string, IMembershipsResponse>>((prev, userKey) => {
|
|
78
|
+
if (storage.shared) {
|
|
79
|
+
// Client-side segments
|
|
80
|
+
// @ts-ignore accessing private prop
|
|
81
|
+
const sharedStorage = storage.shared(userKey);
|
|
82
|
+
prev[getMatching(userKey)] = {
|
|
83
|
+
ms: {
|
|
84
|
+
// @ts-ignore accessing private prop
|
|
85
|
+
k: Object.keys(sharedStorage.segments.segmentCache).map(segmentName => ({ n: segmentName })),
|
|
86
|
+
// cn: sharedStorage.segments.getChangeNumber()
|
|
87
|
+
},
|
|
88
|
+
ls: sharedStorage.largeSegments ? {
|
|
89
|
+
// @ts-ignore accessing private prop
|
|
90
|
+
k: Object.keys(sharedStorage.largeSegments.segmentCache).map(segmentName => ({ n: segmentName })),
|
|
91
|
+
// cn: sharedStorage.largeSegments.getChangeNumber()
|
|
92
|
+
} : undefined
|
|
93
|
+
};
|
|
94
|
+
} else {
|
|
95
|
+
prev[getMatching(userKey)] = {
|
|
96
|
+
ms: {
|
|
97
|
+
// Server-side segments
|
|
98
|
+
// @ts-ignore accessing private prop
|
|
99
|
+
k: Object.keys(storage.segments.segmentCache).reduce<IMySegmentsResponse['k']>((prev, segmentName) => { // @ts-ignore accessing private prop
|
|
100
|
+
return storage.segments.segmentCache[segmentName].has(userKey) ?
|
|
101
|
+
prev!.concat({ n: segmentName }) :
|
|
102
|
+
prev;
|
|
103
|
+
}, [])
|
|
104
|
+
},
|
|
105
|
+
ls: {
|
|
106
|
+
k: []
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return prev;
|
|
111
|
+
}, {}) :
|
|
112
|
+
undefined
|
|
54
113
|
};
|
|
55
114
|
}
|
|
@@ -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 instantiation 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
|
@@ -488,8 +488,6 @@ export interface IStorageAsync extends IStorageBase<
|
|
|
488
488
|
|
|
489
489
|
/** StorageFactory */
|
|
490
490
|
|
|
491
|
-
export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
|
|
492
|
-
|
|
493
491
|
export interface IStorageFactoryParams {
|
|
494
492
|
settings: ISettings,
|
|
495
493
|
/**
|
|
@@ -497,6 +495,10 @@ export interface IStorageFactoryParams {
|
|
|
497
495
|
* It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
|
|
498
496
|
*/
|
|
499
497
|
onReadyCb: (error?: any) => void,
|
|
498
|
+
/**
|
|
499
|
+
* It is meant for emitting SDK_READY_FROM_CACHE event in standalone mode with preloaded data
|
|
500
|
+
*/
|
|
501
|
+
onReadyFromCacheCb: () => void,
|
|
500
502
|
}
|
|
501
503
|
|
|
502
504
|
export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
|
|
@@ -38,7 +38,7 @@ export function segmentChangesUpdaterFactory(
|
|
|
38
38
|
segmentChangesFetcher(since, segmentName, noCache, till).then((changes) => {
|
|
39
39
|
return Promise.all(changes.map(x => {
|
|
40
40
|
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
41
|
-
return segments.update(
|
|
41
|
+
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
42
42
|
})).then((updates) => {
|
|
43
43
|
return updates.some(update => update);
|
|
44
44
|
});
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISplitFiltersValidation } from './dtos/types';
|
|
1
|
+
import { IMembershipsResponse, 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';
|
|
@@ -97,6 +97,7 @@ export interface ISettings {
|
|
|
97
97
|
eventsFirstPushWindow: number
|
|
98
98
|
},
|
|
99
99
|
readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
|
|
100
|
+
readonly preloadedData?: SplitIO.PreloadedData,
|
|
100
101
|
readonly integrations: Array<{
|
|
101
102
|
readonly type: string,
|
|
102
103
|
(params: IIntegrationFactoryParams): IIntegration | void
|
|
@@ -770,31 +771,31 @@ export namespace SplitIO {
|
|
|
770
771
|
* If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
|
|
771
772
|
* @TODO configurable expiration time policy?
|
|
772
773
|
*/
|
|
773
|
-
lastUpdated: number,
|
|
774
|
+
// lastUpdated: number,
|
|
774
775
|
/**
|
|
775
776
|
* Change number of the preloaded data.
|
|
776
777
|
* If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
|
|
777
778
|
*/
|
|
778
779
|
since: number,
|
|
779
780
|
/**
|
|
780
|
-
*
|
|
781
|
+
* List of feature flag definitions.
|
|
782
|
+
* @TODO rename to flags
|
|
781
783
|
*/
|
|
782
|
-
splitsData:
|
|
783
|
-
[splitName: string]: string
|
|
784
|
-
},
|
|
784
|
+
splitsData: ISplit[],
|
|
785
785
|
/**
|
|
786
|
-
* Optional map of user keys to their
|
|
787
|
-
* @TODO
|
|
786
|
+
* Optional map of user keys to their memberships.
|
|
787
|
+
* @TODO rename to memberships
|
|
788
788
|
*/
|
|
789
|
-
|
|
790
|
-
[key: string]:
|
|
789
|
+
membershipsData?: {
|
|
790
|
+
[key: string]: IMembershipsResponse
|
|
791
791
|
},
|
|
792
792
|
/**
|
|
793
|
-
* Optional map of segments to their
|
|
794
|
-
* This property is ignored if `
|
|
793
|
+
* Optional map of segments to their list of keys.
|
|
794
|
+
* This property is ignored if `membershipsData` was provided.
|
|
795
|
+
* @TODO rename to segments
|
|
795
796
|
*/
|
|
796
797
|
segmentsData?: {
|
|
797
|
-
[segmentName: string]: string
|
|
798
|
+
[segmentName: string]: string[]
|
|
798
799
|
},
|
|
799
800
|
}
|
|
800
801
|
/**
|
|
@@ -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
|
+
}, matchingKey?: string): void;
|
|
21
|
+
export declare function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData;
|
|
@@ -387,7 +387,6 @@ export interface IStorageSync extends IStorageBase<ISplitsCacheSync, ISegmentsCa
|
|
|
387
387
|
export interface IStorageAsync extends IStorageBase<ISplitsCacheAsync, ISegmentsCacheAsync, IImpressionsCacheAsync | IImpressionsCacheSync, IImpressionCountsCacheBase, IEventsCacheAsync | IEventsCacheSync, ITelemetryCacheAsync | ITelemetryCacheSync, IUniqueKeysCacheBase> {
|
|
388
388
|
}
|
|
389
389
|
/** StorageFactory */
|
|
390
|
-
export declare type DataLoader = (storage: IStorageSync, matchingKey: string) => void;
|
|
391
390
|
export interface IStorageFactoryParams {
|
|
392
391
|
settings: ISettings;
|
|
393
392
|
/**
|
|
@@ -395,6 +394,10 @@ export interface IStorageFactoryParams {
|
|
|
395
394
|
* It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
|
|
396
395
|
*/
|
|
397
396
|
onReadyCb: (error?: any) => void;
|
|
397
|
+
/**
|
|
398
|
+
* It is meant for emitting SDK_READY_FROM_CACHE event in standalone mode with preloaded data
|
|
399
|
+
*/
|
|
400
|
+
onReadyFromCacheCb: () => void;
|
|
398
401
|
}
|
|
399
402
|
export declare type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
|
|
400
403
|
export declare type IStorageSyncFactory = {
|
package/types/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISplitFiltersValidation } from './dtos/types';
|
|
1
|
+
import { IMembershipsResponse, 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';
|
|
@@ -91,6 +91,7 @@ export interface ISettings {
|
|
|
91
91
|
eventsFirstPushWindow: number;
|
|
92
92
|
};
|
|
93
93
|
readonly storage: IStorageSyncFactory | IStorageAsyncFactory;
|
|
94
|
+
readonly preloadedData?: SplitIO.PreloadedData;
|
|
94
95
|
readonly integrations: Array<{
|
|
95
96
|
readonly type: string;
|
|
96
97
|
(params: IIntegrationFactoryParams): IIntegration | void;
|
|
@@ -769,31 +770,30 @@ export declare namespace SplitIO {
|
|
|
769
770
|
* If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
|
|
770
771
|
* @TODO configurable expiration time policy?
|
|
771
772
|
*/
|
|
772
|
-
lastUpdated: number;
|
|
773
773
|
/**
|
|
774
774
|
* Change number of the preloaded data.
|
|
775
775
|
* If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
|
|
776
776
|
*/
|
|
777
777
|
since: number;
|
|
778
778
|
/**
|
|
779
|
-
*
|
|
779
|
+
* List of feature flag definitions.
|
|
780
|
+
* @TODO rename to flags
|
|
780
781
|
*/
|
|
781
|
-
splitsData:
|
|
782
|
-
[splitName: string]: string;
|
|
783
|
-
};
|
|
782
|
+
splitsData: ISplit[];
|
|
784
783
|
/**
|
|
785
|
-
* Optional map of user keys to their
|
|
786
|
-
* @TODO
|
|
784
|
+
* Optional map of user keys to their memberships.
|
|
785
|
+
* @TODO rename to memberships
|
|
787
786
|
*/
|
|
788
|
-
|
|
789
|
-
[key: string]:
|
|
787
|
+
membershipsData?: {
|
|
788
|
+
[key: string]: IMembershipsResponse;
|
|
790
789
|
};
|
|
791
790
|
/**
|
|
792
|
-
* Optional map of segments to their
|
|
793
|
-
* This property is ignored if `
|
|
791
|
+
* Optional map of segments to their list of keys.
|
|
792
|
+
* This property is ignored if `membershipsData` was provided.
|
|
793
|
+
* @TODO rename to segments
|
|
794
794
|
*/
|
|
795
795
|
segmentsData?: {
|
|
796
|
-
[segmentName: string]: string;
|
|
796
|
+
[segmentName: string]: string[];
|
|
797
797
|
};
|
|
798
798
|
}
|
|
799
799
|
/**
|