@splitsoftware/splitio-commons 3.1.1-rc.12 → 3.1.1-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +2 -0
- package/cjs/storages/AbstractDefinitionsCacheAsync.js +2 -1
- package/cjs/storages/AbstractDefinitionsCacheSync.js +2 -2
- package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
- package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
- package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +9 -7
- package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
- package/cjs/sync/offline/splitsParser/definitionChangesBuilder.js +37 -0
- package/cjs/sync/offline/splitsParser/splitsParserFromSettings.js +5 -32
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +26 -46
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -11
- package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +1 -1
- package/esm/storages/AbstractDefinitionsCacheAsync.js +2 -1
- package/esm/storages/AbstractDefinitionsCacheSync.js +2 -2
- package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
- package/esm/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
- package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +9 -7
- package/esm/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
- package/esm/sync/offline/splitsParser/definitionChangesBuilder.js +33 -0
- package/esm/sync/offline/splitsParser/splitsParserFromSettings.js +6 -33
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +23 -43
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +8 -11
- package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +2 -2
- package/package.json +1 -1
- package/src/storages/AbstractDefinitionsCacheAsync.ts +3 -2
- package/src/storages/AbstractDefinitionsCacheSync.ts +3 -3
- package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +3 -1
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +5 -3
- package/src/storages/inMemory/DefinitionsCacheInMemory.ts +1 -0
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -3
- package/src/storages/inRedis/DefinitionsCacheInRedis.ts +9 -7
- package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +20 -11
- package/src/storages/pluggable/DefinitionsCachePluggable.ts +10 -7
- package/src/storages/pluggable/RBSegmentsCachePluggable.ts +20 -11
- package/src/storages/types.ts +6 -6
- package/src/sync/offline/splitsParser/definitionChangesBuilder.ts +49 -0
- package/src/sync/offline/splitsParser/splitsParserFromSettings.ts +8 -38
- package/src/sync/offline/splitsParser/types.ts +7 -3
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +25 -45
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +9 -13
- package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +2 -2
- package/src/sync/syncManagerOnline.ts +0 -1
|
@@ -25,20 +25,28 @@ var RBSegmentsCacheInRedis = /** @class */ (function () {
|
|
|
25
25
|
var _this = this;
|
|
26
26
|
return Promise.all([
|
|
27
27
|
this.setChangeNumber(changeNumber),
|
|
28
|
-
Promise.all(toAdd.map(function (
|
|
29
|
-
|
|
30
|
-
var stringifiedNewRBSegment = JSON.stringify(toAdd);
|
|
31
|
-
return _this.redis.set(key, stringifiedNewRBSegment).then(function () { return true; });
|
|
32
|
-
})),
|
|
33
|
-
Promise.all(toRemove.map(function (toRemove) {
|
|
34
|
-
var key = _this.keys.buildRBSegmentKey(toRemove);
|
|
35
|
-
return _this.redis.del(key).then(function (status) { return status === 1; });
|
|
36
|
-
}))
|
|
28
|
+
Promise.all(toAdd.map(function (rbSegment) { return _this.add(rbSegment); })),
|
|
29
|
+
Promise.all(toRemove.map(function (name) { return _this.remove(name); }))
|
|
37
30
|
]).then(function (_a) {
|
|
38
31
|
var added = _a[1], removed = _a[2];
|
|
39
|
-
return
|
|
32
|
+
return toRemove.filter(function (_, i) { return removed[i]; })
|
|
33
|
+
.concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (rbSegment) { return rbSegment.name; }));
|
|
40
34
|
});
|
|
41
35
|
};
|
|
36
|
+
RBSegmentsCacheInRedis.prototype.add = function (rbSegment) {
|
|
37
|
+
var _this = this;
|
|
38
|
+
var key = this.keys.buildRBSegmentKey(rbSegment.name);
|
|
39
|
+
return this.get(rbSegment.name).then(function (previous) {
|
|
40
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber)
|
|
41
|
+
return false;
|
|
42
|
+
var stringifiedNewRBSegment = JSON.stringify(rbSegment);
|
|
43
|
+
return _this.redis.set(key, stringifiedNewRBSegment).then(function () { return true; });
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
RBSegmentsCacheInRedis.prototype.remove = function (name) {
|
|
47
|
+
var key = this.keys.buildRBSegmentKey(name);
|
|
48
|
+
return this.redis.del(key).then(function (status) { return status === 1; });
|
|
49
|
+
};
|
|
42
50
|
RBSegmentsCacheInRedis.prototype.setChangeNumber = function (changeNumber) {
|
|
43
51
|
if (changeNumber !== undefined) {
|
|
44
52
|
return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(function (status) { return status === 'OK'; });
|
|
@@ -60,6 +60,8 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
|
|
|
60
60
|
var parsedPreviousDefinition, stringifiedNewDefinition;
|
|
61
61
|
try {
|
|
62
62
|
parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
|
|
63
|
+
if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber)
|
|
64
|
+
return false;
|
|
63
65
|
stringifiedNewDefinition = JSON.stringify(definition);
|
|
64
66
|
}
|
|
65
67
|
catch (e) {
|
|
@@ -74,8 +76,8 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
|
|
|
74
76
|
if (parsedPreviousDefinition)
|
|
75
77
|
return _this._decrementCounts(parsedPreviousDefinition);
|
|
76
78
|
});
|
|
77
|
-
}).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); });
|
|
78
|
-
})
|
|
79
|
+
}).then(function () { return _this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets); }).then(function () { return true; });
|
|
80
|
+
});
|
|
79
81
|
};
|
|
80
82
|
/**
|
|
81
83
|
* Remove a given definition.
|
|
@@ -85,11 +87,11 @@ var DefinitionsCachePluggable = /** @class */ (function (_super) {
|
|
|
85
87
|
DefinitionsCachePluggable.prototype.remove = function (name) {
|
|
86
88
|
var _this = this;
|
|
87
89
|
return this.get(name).then(function (definition) {
|
|
88
|
-
if (definition)
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
if (!definition)
|
|
91
|
+
return false;
|
|
92
|
+
return _this._decrementCounts(definition).then(function () { return _this._updateSets(name, definition.sets); }).then(function () {
|
|
93
|
+
return _this.wrapper.del(_this.keys.buildDefinitionKey(name));
|
|
94
|
+
});
|
|
93
95
|
});
|
|
94
96
|
};
|
|
95
97
|
/**
|
|
@@ -25,20 +25,28 @@ var RBSegmentsCachePluggable = /** @class */ (function () {
|
|
|
25
25
|
var _this = this;
|
|
26
26
|
return Promise.all([
|
|
27
27
|
this.setChangeNumber(changeNumber),
|
|
28
|
-
Promise.all(toAdd.map(function (
|
|
29
|
-
|
|
30
|
-
var stringifiedNewRBSegment = JSON.stringify(toAdd);
|
|
31
|
-
return _this.wrapper.set(key, stringifiedNewRBSegment).then(function () { return true; });
|
|
32
|
-
})),
|
|
33
|
-
Promise.all(toRemove.map(function (toRemove) {
|
|
34
|
-
var key = _this.keys.buildRBSegmentKey(toRemove);
|
|
35
|
-
return _this.wrapper.del(key);
|
|
36
|
-
}))
|
|
28
|
+
Promise.all(toAdd.map(function (rbSegment) { return _this.add(rbSegment); })),
|
|
29
|
+
Promise.all(toRemove.map(function (name) { return _this.remove(name); }))
|
|
37
30
|
]).then(function (_a) {
|
|
38
31
|
var added = _a[1], removed = _a[2];
|
|
39
|
-
return
|
|
32
|
+
return toRemove.filter(function (_, i) { return removed[i]; })
|
|
33
|
+
.concat(toAdd.filter(function (_, i) { return added[i]; }).map(function (rbSegment) { return rbSegment.name; }));
|
|
40
34
|
});
|
|
41
35
|
};
|
|
36
|
+
RBSegmentsCachePluggable.prototype.add = function (rbSegment) {
|
|
37
|
+
var _this = this;
|
|
38
|
+
var key = this.keys.buildRBSegmentKey(rbSegment.name);
|
|
39
|
+
return this.get(rbSegment.name).then(function (previous) {
|
|
40
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber)
|
|
41
|
+
return false;
|
|
42
|
+
var stringifiedNewRBSegment = JSON.stringify(rbSegment);
|
|
43
|
+
return _this.wrapper.set(key, stringifiedNewRBSegment).then(function () { return true; });
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
RBSegmentsCachePluggable.prototype.remove = function (name) {
|
|
47
|
+
var key = this.keys.buildRBSegmentKey(name);
|
|
48
|
+
return this.wrapper.del(key);
|
|
49
|
+
};
|
|
42
50
|
RBSegmentsCachePluggable.prototype.setChangeNumber = function (changeNumber) {
|
|
43
51
|
if (changeNumber !== undefined) {
|
|
44
52
|
return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { __assign } from "tslib";
|
|
2
|
+
import { CONTROL } from '../../../utils/constants';
|
|
3
|
+
/**
|
|
4
|
+
* Factory of a stateful function that, given the current mocked definitions, returns the changes since the previous call
|
|
5
|
+
* as an `IDefinitionChangesResponse`, i.e., the full definitions that were added or modified, and the names of the removed ones.
|
|
6
|
+
* Definitions are compared by value. The response `till` is an incrementing value based on the current time.
|
|
7
|
+
* Definitions have no `changeNumber`, so storages never consider them outdated.
|
|
8
|
+
*
|
|
9
|
+
* The response has no `d` property if there are no changes, except for the first call, which reports the (possibly empty) mock.
|
|
10
|
+
* It is meant to be used by `IDefinitionsParser` implementations, such as `splitsParserFromSettings`.
|
|
11
|
+
*/
|
|
12
|
+
export function definitionChangesBuilderFactory() {
|
|
13
|
+
var previous; // serialized definitions of the last reported mock, by name
|
|
14
|
+
var lastTill = 0;
|
|
15
|
+
return function definitionChangesBuilder(mock) {
|
|
16
|
+
var current = {};
|
|
17
|
+
var updatedNames = Object.keys(mock).reduce(function (updatedNames, name) {
|
|
18
|
+
current[name] = JSON.stringify(mock[name]);
|
|
19
|
+
if (!previous || previous[name] !== current[name])
|
|
20
|
+
updatedNames.push(name);
|
|
21
|
+
return updatedNames;
|
|
22
|
+
}, []);
|
|
23
|
+
var removed = previous ? Object.keys(previous).filter(function (name) { return current[name] === undefined; }) : [];
|
|
24
|
+
// Only the first call reports an empty change
|
|
25
|
+
if (previous && updatedNames.length === 0 && removed.length === 0)
|
|
26
|
+
return {};
|
|
27
|
+
previous = current;
|
|
28
|
+
// Incrementing `till` based on the current time
|
|
29
|
+
var till = lastTill = Math.max(Date.now(), lastTill + 1);
|
|
30
|
+
var updated = updatedNames.map(function (name) { return (__assign({ name: name, status: 'ACTIVE', killed: false, trafficAllocation: 100, defaultTreatment: CONTROL }, mock[name])); });
|
|
31
|
+
return { d: { updated: updated, removed: removed, till: till } };
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,42 +1,15 @@
|
|
|
1
|
-
import { isObject, forOwn
|
|
1
|
+
import { isObject, forOwn } from '../../../utils/lang';
|
|
2
2
|
import { parseCondition } from './parseCondition';
|
|
3
|
-
|
|
4
|
-
if (typeof prev !== typeof curr)
|
|
5
|
-
return true;
|
|
6
|
-
if (typeof prev === 'string') { // strings treatments, just compare
|
|
7
|
-
return prev !== curr;
|
|
8
|
-
}
|
|
9
|
-
else { // has treatment and config, compare both
|
|
10
|
-
return prev.treatment !== curr.treatment || prev.config !== curr.config;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
3
|
+
import { definitionChangesBuilderFactory } from './definitionChangesBuilder';
|
|
13
4
|
export function splitsParserFromSettingsFactory() {
|
|
14
|
-
var
|
|
15
|
-
function mockUpdated(currentData) {
|
|
16
|
-
var names = Object.keys(currentData);
|
|
17
|
-
// Different amount of items
|
|
18
|
-
if (names.length !== Object.keys(previousMock).length) {
|
|
19
|
-
previousMock = merge({}, currentData);
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
return names.some(function (name) {
|
|
23
|
-
var newSplit = !previousMock[name];
|
|
24
|
-
var newTreatment = hasTreatmentChanged(previousMock[name], currentData[name]);
|
|
25
|
-
var changed = newSplit || newTreatment;
|
|
26
|
-
if (changed)
|
|
27
|
-
previousMock = merge({}, currentData);
|
|
28
|
-
return changed;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
5
|
+
var definitionChangesBuilder = definitionChangesBuilderFactory();
|
|
31
6
|
/**
|
|
32
7
|
*
|
|
33
8
|
* @param settings - validated object with mocked features mapping.
|
|
34
9
|
*/
|
|
35
10
|
return function splitsParserFromSettings(settings) {
|
|
36
11
|
var features = settings.features || {};
|
|
37
|
-
|
|
38
|
-
return false;
|
|
39
|
-
var splitObjects = {};
|
|
12
|
+
var mock = {};
|
|
40
13
|
forOwn(features, function (data, splitName) {
|
|
41
14
|
var treatment = data;
|
|
42
15
|
var config = null;
|
|
@@ -47,12 +20,12 @@ export function splitsParserFromSettingsFactory() {
|
|
|
47
20
|
var configurations = {};
|
|
48
21
|
if (config !== null)
|
|
49
22
|
configurations[treatment] = config;
|
|
50
|
-
|
|
23
|
+
mock[splitName] = {
|
|
51
24
|
trafficTypeName: 'localhost',
|
|
52
25
|
conditions: [parseCondition({ treatment: treatment })],
|
|
53
26
|
configurations: configurations
|
|
54
27
|
};
|
|
55
28
|
});
|
|
56
|
-
return
|
|
29
|
+
return definitionChangesBuilder(mock);
|
|
57
30
|
};
|
|
58
31
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { forOwn } from '../../../utils/lang';
|
|
2
1
|
import { syncTaskFactory } from '../../syncTask';
|
|
3
|
-
import { CONTROL } from '../../../utils/constants';
|
|
4
2
|
import { SDK_DEFINITIONS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED, FLAGS_UPDATE, SEGMENTS_UPDATE } from '../../../readiness/constants';
|
|
5
3
|
import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
|
|
6
4
|
/**
|
|
@@ -10,53 +8,35 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
10
8
|
var log = settings.log, definitions = storage.definitions;
|
|
11
9
|
var startingUp = true;
|
|
12
10
|
return function objectUpdater() {
|
|
13
|
-
var
|
|
14
|
-
var loadError = null;
|
|
15
|
-
var splitsMock = {};
|
|
11
|
+
var changes;
|
|
16
12
|
try {
|
|
17
|
-
|
|
13
|
+
changes = splitsParser(settings);
|
|
18
14
|
}
|
|
19
15
|
catch (err) {
|
|
20
|
-
loadError = err;
|
|
21
16
|
log.error(ERROR_SYNC_OFFLINE_LOADING, [err]);
|
|
22
17
|
}
|
|
23
|
-
if (!
|
|
24
|
-
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
|
|
25
|
-
forOwn(splitsMock, function (val, name) {
|
|
26
|
-
// @ts-ignore Split changeNumber and seed is undefined in localhost mode
|
|
27
|
-
splits.push({
|
|
28
|
-
name: name,
|
|
29
|
-
status: 'ACTIVE',
|
|
30
|
-
killed: false,
|
|
31
|
-
trafficAllocation: 100,
|
|
32
|
-
defaultTreatment: CONTROL,
|
|
33
|
-
conditions: val.conditions || [],
|
|
34
|
-
configurations: val.configurations,
|
|
35
|
-
trafficTypeName: val.trafficTypeName
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
return Promise.all([
|
|
39
|
-
definitions.clear(),
|
|
40
|
-
definitions.update(splits, [], Date.now())
|
|
41
|
-
]).then(function () {
|
|
42
|
-
readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [] });
|
|
43
|
-
if (startingUp) {
|
|
44
|
-
startingUp = false;
|
|
45
|
-
Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
|
|
46
|
-
// Emits SDK_READY_FROM_CACHE
|
|
47
|
-
if (!cacheMetadata.initialCacheLoad) {
|
|
48
|
-
readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
|
|
49
|
-
}
|
|
50
|
-
// Emits SDK_READY
|
|
51
|
-
readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return true;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
18
|
+
if (!changes || !changes.d)
|
|
58
19
|
return Promise.resolve(true);
|
|
59
|
-
|
|
20
|
+
var _a = changes.d, updated = _a.updated, removed = _a.removed, till = _a.till;
|
|
21
|
+
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(changes.d)]);
|
|
22
|
+
// On start, clear definitions that might be in the storage from a previous session (InLocalStorage), since the parser only tracks its own changes.
|
|
23
|
+
return Promise.resolve(startingUp && definitions.clear()).then(function () {
|
|
24
|
+
return definitions.update(updated, removed, till);
|
|
25
|
+
}).then(function (names) {
|
|
26
|
+
readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: names });
|
|
27
|
+
if (startingUp) {
|
|
28
|
+
startingUp = false;
|
|
29
|
+
Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
|
|
30
|
+
// Emits SDK_READY_FROM_CACHE
|
|
31
|
+
if (!cacheMetadata.initialCacheLoad) {
|
|
32
|
+
readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
|
|
33
|
+
}
|
|
34
|
+
// Emits SDK_READY
|
|
35
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
60
40
|
};
|
|
61
41
|
}
|
|
62
42
|
/**
|
|
@@ -68,9 +68,8 @@ export function computeMutation(update, segments, filters) {
|
|
|
68
68
|
else {
|
|
69
69
|
accum.removed.push(ruleEntity.name);
|
|
70
70
|
}
|
|
71
|
-
accum.names.push(ruleEntity.name);
|
|
72
71
|
return accum;
|
|
73
|
-
}, { updated: [], removed: update.removed
|
|
72
|
+
}, { updated: [], removed: update.removed });
|
|
74
73
|
}
|
|
75
74
|
function convertInstantUpdateToDefinitionChanges(instantUpdate) {
|
|
76
75
|
var payload = instantUpdate.payload, changeNumber = instantUpdate.changeNumber;
|
|
@@ -142,30 +141,28 @@ export function definitionChangesUpdaterFactory(log, definitionChangesFetcher, s
|
|
|
142
141
|
.then(function (definitionChanges) {
|
|
143
142
|
var usedSegments = new Set();
|
|
144
143
|
var updatedDefinitions = [];
|
|
145
|
-
var ffUpdate = false;
|
|
146
144
|
if (definitionChanges.d) {
|
|
147
|
-
var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed
|
|
148
|
-
updatedDefinitions = names;
|
|
145
|
+
var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed;
|
|
149
146
|
log.debug(SYNC_UPDATE, [definitionChangesFetcher.type, updated.length, removed.length]);
|
|
150
|
-
|
|
147
|
+
updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
|
|
151
148
|
}
|
|
152
|
-
var
|
|
149
|
+
var updatedRBSNames = [];
|
|
153
150
|
if (definitionChanges.rbs) {
|
|
154
151
|
var _b = computeMutation(definitionChanges.rbs, usedSegments), updated = _b.updated, removed = _b.removed;
|
|
155
152
|
log.debug(SYNC_UPDATE, ['rule-based segments', updated.length, removed.length]);
|
|
156
|
-
|
|
153
|
+
updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
|
|
157
154
|
}
|
|
158
|
-
return Promise.all([
|
|
155
|
+
return Promise.all([updatedDefinitions, updatedRBSNames,
|
|
159
156
|
// @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
|
|
160
157
|
segments.registerSegments(setToArray(usedSegments))
|
|
161
158
|
]).then(function (_a) {
|
|
162
|
-
var
|
|
159
|
+
var updatedDefinitions = _a[0], updatedRBSNames = _a[1];
|
|
163
160
|
if (storage.save)
|
|
164
161
|
storage.save();
|
|
165
162
|
startingUp = false;
|
|
166
163
|
if (definitionsEventEmitter) {
|
|
167
164
|
// To emit SDK_DEFINITIONS_ARRIVED for server-side SDK, we must wait for all registered segments to be fetched
|
|
168
|
-
return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((
|
|
165
|
+
return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((updatedDefinitions.length > 0 || updatedRBSNames.length > 0) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
|
|
169
166
|
.catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
|
|
170
167
|
.then(function (emitSplitsArrivedEvent) {
|
|
171
168
|
// emit SDK events
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STREAMING_PARSING_SPLIT_UPDATE } from '../../../logger/constants';
|
|
2
|
-
import { SDK_DEFINITIONS_ARRIVED } from '../../../readiness/constants';
|
|
2
|
+
import { SDK_DEFINITIONS_ARRIVED, FLAGS_UPDATE } from '../../../readiness/constants';
|
|
3
3
|
import { Backoff } from '../../../utils/Backoff';
|
|
4
4
|
import { SPLITS } from '../../../utils/constants';
|
|
5
5
|
import { RB_SEGMENT_UPDATE } from '../constants';
|
|
@@ -118,7 +118,7 @@ export function DefinitionsUpdateWorker(params, pollingManager) {
|
|
|
118
118
|
var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
|
|
119
119
|
if (storage.definitions.killLocally(splitName, defaultTreatment, changeNumber)) {
|
|
120
120
|
// trigger an SDK_UPDATE if Split was killed locally
|
|
121
|
-
definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, true);
|
|
121
|
+
definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [splitName] }, true /* isSplitKill */);
|
|
122
122
|
}
|
|
123
123
|
// queues the SplitChanges fetch (only if changeNumber is newer)
|
|
124
124
|
ff.put({ changeNumber: changeNumber });
|
package/package.json
CHANGED
|
@@ -12,13 +12,14 @@ export abstract class AbstractDefinitionsCacheAsync implements IDefinitionsCache
|
|
|
12
12
|
protected abstract remove(name: string): Promise<boolean>
|
|
13
13
|
protected abstract setChangeNumber(changeNumber?: number): MaybeThenable<boolean | void>
|
|
14
14
|
|
|
15
|
-
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): Promise<
|
|
15
|
+
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): Promise<string[]> {
|
|
16
16
|
return Promise.all([
|
|
17
17
|
this.setChangeNumber(changeNumber),
|
|
18
18
|
Promise.all(toAdd.map(addedFF => this.add(addedFF))),
|
|
19
19
|
Promise.all(toRemove.map(removedFF => this.remove(removedFF)))
|
|
20
20
|
]).then(([, added, removed]) => {
|
|
21
|
-
return
|
|
21
|
+
return toRemove.filter((_, i) => removed[i])
|
|
22
|
+
.concat(toAdd.filter((_, i) => added[i]).map(addedFF => addedFF.name));
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -13,9 +13,9 @@ export abstract class AbstractDefinitionsCacheSync implements IDefinitionsCacheS
|
|
|
13
13
|
protected abstract remove(name: string): boolean
|
|
14
14
|
protected abstract setChangeNumber(changeNumber?: number): boolean | void
|
|
15
15
|
|
|
16
|
-
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number):
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): string[] {
|
|
17
|
+
const updated = toRemove.filter(removedFF => this.remove(removedFF))
|
|
18
|
+
.concat(toAdd.filter(addedFF => this.add(addedFF)).map(addedFF => addedFF.name));
|
|
19
19
|
this.setChangeNumber(changeNumber);
|
|
20
20
|
return updated;
|
|
21
21
|
}
|
|
@@ -79,13 +79,15 @@ export class DefinitionsCacheInLocal extends AbstractDefinitionsCacheSync {
|
|
|
79
79
|
this.hasSync = false;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
add(definition: IDefinition) {
|
|
82
|
+
add(definition: IDefinition): boolean {
|
|
83
83
|
const name = definition.name;
|
|
84
84
|
const definitionKey = this.keys.buildDefinitionKey(name);
|
|
85
85
|
const definitionFromStorage = this.storage.getItem(definitionKey);
|
|
86
86
|
const previousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : null;
|
|
87
87
|
|
|
88
88
|
if (previousDefinition) {
|
|
89
|
+
if (previousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
90
|
+
|
|
89
91
|
this._decrementCounts(previousDefinition);
|
|
90
92
|
this.removeFromSets(previousDefinition.name, previousDefinition.sets);
|
|
91
93
|
}
|
|
@@ -25,9 +25,9 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
25
25
|
this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number):
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[] {
|
|
29
|
+
const updated = toRemove.filter(name => this.remove(name))
|
|
30
|
+
.concat(toAdd.filter(rbSegment => this.add(rbSegment)).map(rbSegment => rbSegment.name));
|
|
31
31
|
this.setChangeNumber(changeNumber);
|
|
32
32
|
return updated;
|
|
33
33
|
}
|
|
@@ -54,6 +54,8 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
54
54
|
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
|
|
55
55
|
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
|
|
56
56
|
|
|
57
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
58
|
+
|
|
57
59
|
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
|
|
58
60
|
|
|
59
61
|
let usesSegmentsDiff = 0;
|
|
@@ -31,6 +31,7 @@ export class DefinitionsCacheInMemory extends AbstractDefinitionsCacheSync {
|
|
|
31
31
|
const name = definition.name;
|
|
32
32
|
const previousDefinition = this.get(name);
|
|
33
33
|
if (previousDefinition) { // We had this Split already
|
|
34
|
+
if (previousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
34
35
|
|
|
35
36
|
const previousTtName = previousDefinition.trafficTypeName;
|
|
36
37
|
this.ttCache[previousTtName]--;
|
|
@@ -15,9 +15,9 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
|
|
|
15
15
|
this.segmentsCount = 0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number):
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[] {
|
|
19
|
+
const updated = toRemove.filter(name => this.remove(name))
|
|
20
|
+
.concat(toAdd.filter(rbSegment => this.add(rbSegment)).map(rbSegment => rbSegment.name));
|
|
21
21
|
if (changeNumber !== undefined) this.changeNumber = changeNumber;
|
|
22
22
|
return updated;
|
|
23
23
|
}
|
|
@@ -25,6 +25,7 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
|
|
|
25
25
|
private add(rbSegment: IRBSegment): boolean {
|
|
26
26
|
const name = rbSegment.name;
|
|
27
27
|
const previous = this.get(name);
|
|
28
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
28
29
|
if (previous && usesSegments(previous)) this.segmentsCount--;
|
|
29
30
|
|
|
30
31
|
this.cache[name] = rbSegment;
|
|
@@ -91,6 +91,8 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
91
91
|
let parsedPreviousDefinition: IDefinition, stringifiedNewDefinition;
|
|
92
92
|
try {
|
|
93
93
|
parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
|
|
94
|
+
if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
95
|
+
|
|
94
96
|
stringifiedNewDefinition = JSON.stringify(definition);
|
|
95
97
|
} catch (e) {
|
|
96
98
|
throw new Error('Error parsing feature flag definition: ' + e);
|
|
@@ -104,8 +106,8 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
104
106
|
return this._incrementCounts(definition).then(() => {
|
|
105
107
|
if (parsedPreviousDefinition) return this._decrementCounts(parsedPreviousDefinition);
|
|
106
108
|
});
|
|
107
|
-
}).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets));
|
|
108
|
-
})
|
|
109
|
+
}).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets)).then(() => true);
|
|
110
|
+
});
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/**
|
|
@@ -115,11 +117,11 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
115
117
|
*/
|
|
116
118
|
remove(name: string) {
|
|
117
119
|
return this.get(name).then((definition) => {
|
|
118
|
-
if (definition)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
if (!definition) return false;
|
|
121
|
+
|
|
122
|
+
return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets)).then(() => {
|
|
123
|
+
return this.redis.del(this.keys.buildDefinitionKey(name)).then((status: number) => status === 1);
|
|
124
|
+
});
|
|
123
125
|
});
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -37,23 +37,32 @@ export class RBSegmentsCacheInRedis implements IRBSegmentsCacheAsync {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<
|
|
40
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<string[]> {
|
|
41
41
|
return Promise.all([
|
|
42
42
|
this.setChangeNumber(changeNumber),
|
|
43
|
-
Promise.all(toAdd.map(
|
|
44
|
-
|
|
45
|
-
const stringifiedNewRBSegment = JSON.stringify(toAdd);
|
|
46
|
-
return this.redis.set(key, stringifiedNewRBSegment).then(() => true);
|
|
47
|
-
})),
|
|
48
|
-
Promise.all(toRemove.map(toRemove => {
|
|
49
|
-
const key = this.keys.buildRBSegmentKey(toRemove);
|
|
50
|
-
return this.redis.del(key).then((status: number) => status === 1);
|
|
51
|
-
}))
|
|
43
|
+
Promise.all(toAdd.map(rbSegment => this.add(rbSegment))),
|
|
44
|
+
Promise.all(toRemove.map(name => this.remove(name)))
|
|
52
45
|
]).then(([, added, removed]) => {
|
|
53
|
-
return
|
|
46
|
+
return toRemove.filter((_, i) => removed[i])
|
|
47
|
+
.concat(toAdd.filter((_, i) => added[i]).map(rbSegment => rbSegment.name));
|
|
54
48
|
});
|
|
55
49
|
}
|
|
56
50
|
|
|
51
|
+
private add(rbSegment: IRBSegment): Promise<boolean> {
|
|
52
|
+
const key = this.keys.buildRBSegmentKey(rbSegment.name);
|
|
53
|
+
return this.get(rbSegment.name).then(previous => {
|
|
54
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
55
|
+
|
|
56
|
+
const stringifiedNewRBSegment = JSON.stringify(rbSegment);
|
|
57
|
+
return this.redis.set(key, stringifiedNewRBSegment).then(() => true);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private remove(name: string): Promise<boolean> {
|
|
62
|
+
const key = this.keys.buildRBSegmentKey(name);
|
|
63
|
+
return this.redis.del(key).then((status: number) => status === 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
setChangeNumber(changeNumber?: number) {
|
|
58
67
|
if (changeNumber !== undefined) {
|
|
59
68
|
return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
|
|
@@ -75,11 +75,14 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
|
|
|
75
75
|
let parsedPreviousDefinition: IDefinition, stringifiedNewDefinition;
|
|
76
76
|
try {
|
|
77
77
|
parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
|
|
78
|
+
if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
79
|
+
|
|
78
80
|
stringifiedNewDefinition = JSON.stringify(definition);
|
|
79
81
|
} catch (e) {
|
|
80
82
|
throw new Error('Error parsing feature flag definition: ' + e);
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
|
|
83
86
|
return this.wrapper.set(definitionKey, stringifiedNewDefinition).then(() => {
|
|
84
87
|
// avoid unnecessary increment/decrement operations
|
|
85
88
|
if (parsedPreviousDefinition && parsedPreviousDefinition.trafficTypeName === definition.trafficTypeName) return;
|
|
@@ -88,8 +91,8 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
|
|
|
88
91
|
return this._incrementCounts(definition).then(() => {
|
|
89
92
|
if (parsedPreviousDefinition) return this._decrementCounts(parsedPreviousDefinition);
|
|
90
93
|
});
|
|
91
|
-
}).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets));
|
|
92
|
-
})
|
|
94
|
+
}).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets)).then(() => true);
|
|
95
|
+
});
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
/**
|
|
@@ -99,11 +102,11 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
|
|
|
99
102
|
*/
|
|
100
103
|
remove(name: string) {
|
|
101
104
|
return this.get(name).then((definition) => {
|
|
102
|
-
if (definition)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (!definition) return false;
|
|
106
|
+
|
|
107
|
+
return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets)).then(() => {
|
|
108
|
+
return this.wrapper.del(this.keys.buildDefinitionKey(name));
|
|
109
|
+
});
|
|
107
110
|
});
|
|
108
111
|
}
|
|
109
112
|
|