@splitsoftware/splitio-commons 2.0.0-rc.0 → 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 +5 -1
- package/cjs/evaluator/Engine.js +1 -1
- package/cjs/evaluator/index.js +1 -1
- package/cjs/readiness/readinessManager.js +13 -2
- package/cjs/sdkClient/sdkClientMethodCS.js +0 -1
- package/cjs/sdkFactory/index.js +29 -9
- package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
- package/cjs/storages/dataLoader.js +99 -37
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +3 -2
- package/cjs/storages/inLocalStorage/index.js +1 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +18 -6
- package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +2 -1
- package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +2 -1
- package/cjs/storages/inRedis/RedisAdapter.js +2 -1
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +2 -1
- package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +2 -1
- package/cjs/storages/pluggable/inMemoryWrapper.js +2 -1
- package/cjs/sync/offline/syncManagerOffline.js +18 -11
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -1
- package/cjs/sync/syncManagerOnline.js +20 -21
- package/cjs/trackers/eventTracker.js +12 -10
- package/cjs/trackers/impressionsTracker.js +16 -14
- package/cjs/trackers/uniqueKeysTracker.js +5 -3
- package/cjs/utils/lang/sets.js +12 -2
- package/esm/evaluator/Engine.js +1 -1
- package/esm/evaluator/index.js +2 -2
- package/esm/readiness/readinessManager.js +13 -2
- package/esm/sdkClient/sdkClientMethodCS.js +0 -1
- package/esm/sdkFactory/index.js +30 -10
- package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
- package/esm/storages/dataLoader.js +96 -35
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +3 -2
- package/esm/storages/inLocalStorage/index.js +1 -1
- package/esm/storages/inMemory/InMemoryStorageCS.js +18 -6
- package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +2 -1
- package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +2 -1
- package/esm/storages/inRedis/RedisAdapter.js +2 -1
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +2 -1
- package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/esm/storages/pluggable/UniqueKeysCachePluggable.js +2 -1
- package/esm/storages/pluggable/inMemoryWrapper.js +2 -1
- package/esm/sync/offline/syncManagerOffline.js +18 -11
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -1
- package/esm/sync/syncManagerOnline.js +20 -21
- package/esm/trackers/eventTracker.js +12 -10
- package/esm/trackers/impressionsTracker.js +16 -14
- package/esm/trackers/uniqueKeysTracker.js +5 -3
- package/esm/utils/lang/sets.js +10 -1
- package/package.json +1 -1
- package/src/evaluator/Engine.ts +1 -1
- package/src/evaluator/index.ts +2 -2
- package/src/readiness/readinessManager.ts +12 -3
- package/src/readiness/types.ts +3 -0
- package/src/sdkClient/sdkClientMethodCS.ts +0 -2
- package/src/sdkFactory/index.ts +34 -12
- package/src/sdkFactory/types.ts +2 -0
- package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
- package/src/storages/dataLoader.ts +97 -38
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +3 -2
- package/src/storages/inLocalStorage/index.ts +1 -1
- package/src/storages/inMemory/InMemoryStorageCS.ts +21 -6
- package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
- package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
- package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +2 -1
- package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +2 -1
- package/src/storages/inRedis/RedisAdapter.ts +2 -1
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
- package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +2 -1
- package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
- package/src/storages/pluggable/UniqueKeysCachePluggable.ts +2 -1
- package/src/storages/pluggable/inMemoryWrapper.ts +2 -1
- package/src/storages/types.ts +7 -11
- package/src/sync/offline/syncManagerOffline.ts +21 -13
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
- package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -1
- package/src/sync/syncManagerOnline.ts +17 -17
- package/src/sync/types.ts +1 -1
- package/src/trackers/eventTracker.ts +11 -8
- package/src/trackers/impressionsTracker.ts +13 -10
- package/src/trackers/types.ts +1 -0
- package/src/trackers/uniqueKeysTracker.ts +6 -4
- package/src/types.ts +14 -13
- package/src/utils/lang/sets.ts +11 -1
- package/types/readiness/types.d.ts +3 -0
- package/types/sdkFactory/types.d.ts +1 -0
- package/types/storages/dataLoader.d.ts +17 -6
- package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
- package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
- package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
- package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
- package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
- package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
- package/types/storages/types.d.ts +7 -10
- package/types/sync/types.d.ts +1 -1
- package/types/trackers/eventTracker.d.ts +1 -1
- package/types/trackers/impressionsTracker.d.ts +1 -1
- package/types/trackers/types.d.ts +1 -0
- package/types/types.d.ts +13 -13
- package/types/utils/lang/sets.d.ts +1 -0
|
@@ -2,6 +2,7 @@ import { __extends } from "tslib";
|
|
|
2
2
|
import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
|
|
3
3
|
import { DEFAULT_CACHE_SIZE, REFRESH_RATE, TTL_REFRESH } from './constants';
|
|
4
4
|
import { LOG_PREFIX } from './constants';
|
|
5
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
5
6
|
var UniqueKeysCacheInRedis = /** @class */ (function (_super) {
|
|
6
7
|
__extends(UniqueKeysCacheInRedis, _super);
|
|
7
8
|
function UniqueKeysCacheInRedis(log, key, redis, uniqueKeysQueueSize, refreshRate) {
|
|
@@ -21,7 +22,7 @@ var UniqueKeysCacheInRedis = /** @class */ (function (_super) {
|
|
|
21
22
|
if (!featureNames.length)
|
|
22
23
|
return Promise.resolve(false);
|
|
23
24
|
var uniqueKeysArray = featureNames.map(function (featureName) {
|
|
24
|
-
var featureKeys =
|
|
25
|
+
var featureKeys = setToArray(_this.uniqueKeysTracker[featureName]);
|
|
25
26
|
var uniqueKeysPayload = {
|
|
26
27
|
f: featureName,
|
|
27
28
|
ks: featureKeys
|
|
@@ -12,32 +12,19 @@ var SegmentsCachePluggable = /** @class */ (function () {
|
|
|
12
12
|
this.wrapper = wrapper;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
* The returned promise is resolved
|
|
17
|
-
* or rejected if wrapper operation fails.
|
|
18
|
-
*/
|
|
19
|
-
SegmentsCachePluggable.prototype.addToSegment = function (name, segmentKeys) {
|
|
20
|
-
var segmentKey = this.keys.buildSegmentNameKey(name);
|
|
21
|
-
if (segmentKeys.length) {
|
|
22
|
-
return this.wrapper.addItems(segmentKey, segmentKeys);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
return Promise.resolve();
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Remove a list of `segmentKeys` from the given segment `name`.
|
|
30
|
-
* The returned promise is resolved when the operation success
|
|
31
|
-
* or rejected if wrapper operation fails.
|
|
15
|
+
* Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
|
|
16
|
+
* The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
|
|
17
|
+
* or rejected if it fails (e.g., wrapper operation fails).
|
|
32
18
|
*/
|
|
33
|
-
SegmentsCachePluggable.prototype.
|
|
19
|
+
SegmentsCachePluggable.prototype.update = function (name, addedKeys, removedKeys, changeNumber) {
|
|
34
20
|
var segmentKey = this.keys.buildSegmentNameKey(name);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
return Promise.all([
|
|
22
|
+
addedKeys.length && this.wrapper.addItems(segmentKey, addedKeys),
|
|
23
|
+
removedKeys.length && this.wrapper.removeItems(segmentKey, removedKeys),
|
|
24
|
+
this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
|
|
25
|
+
]).then(function () {
|
|
26
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
27
|
+
});
|
|
41
28
|
};
|
|
42
29
|
/**
|
|
43
30
|
* Returns a promise that resolves with a boolean value indicating if `key` is part of `name` segment.
|
|
@@ -46,14 +33,6 @@ var SegmentsCachePluggable = /** @class */ (function () {
|
|
|
46
33
|
SegmentsCachePluggable.prototype.isInSegment = function (name, key) {
|
|
47
34
|
return this.wrapper.itemContains(this.keys.buildSegmentNameKey(name), key);
|
|
48
35
|
};
|
|
49
|
-
/**
|
|
50
|
-
* Set till number for the given segment `name`.
|
|
51
|
-
* The returned promise is resolved when the operation success,
|
|
52
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
53
|
-
*/
|
|
54
|
-
SegmentsCachePluggable.prototype.setChangeNumber = function (name, changeNumber) {
|
|
55
|
-
return this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '');
|
|
56
|
-
};
|
|
57
36
|
/**
|
|
58
37
|
* Get till number or -1 if it's not defined.
|
|
59
38
|
* The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.
|
|
@@ -2,6 +2,7 @@ import { __extends } from "tslib";
|
|
|
2
2
|
import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
|
|
3
3
|
import { DEFAULT_CACHE_SIZE, REFRESH_RATE } from '../inRedis/constants';
|
|
4
4
|
import { LOG_PREFIX } from './constants';
|
|
5
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
5
6
|
var UniqueKeysCachePluggable = /** @class */ (function (_super) {
|
|
6
7
|
__extends(UniqueKeysCachePluggable, _super);
|
|
7
8
|
function UniqueKeysCachePluggable(log, key, wrapper, uniqueKeysQueueSize, refreshRate) {
|
|
@@ -21,7 +22,7 @@ var UniqueKeysCachePluggable = /** @class */ (function (_super) {
|
|
|
21
22
|
if (!featureNames.length)
|
|
22
23
|
return Promise.resolve(false);
|
|
23
24
|
var uniqueKeysArray = featureNames.map(function (featureName) {
|
|
24
|
-
var featureKeys =
|
|
25
|
+
var featureKeys = setToArray(_this.uniqueKeysTracker[featureName]);
|
|
25
26
|
var uniqueKeysPayload = {
|
|
26
27
|
f: featureName,
|
|
27
28
|
ks: featureKeys
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { startsWith, toNumber } from '../../utils/lang';
|
|
2
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
2
3
|
/**
|
|
3
4
|
* Creates a IPluggableStorageWrapper implementation that stores items in memory.
|
|
4
5
|
* The `_cache` property is the object were items are stored.
|
|
@@ -115,7 +116,7 @@ export function inMemoryWrapperFactory(connDelay) {
|
|
|
115
116
|
if (!set)
|
|
116
117
|
return Promise.resolve([]);
|
|
117
118
|
if (set instanceof Set)
|
|
118
|
-
return Promise.resolve(
|
|
119
|
+
return Promise.resolve(setToArray(set));
|
|
119
120
|
return Promise.reject('key is not a set');
|
|
120
121
|
},
|
|
121
122
|
// always connects and disconnects
|
|
@@ -16,23 +16,30 @@ export function syncManagerOfflineFactory(splitsParserFactory) {
|
|
|
16
16
|
*/
|
|
17
17
|
return function (_a) {
|
|
18
18
|
var settings = _a.settings, readiness = _a.readiness, storage = _a.storage;
|
|
19
|
-
|
|
19
|
+
var mainSyncManager = fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings);
|
|
20
|
+
var mainStart = mainSyncManager.start;
|
|
21
|
+
var sharedStarts = [];
|
|
22
|
+
return objectAssign(mainSyncManager, {
|
|
23
|
+
start: function () {
|
|
24
|
+
mainStart();
|
|
25
|
+
sharedStarts.forEach(function (cb) { return cb(); });
|
|
26
|
+
sharedStarts.length = 0;
|
|
27
|
+
},
|
|
20
28
|
// fake flush, that resolves immediately
|
|
21
29
|
flush: flush,
|
|
22
30
|
// [Only used for client-side]
|
|
23
31
|
shared: function (matchingKey, readinessManager) {
|
|
32
|
+
// In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
|
|
33
|
+
// SDK_READY cannot be emitted directly because this will not update the readiness status
|
|
34
|
+
function emitSdkReady() {
|
|
35
|
+
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
|
|
36
|
+
}
|
|
37
|
+
if (mainSyncManager.isRunning())
|
|
38
|
+
setTimeout(emitSdkReady);
|
|
39
|
+
else
|
|
40
|
+
sharedStarts.push(emitSdkReady);
|
|
24
41
|
return {
|
|
25
|
-
start: function () {
|
|
26
|
-
// In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
|
|
27
|
-
// SDK_READY cannot be emitted directly because this will not update the readiness status
|
|
28
|
-
setTimeout(function () {
|
|
29
|
-
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
|
|
30
|
-
}, 0);
|
|
31
|
-
},
|
|
32
42
|
stop: function () { },
|
|
33
|
-
isRunning: function () {
|
|
34
|
-
return true;
|
|
35
|
-
},
|
|
36
43
|
flush: flush,
|
|
37
44
|
};
|
|
38
45
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { findIndex } from '../../../utils/lang';
|
|
2
1
|
import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
3
2
|
import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
|
|
4
|
-
import { thenable } from '../../../utils/promise/thenable';
|
|
5
3
|
/**
|
|
6
4
|
* Factory of SegmentChanges updater, a task that:
|
|
7
5
|
* - fetches segment changes using `segmentChangesFetcher`
|
|
@@ -20,27 +18,16 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
|
|
|
20
18
|
var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
|
|
21
19
|
return sincePromise.then(function (since) {
|
|
22
20
|
// if fetchOnlyNew flag, avoid processing already fetched segments
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
results.push(segments.removeFromSegment(segmentName, x.removed));
|
|
33
|
-
if (x.added.length > 0 || x.removed.length > 0) {
|
|
34
|
-
results.push(segments.setChangeNumber(segmentName, x.till));
|
|
35
|
-
changeNumber = x.till;
|
|
36
|
-
}
|
|
37
|
-
log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
|
|
21
|
+
return fetchOnlyNew && since !== -1 ?
|
|
22
|
+
false :
|
|
23
|
+
segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
|
|
24
|
+
return Promise.all(changes.map(function (x) {
|
|
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(segmentName, x.added, x.removed, x.till);
|
|
27
|
+
})).then(function (updates) {
|
|
28
|
+
return updates.some(function (update) { return update; });
|
|
29
|
+
});
|
|
38
30
|
});
|
|
39
|
-
// If at least one storage operation result is a promise, join all in a single promise.
|
|
40
|
-
if (results.some(function (result) { return thenable(result); }))
|
|
41
|
-
return Promise.all(results).then(function () { return changeNumber; });
|
|
42
|
-
return changeNumber;
|
|
43
|
-
});
|
|
44
31
|
});
|
|
45
32
|
}
|
|
46
33
|
/**
|
|
@@ -59,14 +46,11 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
|
|
|
59
46
|
// If not a segment name provided, read list of available segments names to be updated.
|
|
60
47
|
var segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
|
|
61
48
|
return segmentsPromise.then(function (segmentNames) {
|
|
62
|
-
// Async fetchers
|
|
63
|
-
var updaters =
|
|
64
|
-
for (var index = 0; index < segmentNames.length; index++) {
|
|
65
|
-
updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
|
|
66
|
-
}
|
|
49
|
+
// Async fetchers
|
|
50
|
+
var updaters = segmentNames.map(function (segmentName) { return updateSegment(segmentName, noCache, till, fetchOnlyNew); });
|
|
67
51
|
return Promise.all(updaters).then(function (shouldUpdateFlags) {
|
|
68
52
|
// if at least one segment fetch succeeded, mark segments ready
|
|
69
|
-
if (
|
|
53
|
+
if (shouldUpdateFlags.some(function (update) { return update; }) || readyOnAlreadyExistentState) {
|
|
70
54
|
readyOnAlreadyExistentState = false;
|
|
71
55
|
if (readiness)
|
|
72
56
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
@@ -3,6 +3,7 @@ import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/
|
|
|
3
3
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
4
4
|
import { startsWith } from '../../../utils/lang';
|
|
5
5
|
import { IN_SEGMENT } from '../../../utils/constants';
|
|
6
|
+
import { setToArray } from '../../../utils/lang/sets';
|
|
6
7
|
// Checks that all registered segments have been fetched (changeNumber !== -1 for every segment).
|
|
7
8
|
// Returns a promise that could be rejected.
|
|
8
9
|
// @TODO review together with Segments and MySegments storage APIs
|
|
@@ -67,7 +68,7 @@ export function computeSplitsMutation(entries, filters) {
|
|
|
67
68
|
}
|
|
68
69
|
return accum;
|
|
69
70
|
}, { added: [], removed: [], segments: [] });
|
|
70
|
-
computed.segments =
|
|
71
|
+
computed.segments = setToArray(segments);
|
|
71
72
|
return computed;
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
@@ -115,33 +115,32 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
115
115
|
if (!pollingManager)
|
|
116
116
|
return;
|
|
117
117
|
var mySegmentsSyncTask = pollingManager.add(matchingKey, readinessManager, storage);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (pollingManager.isRunning()) {
|
|
124
|
-
// if doing polling, we must start the periodic fetch of data
|
|
125
|
-
if (storage.splits.usesSegments())
|
|
126
|
-
mySegmentsSyncTask.start();
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
// if not polling, we must execute the sync task for the initial fetch
|
|
130
|
-
// of segments since `syncAll` was already executed when starting the main client
|
|
131
|
-
mySegmentsSyncTask.execute();
|
|
132
|
-
}
|
|
133
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
118
|
+
if (running) {
|
|
119
|
+
if (syncEnabled) {
|
|
120
|
+
if (pushManager) {
|
|
121
|
+
if (pollingManager.isRunning()) {
|
|
122
|
+
// if doing polling, we must start the periodic fetch of data
|
|
136
123
|
if (storage.splits.usesSegments())
|
|
137
124
|
mySegmentsSyncTask.start();
|
|
138
125
|
}
|
|
126
|
+
else {
|
|
127
|
+
// if not polling, we must execute the sync task for the initial fetch
|
|
128
|
+
// of segments since `syncAll` was already executed when starting the main client
|
|
129
|
+
mySegmentsSyncTask.execute();
|
|
130
|
+
}
|
|
131
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
139
132
|
}
|
|
140
133
|
else {
|
|
141
|
-
if (
|
|
142
|
-
mySegmentsSyncTask.
|
|
134
|
+
if (storage.splits.usesSegments())
|
|
135
|
+
mySegmentsSyncTask.start();
|
|
143
136
|
}
|
|
144
|
-
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (!readinessManager.isReady())
|
|
140
|
+
mySegmentsSyncTask.execute();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
145
144
|
stop: function () {
|
|
146
145
|
// check in case `client.destroy()` has been invoked more than once for the same client
|
|
147
146
|
var mySegmentsSyncTask = pollingManager.get(matchingKey);
|
|
@@ -9,7 +9,7 @@ import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
|
9
9
|
* @param eventsCache cache to save events
|
|
10
10
|
* @param integrationsManager optional event handler used for integrations
|
|
11
11
|
*/
|
|
12
|
-
export function eventTrackerFactory(settings, eventsCache, integrationsManager, telemetryCache) {
|
|
12
|
+
export function eventTrackerFactory(settings, eventsCache, whenInit, integrationsManager, telemetryCache) {
|
|
13
13
|
var log = settings.log, mode = settings.mode;
|
|
14
14
|
var isAsync = isConsumerMode(mode);
|
|
15
15
|
function queueEventsCallback(eventData, tracked) {
|
|
@@ -19,15 +19,17 @@ export function eventTrackerFactory(settings, eventsCache, integrationsManager,
|
|
|
19
19
|
if (tracked) {
|
|
20
20
|
log.info(EVENTS_TRACKER_SUCCESS, [msg]);
|
|
21
21
|
if (integrationsManager) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
whenInit(function () {
|
|
23
|
+
// Wrap in a timeout because we don't want it to be blocking.
|
|
24
|
+
setTimeout(function () {
|
|
25
|
+
// copy of event, to avoid unexpected behaviour if modified by integrations
|
|
26
|
+
var eventDataCopy = objectAssign({}, eventData);
|
|
27
|
+
if (properties)
|
|
28
|
+
eventDataCopy.properties = objectAssign({}, properties);
|
|
29
|
+
// integrationsManager does not throw errors (they are internally handled by each integration module)
|
|
30
|
+
integrationsManager.handleEvent(eventDataCopy);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
else {
|
|
@@ -11,7 +11,7 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
|
|
|
11
11
|
* @param integrationsManager optional integrations manager
|
|
12
12
|
* @param strategy strategy for impressions tracking.
|
|
13
13
|
*/
|
|
14
|
-
export function impressionsTrackerFactory(settings, impressionsCache, strategy, integrationsManager, telemetryCache) {
|
|
14
|
+
export function impressionsTrackerFactory(settings, impressionsCache, strategy, whenInit, integrationsManager, telemetryCache) {
|
|
15
15
|
var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
|
|
16
16
|
return {
|
|
17
17
|
track: function (impressions, attributes) {
|
|
@@ -50,19 +50,21 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
|
|
|
50
50
|
hostname: hostname,
|
|
51
51
|
sdkLanguageVersion: version
|
|
52
52
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
integrationsManager
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
impressionListener
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
whenInit(function () {
|
|
54
|
+
// Wrap in a timeout because we don't want it to be blocking.
|
|
55
|
+
setTimeout(function () {
|
|
56
|
+
// integrationsManager.handleImpression does not throw errors
|
|
57
|
+
if (integrationsManager)
|
|
58
|
+
integrationsManager.handleImpression(impressionData);
|
|
59
|
+
try { // @ts-ignore. An exception on the listeners should not break the SDK.
|
|
60
|
+
if (impressionListener)
|
|
61
|
+
impressionListener.logImpression(impressionData);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
66
68
|
};
|
|
67
69
|
for (var i = 0; i < impressionsToListenerCount; i++) {
|
|
68
70
|
_loop_1(i);
|
|
@@ -16,9 +16,6 @@ var noopFilterAdapter = {
|
|
|
16
16
|
export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
|
|
17
17
|
if (filterAdapter === void 0) { filterAdapter = noopFilterAdapter; }
|
|
18
18
|
var intervalId;
|
|
19
|
-
if (filterAdapter.refreshRate) {
|
|
20
|
-
intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
|
|
21
|
-
}
|
|
22
19
|
return {
|
|
23
20
|
track: function (key, featureName) {
|
|
24
21
|
if (!filterAdapter.add(key, featureName)) {
|
|
@@ -27,6 +24,11 @@ export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
|
|
|
27
24
|
}
|
|
28
25
|
uniqueKeysCache.track(key, featureName);
|
|
29
26
|
},
|
|
27
|
+
start: function () {
|
|
28
|
+
if (filterAdapter.refreshRate) {
|
|
29
|
+
intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
30
32
|
stop: function () {
|
|
31
33
|
clearInterval(intervalId);
|
|
32
34
|
}
|
package/esm/utils/lang/sets.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
export function setToArray(set) {
|
|
2
|
+
if (Array.from)
|
|
3
|
+
return Array.from(set);
|
|
4
|
+
var array = [];
|
|
5
|
+
set.forEach(function (value) {
|
|
6
|
+
array.push(value);
|
|
7
|
+
});
|
|
8
|
+
return array;
|
|
9
|
+
}
|
|
1
10
|
export function returnSetsUnion(set, set2) {
|
|
2
|
-
return new Set(
|
|
11
|
+
return new Set(setToArray(set).concat(setToArray(set2)));
|
|
3
12
|
}
|
|
4
13
|
export function returnDifference(list, list2) {
|
|
5
14
|
if (list === void 0) { list = []; }
|
package/package.json
CHANGED
package/src/evaluator/Engine.ts
CHANGED
|
@@ -21,7 +21,7 @@ export class Engine {
|
|
|
21
21
|
|
|
22
22
|
constructor(private baseInfo: ISplit, private evaluator: IEvaluator) {
|
|
23
23
|
|
|
24
|
-
// in case we don't have a default treatment in the
|
|
24
|
+
// in case we don't have a default treatment in the instantiation, use 'control'
|
|
25
25
|
if (typeof this.baseInfo.defaultTreatment !== 'string') {
|
|
26
26
|
this.baseInfo.defaultTreatment = CONTROL;
|
|
27
27
|
}
|
package/src/evaluator/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
|
7
7
|
import { IEvaluationResult } from './types';
|
|
8
8
|
import { SplitIO } from '../types';
|
|
9
9
|
import { ILogger } from '../logger/types';
|
|
10
|
-
import { returnSetsUnion } from '../utils/lang/sets';
|
|
10
|
+
import { returnSetsUnion, setToArray } from '../utils/lang/sets';
|
|
11
11
|
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
|
|
12
12
|
|
|
13
13
|
const treatmentException = {
|
|
@@ -113,7 +113,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
return featureFlags.size ?
|
|
116
|
-
evaluateFeatures(log, key,
|
|
116
|
+
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
|
|
117
117
|
{};
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -7,6 +7,8 @@ function splitsEventEmitterFactory(EventEmitter: new () => IEventEmitter): ISpli
|
|
|
7
7
|
const splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
8
8
|
splitsArrived: false,
|
|
9
9
|
splitsCacheLoaded: false,
|
|
10
|
+
initialized: false,
|
|
11
|
+
initCallbacks: []
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
// `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
|
|
@@ -56,8 +58,8 @@ export function readinessManagerFactory(
|
|
|
56
58
|
// emit SDK_READY_TIMED_OUT
|
|
57
59
|
let hasTimedout = false;
|
|
58
60
|
|
|
59
|
-
function timeout() {
|
|
60
|
-
if (hasTimedout) return;
|
|
61
|
+
function timeout() { // eslint-disable-next-line no-use-before-define
|
|
62
|
+
if (hasTimedout || isReady) return;
|
|
61
63
|
hasTimedout = true;
|
|
62
64
|
syncLastUpdate();
|
|
63
65
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
@@ -65,7 +67,8 @@ export function readinessManagerFactory(
|
|
|
65
67
|
|
|
66
68
|
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
67
69
|
if (readyTimeout > 0) {
|
|
68
|
-
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
70
|
+
if (splits.initialized) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
71
|
+
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
// emit SDK_READY and SDK_UPDATE
|
|
@@ -132,6 +135,12 @@ export function readinessManagerFactory(
|
|
|
132
135
|
// tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event
|
|
133
136
|
setDestroyed() { isDestroyed = true; },
|
|
134
137
|
|
|
138
|
+
init() {
|
|
139
|
+
if (splits.initialized) return;
|
|
140
|
+
splits.initialized = true;
|
|
141
|
+
splits.initCallbacks.forEach(cb => cb());
|
|
142
|
+
},
|
|
143
|
+
|
|
135
144
|
destroy() {
|
|
136
145
|
isDestroyed = true;
|
|
137
146
|
syncLastUpdate();
|
package/src/readiness/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ISplitsEventEmitter extends IEventEmitter {
|
|
|
12
12
|
once(event: ISplitsEvent, listener: (...args: any[]) => void): this;
|
|
13
13
|
splitsArrived: boolean
|
|
14
14
|
splitsCacheLoaded: boolean
|
|
15
|
+
initialized: boolean,
|
|
16
|
+
initCallbacks: (() => void)[]
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/** Segments data emitter */
|
|
@@ -59,6 +61,7 @@ export interface IReadinessManager {
|
|
|
59
61
|
timeout(): void,
|
|
60
62
|
setDestroyed(): void,
|
|
61
63
|
destroy(): void,
|
|
64
|
+
init(): void,
|
|
62
65
|
|
|
63
66
|
/** for client-side */
|
|
64
67
|
shared(): IReadinessManager,
|
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';
|
|
@@ -23,21 +23,27 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
23
23
|
const { settings, platform, storageFactory, splitApiFactory, extraProps,
|
|
24
24
|
syncManagerFactory, SignalListener, impressionsObserverFactory,
|
|
25
25
|
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
|
|
26
|
-
filterAdapterFactory } = params;
|
|
26
|
+
filterAdapterFactory, lazyInit } = params;
|
|
27
27
|
const { log, sync: { impressionsMode } } = settings;
|
|
28
28
|
|
|
29
29
|
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
|
|
30
30
|
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// initialization
|
|
33
|
+
let hasInit = false;
|
|
34
|
+
const initCallbacks: (() => void)[] = [];
|
|
35
|
+
|
|
36
|
+
function whenInit(cb: () => void) {
|
|
37
|
+
if (hasInit) cb();
|
|
38
|
+
else initCallbacks.push(cb);
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
|
|
36
42
|
const readiness = sdkReadinessManager.readinessManager;
|
|
37
43
|
|
|
38
44
|
const storage = storageFactory({
|
|
39
45
|
settings,
|
|
40
|
-
onReadyCb
|
|
46
|
+
onReadyCb(error) {
|
|
41
47
|
if (error) {
|
|
42
48
|
// If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
|
|
43
49
|
readiness.timeout();
|
|
@@ -46,8 +52,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
46
52
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
47
53
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
48
54
|
},
|
|
55
|
+
onReadyFromCacheCb() {
|
|
56
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
57
|
+
}
|
|
49
58
|
});
|
|
50
|
-
|
|
59
|
+
|
|
51
60
|
const clients: Record<string, IBasicClient> = {};
|
|
52
61
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
53
62
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -67,8 +76,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
67
76
|
strategy = strategyDebugFactory(observer);
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
|
|
71
|
-
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
|
|
79
|
+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
|
|
80
|
+
const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
|
|
72
81
|
|
|
73
82
|
// splitApi is used by SyncManager and Browser signal listener
|
|
74
83
|
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
@@ -85,8 +94,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
85
94
|
const clientMethod = sdkClientMethodFactory(ctx);
|
|
86
95
|
const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
|
|
98
|
+
function init() {
|
|
99
|
+
if (hasInit) return;
|
|
100
|
+
hasInit = true;
|
|
101
|
+
|
|
102
|
+
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
|
|
103
|
+
validateAndTrackApiKey(log, settings.core.authorizationKey);
|
|
104
|
+
readiness.init();
|
|
105
|
+
uniqueKeysTracker && uniqueKeysTracker.start();
|
|
106
|
+
syncManager && syncManager.start();
|
|
107
|
+
signalListener && signalListener.start();
|
|
108
|
+
|
|
109
|
+
initCallbacks.forEach((cb) => cb());
|
|
110
|
+
initCallbacks.length = 0;
|
|
111
|
+
}
|
|
90
112
|
|
|
91
113
|
log.info(NEW_FACTORY);
|
|
92
114
|
|
|
@@ -107,7 +129,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
107
129
|
settings,
|
|
108
130
|
|
|
109
131
|
destroy() {
|
|
110
|
-
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
|
|
132
|
+
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
111
133
|
}
|
|
112
|
-
}, extraProps && extraProps(ctx));
|
|
134
|
+
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
|
113
135
|
}
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
|
|
|
68
68
|
* Object parameter with the modules required to create an SDK factory instance
|
|
69
69
|
*/
|
|
70
70
|
export interface ISdkFactoryParams {
|
|
71
|
+
// If true, the `sdkFactory` is pure (no side effects), and the SDK instance includes a `init` method to run initialization side effects
|
|
72
|
+
lazyInit?: boolean,
|
|
71
73
|
|
|
72
74
|
// The settings must be already validated
|
|
73
75
|
settings: ISettings,
|