@splitsoftware/splitio-commons 1.11.0 → 1.12.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +4 -0
- package/cjs/evaluator/index.js +19 -3
- package/cjs/logger/constants.js +4 -2
- package/cjs/logger/messages/warn.js +3 -1
- package/cjs/sdkClient/client.js +1 -1
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +2 -7
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +2 -10
- package/cjs/storages/pluggable/SplitsCachePluggable.js +25 -8
- package/cjs/storages/pluggable/index.js +1 -1
- package/cjs/utils/lang/sets.js +11 -1
- package/cjs/utils/settingsValidation/index.js +1 -1
- package/cjs/utils/settingsValidation/splitFilters.js +17 -10
- package/esm/evaluator/index.js +20 -4
- package/esm/logger/constants.js +2 -0
- package/esm/logger/messages/warn.js +3 -1
- package/esm/sdkClient/client.js +1 -1
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +3 -8
- package/esm/storages/inMemory/SplitsCacheInMemory.js +3 -11
- package/esm/storages/pluggable/SplitsCachePluggable.js +26 -9
- package/esm/storages/pluggable/index.js +1 -1
- package/esm/utils/lang/sets.js +9 -0
- package/esm/utils/settingsValidation/index.js +1 -1
- package/esm/utils/settingsValidation/splitFilters.js +9 -2
- package/package.json +2 -2
- package/src/evaluator/index.ts +24 -4
- package/src/logger/constants.ts +2 -0
- package/src/logger/messages/warn.ts +9 -7
- package/src/sdkClient/client.ts +1 -1
- package/src/storages/AbstractSplitsCacheAsync.ts +1 -1
- package/src/storages/AbstractSplitsCacheSync.ts +1 -1
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +4 -10
- package/src/storages/inMemory/SplitsCacheInMemory.ts +6 -14
- package/src/storages/inRedis/SplitsCacheInRedis.ts +1 -1
- package/src/storages/pluggable/SplitsCachePluggable.ts +32 -10
- package/src/storages/pluggable/index.ts +1 -1
- package/src/storages/types.ts +5 -5
- package/src/types.ts +0 -2
- package/src/utils/lang/sets.ts +9 -1
- package/src/utils/settingsValidation/index.ts +1 -1
- package/src/utils/settingsValidation/splitFilters.ts +13 -6
- package/types/evaluator/index.d.ts +1 -1
- package/types/logger/constants.d.ts +2 -0
- package/types/sdkClient/identity.d.ts +6 -0
- package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
- package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +1 -1
- package/types/storages/inMemory/SplitsCacheInMemory.d.ts +1 -1
- package/types/storages/inRedis/SplitsCacheInRedis.d.ts +1 -1
- package/types/storages/pluggable/SplitsCachePluggable.d.ts +7 -6
- package/types/storages/types.d.ts +5 -5
- package/types/types.d.ts +0 -2
- package/types/utils/inputValidation/sdkKey.d.ts +7 -0
- package/types/utils/lang/sets.d.ts +1 -0
- package/types/utils/settingsValidation/splitFilters.d.ts +2 -1
- package/types/myLogger.d.ts +0 -5
- package/types/sdkClient/types.d.ts +0 -18
- package/types/storages/inMemory/CountsCacheInMemory.d.ts +0 -20
- package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +0 -20
- package/types/storages/inRedis/CountsCacheInRedis.d.ts +0 -9
- package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +0 -9
- package/types/storages/metadataBuilder.d.ts +0 -3
- package/types/sync/offline/LocalhostFromFile.d.ts +0 -2
- package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +0 -2
- package/types/sync/offline/updaters/splitChangesUpdater.d.ts +0 -0
- package/types/sync/submitters/eventsSyncTask.d.ts +0 -8
- package/types/sync/submitters/impressionCountsSubmitterInRedis.d.ts +0 -5
- package/types/sync/submitters/impressionCountsSyncTask.d.ts +0 -13
- package/types/sync/submitters/impressionsSyncTask.d.ts +0 -14
- package/types/sync/submitters/metricsSyncTask.d.ts +0 -12
- package/types/sync/submitters/submitterSyncTask.d.ts +0 -10
- package/types/sync/submitters/uniqueKeysSubmitterInRedis.d.ts +0 -5
- package/types/sync/syncTaskComposite.d.ts +0 -5
- package/types/trackers/filter/bloomFilter.d.ts +0 -10
- package/types/trackers/filter/dictionaryFilter.d.ts +0 -8
- package/types/trackers/filter/types.d.ts +0 -5
- package/types/utils/timeTracker/index.d.ts +0 -70
- /package/types/storages/inMemory/{uniqueKeysCacheInMemory.d.ts → UniqueKeysCacheInMemory.d.ts} +0 -0
- /package/types/storages/inMemory/{uniqueKeysCacheInMemoryCS.d.ts → UniqueKeysCacheInMemoryCS.d.ts} +0 -0
- /package/types/storages/inRedis/{uniqueKeysCacheInRedis.d.ts → UniqueKeysCacheInRedis.d.ts} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { __extends } from "tslib";
|
|
1
|
+
import { __extends, __spreadArray } from "tslib";
|
|
2
2
|
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
3
3
|
import { LOG_PREFIX } from './constants';
|
|
4
4
|
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
5
|
+
import { _Set, returnListDifference } from '../../utils/lang/sets';
|
|
5
6
|
/**
|
|
6
7
|
* ISplitsCacheAsync implementation for pluggable storages.
|
|
7
8
|
*/
|
|
@@ -13,11 +14,12 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
13
14
|
* @param keys Key builder.
|
|
14
15
|
* @param wrapper Adapted wrapper storage.
|
|
15
16
|
*/
|
|
16
|
-
function SplitsCachePluggable(log, keys, wrapper) {
|
|
17
|
+
function SplitsCachePluggable(log, keys, wrapper, splitFiltersValidation) {
|
|
17
18
|
var _this = _super.call(this) || this;
|
|
18
19
|
_this.log = log;
|
|
19
20
|
_this.keys = keys;
|
|
20
21
|
_this.wrapper = wrapper;
|
|
22
|
+
_this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
|
|
21
23
|
return _this;
|
|
22
24
|
}
|
|
23
25
|
SplitsCachePluggable.prototype._decrementCounts = function (split) {
|
|
@@ -32,6 +34,18 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
32
34
|
var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
33
35
|
return this.wrapper.incr(ttKey);
|
|
34
36
|
};
|
|
37
|
+
SplitsCachePluggable.prototype._updateFlagSets = function (featureFlagName, flagSetsOfRemovedFlag, flagSetsOfAddedFlag) {
|
|
38
|
+
var _this = this;
|
|
39
|
+
var removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
40
|
+
var addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
41
|
+
if (this.flagSetsFilter.length > 0) {
|
|
42
|
+
addToFlagSets = addToFlagSets.filter(function (flagSet) {
|
|
43
|
+
return _this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === flagSet; });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
var items = [featureFlagName];
|
|
47
|
+
return Promise.all(__spreadArray(__spreadArray([], removeFromFlagSets.map(function (flagSetName) { return _this.wrapper.removeItems(_this.keys.buildFlagSetKey(flagSetName), items); }), true), addToFlagSets.map(function (flagSetName) { return _this.wrapper.addItems(_this.keys.buildFlagSetKey(flagSetName), items); }), true));
|
|
48
|
+
};
|
|
35
49
|
/**
|
|
36
50
|
* Add a given split.
|
|
37
51
|
* The returned promise is resolved when the operation success
|
|
@@ -59,7 +73,7 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
59
73
|
if (parsedPreviousSplit)
|
|
60
74
|
return _this._decrementCounts(parsedPreviousSplit);
|
|
61
75
|
});
|
|
62
|
-
});
|
|
76
|
+
}).then(function () { return _this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets); });
|
|
63
77
|
}).then(function () { return true; });
|
|
64
78
|
};
|
|
65
79
|
/**
|
|
@@ -80,8 +94,9 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
80
94
|
var _this = this;
|
|
81
95
|
return this.getSplit(name).then(function (split) {
|
|
82
96
|
if (split) {
|
|
83
|
-
_this._decrementCounts(split);
|
|
97
|
+
return _this._decrementCounts(split).then(function () { return _this._updateFlagSets(name, split.sets); });
|
|
84
98
|
}
|
|
99
|
+
}).then(function () {
|
|
85
100
|
return _this.wrapper.del(_this.keys.buildSplitKey(name));
|
|
86
101
|
});
|
|
87
102
|
};
|
|
@@ -145,12 +160,14 @@ var SplitsCachePluggable = /** @class */ (function (_super) {
|
|
|
145
160
|
/**
|
|
146
161
|
* Get list of split names related to a given flag set names list.
|
|
147
162
|
* The returned promise is resolved with the list of split names,
|
|
148
|
-
* or rejected if wrapper operation fails.
|
|
149
|
-
* @todo this is a no-op method to be implemented
|
|
163
|
+
* or rejected if any wrapper operation fails.
|
|
150
164
|
*/
|
|
151
|
-
SplitsCachePluggable.prototype.getNamesByFlagSets = function () {
|
|
152
|
-
|
|
153
|
-
return Promise.
|
|
165
|
+
SplitsCachePluggable.prototype.getNamesByFlagSets = function (flagSets) {
|
|
166
|
+
var _this = this;
|
|
167
|
+
return Promise.all(flagSets.map(function (flagSet) {
|
|
168
|
+
var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
|
|
169
|
+
return _this.wrapper.getItems(flagSetKey);
|
|
170
|
+
})).then(function (namesByFlagSets) { return namesByFlagSets.map(function (namesByFlagSet) { return new _Set(namesByFlagSet); }); });
|
|
154
171
|
};
|
|
155
172
|
/**
|
|
156
173
|
* Check traffic type existence.
|
|
@@ -89,7 +89,7 @@ export function PluggableStorage(options) {
|
|
|
89
89
|
return e; // Propagate error for shared clients
|
|
90
90
|
});
|
|
91
91
|
return {
|
|
92
|
-
splits: new SplitsCachePluggable(log, keys, wrapper),
|
|
92
|
+
splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
|
|
93
93
|
segments: new SegmentsCachePluggable(log, keys, wrapper),
|
|
94
94
|
impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
|
|
95
95
|
impressionCounts: impressionCountsCache,
|
package/esm/utils/lang/sets.js
CHANGED
|
@@ -102,3 +102,12 @@ export function returnSetsUnion(set, set2) {
|
|
|
102
102
|
});
|
|
103
103
|
return result;
|
|
104
104
|
}
|
|
105
|
+
export function returnListDifference(list, list2) {
|
|
106
|
+
if (list === void 0) { list = []; }
|
|
107
|
+
if (list2 === void 0) { list2 = []; }
|
|
108
|
+
var result = new _Set(list);
|
|
109
|
+
list2.forEach(function (item) {
|
|
110
|
+
result.delete(item);
|
|
111
|
+
});
|
|
112
|
+
return setToArray(result);
|
|
113
|
+
}
|
|
@@ -176,7 +176,7 @@ export function settingsValidation(config, validationParams) {
|
|
|
176
176
|
withDefaults.sync.enabled = true;
|
|
177
177
|
}
|
|
178
178
|
// validate the `splitFilters` settings and parse splits query
|
|
179
|
-
var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters);
|
|
179
|
+
var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
180
180
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
181
181
|
withDefaults.sync.__splitFiltersValidation = splitFiltersValidation;
|
|
182
182
|
// ensure a valid user consent value
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
|
|
1
2
|
import { validateSplits } from '../inputValidation/splits';
|
|
2
|
-
import { WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
|
|
3
|
+
import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
|
|
3
4
|
import { objectAssign } from '../lang/objectAssign';
|
|
4
5
|
import { find, uniq } from '../lang';
|
|
5
6
|
// Split filters metadata.
|
|
@@ -117,6 +118,7 @@ function configuredFilter(validFilters, filterType) {
|
|
|
117
118
|
*
|
|
118
119
|
* @param {ILogger} log logger
|
|
119
120
|
* @param {any} maybeSplitFilters split filters configuration param provided by the user
|
|
121
|
+
* @param {string} mode settings mode
|
|
120
122
|
* @returns it returns an object with the following properties:
|
|
121
123
|
* - `validFilters`: the validated `splitFilters` configuration object defined by the user.
|
|
122
124
|
* - `queryString`: the parsed split filter query. it is null if all filters are invalid or all values in filters are invalid.
|
|
@@ -124,7 +126,7 @@ function configuredFilter(validFilters, filterType) {
|
|
|
124
126
|
*
|
|
125
127
|
* @throws Error if the some of the grouped list of values per filter exceeds the max allowed length
|
|
126
128
|
*/
|
|
127
|
-
export function validateSplitFilters(log, maybeSplitFilters) {
|
|
129
|
+
export function validateSplitFilters(log, maybeSplitFilters, mode) {
|
|
128
130
|
// Validation result schema
|
|
129
131
|
var res = {
|
|
130
132
|
validFilters: [],
|
|
@@ -134,6 +136,11 @@ export function validateSplitFilters(log, maybeSplitFilters) {
|
|
|
134
136
|
// do nothing if `splitFilters` param is not a non-empty array or mode is not STANDALONE
|
|
135
137
|
if (!maybeSplitFilters)
|
|
136
138
|
return res;
|
|
139
|
+
// Warn depending on the mode
|
|
140
|
+
if (mode === CONSUMER_MODE || mode === CONSUMER_PARTIAL_MODE) {
|
|
141
|
+
log.warn(WARN_SPLITS_FILTER_IGNORED);
|
|
142
|
+
return res;
|
|
143
|
+
}
|
|
137
144
|
// Check collection type
|
|
138
145
|
if (!Array.isArray(maybeSplitFilters) || maybeSplitFilters.length === 0) {
|
|
139
146
|
log.warn(WARN_SPLITS_FILTER_EMPTY);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splitsoftware/splitio-commons",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1-rc.0",
|
|
4
4
|
"description": "Split Javascript SDK common components",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build": "npm run build:cjs && npm run build:esm",
|
|
23
23
|
"build:esm": "rimraf esm && tsc -m es2015 --outDir esm -d true --declarationDir types",
|
|
24
24
|
"build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs",
|
|
25
|
-
"test": "jest",
|
|
25
|
+
"test": "jest --runInBand",
|
|
26
26
|
"test:coverage": "jest --coverage",
|
|
27
27
|
"all": "npm run check && npm run build && npm run test",
|
|
28
28
|
"publish:rc": "npm run check && npm run test && npm run build && npm publish --tag rc",
|
package/src/evaluator/index.ts
CHANGED
|
@@ -7,7 +7,8 @@ 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 { ISet, setToArray } from '../utils/lang/sets';
|
|
10
|
+
import { ISet, setToArray, returnSetsUnion, _Set } from '../utils/lang/sets';
|
|
11
|
+
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
|
|
11
12
|
|
|
12
13
|
const treatmentException = {
|
|
13
14
|
treatment: CONTROL,
|
|
@@ -94,8 +95,27 @@ export function evaluateFeaturesByFlagSets(
|
|
|
94
95
|
flagSets: string[],
|
|
95
96
|
attributes: SplitIO.Attributes | undefined,
|
|
96
97
|
storage: IStorageSync | IStorageAsync,
|
|
98
|
+
method: string,
|
|
97
99
|
): MaybeThenable<Record<string, IEvaluationResult>> {
|
|
98
|
-
let storedFlagNames: MaybeThenable<ISet<string
|
|
100
|
+
let storedFlagNames: MaybeThenable<ISet<string>[]>;
|
|
101
|
+
|
|
102
|
+
function evaluate(
|
|
103
|
+
featureFlagsByFlagSets: ISet<string>[],
|
|
104
|
+
) {
|
|
105
|
+
let featureFlags = new _Set();
|
|
106
|
+
for (let i = 0; i < flagSets.length; i++) {
|
|
107
|
+
const featureFlagByFlagSet = featureFlagsByFlagSets[i];
|
|
108
|
+
if (featureFlagByFlagSet.size) {
|
|
109
|
+
featureFlags = returnSetsUnion(featureFlags, featureFlagByFlagSet);
|
|
110
|
+
} else {
|
|
111
|
+
log.warn(WARN_FLAGSET_WITHOUT_FLAGS, [method, flagSets[i]]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return featureFlags.size ?
|
|
116
|
+
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
|
|
117
|
+
{};
|
|
118
|
+
}
|
|
99
119
|
|
|
100
120
|
// get features by flag sets
|
|
101
121
|
try {
|
|
@@ -107,11 +127,11 @@ export function evaluateFeaturesByFlagSets(
|
|
|
107
127
|
|
|
108
128
|
// evaluate related features
|
|
109
129
|
return thenable(storedFlagNames) ?
|
|
110
|
-
storedFlagNames.then((
|
|
130
|
+
storedFlagNames.then((storedFlagNames) => evaluate(storedFlagNames))
|
|
111
131
|
.catch(() => {
|
|
112
132
|
return {};
|
|
113
133
|
}) :
|
|
114
|
-
|
|
134
|
+
evaluate(storedFlagNames);
|
|
115
135
|
}
|
|
116
136
|
|
|
117
137
|
function getEvaluation(
|
package/src/logger/constants.ts
CHANGED
|
@@ -91,6 +91,7 @@ export const WARN_NOT_EXISTENT_SPLIT = 215;
|
|
|
91
91
|
export const WARN_LOWERCASE_TRAFFIC_TYPE = 216;
|
|
92
92
|
export const WARN_NOT_EXISTENT_TT = 217;
|
|
93
93
|
export const WARN_INTEGRATION_INVALID = 218;
|
|
94
|
+
export const WARN_SPLITS_FILTER_IGNORED = 219;
|
|
94
95
|
export const WARN_SPLITS_FILTER_INVALID = 220;
|
|
95
96
|
export const WARN_SPLITS_FILTER_EMPTY = 221;
|
|
96
97
|
export const WARN_SDK_KEY = 222;
|
|
@@ -99,6 +100,7 @@ export const STREAMING_PARSING_SPLIT_UPDATE = 224;
|
|
|
99
100
|
export const WARN_SPLITS_FILTER_INVALID_SET = 225;
|
|
100
101
|
export const WARN_SPLITS_FILTER_LOWERCASE_SET = 226;
|
|
101
102
|
export const WARN_FLAGSET_NOT_CONFIGURED = 227;
|
|
103
|
+
export const WARN_FLAGSET_WITHOUT_FLAGS = 228;
|
|
102
104
|
|
|
103
105
|
export const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
|
|
104
106
|
export const ERROR_LOGLEVEL_INVALID = 301;
|
|
@@ -24,15 +24,17 @@ export const codesWarn: [number, string][] = codesError.concat([
|
|
|
24
24
|
[c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in the Split user interface.'],
|
|
25
25
|
[c.WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'],
|
|
26
26
|
[c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding feature flag in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split user interface.'],
|
|
27
|
-
[c.WARN_FLAGSET_NOT_CONFIGURED, '%s:
|
|
27
|
+
[c.WARN_FLAGSET_NOT_CONFIGURED, '%s: you passed %s which is not part of the configured FlagSetsFilter, ignoring Flag Set.'],
|
|
28
28
|
// initialization / settings validation
|
|
29
|
-
[c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS+': %s integration item(s) at settings is invalid. %s'],
|
|
30
|
-
[c.
|
|
31
|
-
[c.
|
|
32
|
-
[c.
|
|
29
|
+
[c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS + ': %s integration item(s) at settings is invalid. %s'],
|
|
30
|
+
[c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS + ': feature flag filters are not applicable for Consumer modes where the SDK does not keep rollout data in sync. Filters were discarded'],
|
|
31
|
+
[c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS + ': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("bySet", "byName" or "byPrefix") and a list of "values".'],
|
|
32
|
+
[c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS + ': feature flag filter configuration must be a non-empty array of filter objects.'],
|
|
33
|
+
[c.WARN_SDK_KEY, c.LOG_PREFIX_SETTINGS + ': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
|
|
33
34
|
|
|
34
35
|
[c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
|
|
35
36
|
[c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'],
|
|
36
|
-
[c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS+': you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
|
|
37
|
-
[c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS+': flag set %s should be all lowercase - converting string to lowercase.'],
|
|
37
|
+
[c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS + ': you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
|
|
38
|
+
[c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS + ': flag set %s should be all lowercase - converting string to lowercase.'],
|
|
39
|
+
[c.WARN_FLAGSET_WITHOUT_FLAGS, '%s: you passed %s flag set that does not contain cached feature flag names. Please double check what flag sets are in use in the Split user interface.'],
|
|
38
40
|
]);
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -99,7 +99,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ?
|
|
102
|
-
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage) :
|
|
102
|
+
evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, method) :
|
|
103
103
|
isStorageSync(settings) ? {} : Promise.resolve({}); // Promisify if async
|
|
104
104
|
|
|
105
105
|
return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
|
|
@@ -18,7 +18,7 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
|
|
|
18
18
|
abstract getChangeNumber(): Promise<number>
|
|
19
19
|
abstract getAll(): Promise<ISplit[]>
|
|
20
20
|
abstract getSplitNames(): Promise<string[]>
|
|
21
|
-
abstract getNamesByFlagSets(flagSets: string[]): Promise<ISet<string
|
|
21
|
+
abstract getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
|
|
22
22
|
abstract trafficTypeExists(trafficType: string): Promise<boolean>
|
|
23
23
|
abstract clear(): Promise<boolean | void>
|
|
24
24
|
|
|
@@ -4,7 +4,7 @@ import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
|
|
|
4
4
|
import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
5
5
|
import { ILogger } from '../../logger/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
|
-
import { ISet, _Set,
|
|
7
|
+
import { ISet, _Set, setToArray } from '../../utils/lang/sets';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
@@ -257,19 +257,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
257
257
|
// if the filter didn't change, nothing is done
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
getNamesByFlagSets(flagSets: string[]): ISet<string>{
|
|
261
|
-
|
|
262
|
-
flagSets.forEach(flagSet => {
|
|
260
|
+
getNamesByFlagSets(flagSets: string[]): ISet<string>[] {
|
|
261
|
+
return flagSets.map(flagSet => {
|
|
263
262
|
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
264
263
|
let flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
|
|
265
264
|
|
|
266
|
-
|
|
267
|
-
const flagSetCache = new _Set(JSON.parse(flagSetFromLocalStorage));
|
|
268
|
-
toReturn = returnSetsUnion(toReturn, flagSetCache);
|
|
269
|
-
}
|
|
265
|
+
return new _Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
|
|
270
266
|
});
|
|
271
|
-
return toReturn;
|
|
272
|
-
|
|
273
267
|
}
|
|
274
268
|
|
|
275
269
|
private addToFlagSets(featureFlag: ISplit) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
|
|
2
2
|
import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
|
|
3
3
|
import { isFiniteNumber } from '../../utils/lang';
|
|
4
|
-
import { ISet, _Set
|
|
4
|
+
import { ISet, _Set } from '../../utils/lang/sets';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Default ISplitsCacheSync implementation that stores split definitions in memory.
|
|
@@ -16,9 +16,9 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
|
|
|
16
16
|
private splitsWithSegmentsCount: number = 0;
|
|
17
17
|
private flagSetsCache: Record<string, ISet<string>> = {};
|
|
18
18
|
|
|
19
|
-
constructor(splitFiltersValidation
|
|
19
|
+
constructor(splitFiltersValidation?: ISplitFiltersValidation) {
|
|
20
20
|
super();
|
|
21
|
-
this.flagSetsFilter = splitFiltersValidation.groupedFilters.bySet;
|
|
21
|
+
this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
clear() {
|
|
@@ -105,16 +105,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
|
|
|
105
105
|
return this.getChangeNumber() === -1 || this.splitsWithSegmentsCount > 0;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
getNamesByFlagSets(flagSets: string[]): ISet<string>{
|
|
109
|
-
|
|
110
|
-
flagSets.forEach(flagSet => {
|
|
111
|
-
const featureFlagNames = this.flagSetsCache[flagSet];
|
|
112
|
-
if (featureFlagNames) {
|
|
113
|
-
toReturn = returnSetsUnion(toReturn, featureFlagNames);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
return toReturn;
|
|
117
|
-
|
|
108
|
+
getNamesByFlagSets(flagSets: string[]): ISet<string>[] {
|
|
109
|
+
return flagSets.map(flagSet => this.flagSetsCache[flagSet] || new _Set());
|
|
118
110
|
}
|
|
119
111
|
|
|
120
112
|
private addToFlagSets(featureFlag: ISplit) {
|
|
@@ -129,7 +121,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
|
|
|
129
121
|
});
|
|
130
122
|
}
|
|
131
123
|
|
|
132
|
-
private removeFromFlagSets(featureFlagName
|
|
124
|
+
private removeFromFlagSets(featureFlagName: string, flagSets: string[] | undefined) {
|
|
133
125
|
if (!flagSets) return;
|
|
134
126
|
flagSets.forEach(flagSet => {
|
|
135
127
|
this.removeNames(flagSet, featureFlagName);
|
|
@@ -195,7 +195,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
|
|
|
195
195
|
* or rejected if wrapper operation fails.
|
|
196
196
|
* @todo this is a no-op method to be implemented
|
|
197
197
|
*/
|
|
198
|
-
getNamesByFlagSets(): Promise<ISet<string
|
|
198
|
+
getNamesByFlagSets(): Promise<ISet<string>[]> {
|
|
199
199
|
this.log.error(LOG_PREFIX + 'ByFlagSet/s evaluations are not supported with Redis storage yet.');
|
|
200
200
|
return Promise.reject();
|
|
201
201
|
}
|
|
@@ -2,10 +2,10 @@ import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
|
2
2
|
import { KeyBuilder } from '../KeyBuilder';
|
|
3
3
|
import { IPluggableStorageWrapper } from '../types';
|
|
4
4
|
import { ILogger } from '../../logger/types';
|
|
5
|
-
import { ISplit } from '../../dtos/types';
|
|
5
|
+
import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
8
|
-
import { ISet } from '../../utils/lang/sets';
|
|
8
|
+
import { ISet, _Set, returnListDifference } from '../../utils/lang/sets';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* ISplitsCacheAsync implementation for pluggable storages.
|
|
@@ -15,6 +15,7 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
15
15
|
private readonly log: ILogger;
|
|
16
16
|
private readonly keys: KeyBuilder;
|
|
17
17
|
private readonly wrapper: IPluggableStorageWrapper;
|
|
18
|
+
private readonly flagSetsFilter: string[];
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Create a SplitsCache that uses a storage wrapper.
|
|
@@ -22,11 +23,12 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
22
23
|
* @param keys Key builder.
|
|
23
24
|
* @param wrapper Adapted wrapper storage.
|
|
24
25
|
*/
|
|
25
|
-
constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper) {
|
|
26
|
+
constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper, splitFiltersValidation?: ISplitFiltersValidation) {
|
|
26
27
|
super();
|
|
27
28
|
this.log = log;
|
|
28
29
|
this.keys = keys;
|
|
29
30
|
this.wrapper = wrapper;
|
|
31
|
+
this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
private _decrementCounts(split: ISplit) {
|
|
@@ -41,6 +43,24 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
41
43
|
return this.wrapper.incr(ttKey);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
|
|
47
|
+
const removeFromFlagSets = returnListDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
48
|
+
|
|
49
|
+
let addToFlagSets = returnListDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
50
|
+
if (this.flagSetsFilter.length > 0) {
|
|
51
|
+
addToFlagSets = addToFlagSets.filter(flagSet => {
|
|
52
|
+
return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const items = [featureFlagName];
|
|
57
|
+
|
|
58
|
+
return Promise.all([
|
|
59
|
+
...removeFromFlagSets.map(flagSetName => this.wrapper.removeItems(this.keys.buildFlagSetKey(flagSetName), items)),
|
|
60
|
+
...addToFlagSets.map(flagSetName => this.wrapper.addItems(this.keys.buildFlagSetKey(flagSetName), items))
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
/**
|
|
45
65
|
* Add a given split.
|
|
46
66
|
* The returned promise is resolved when the operation success
|
|
@@ -67,7 +87,7 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
67
87
|
return this._incrementCounts(split).then(() => {
|
|
68
88
|
if (parsedPreviousSplit) return this._decrementCounts(parsedPreviousSplit);
|
|
69
89
|
});
|
|
70
|
-
});
|
|
90
|
+
}).then(() => this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets));
|
|
71
91
|
}).then(() => true);
|
|
72
92
|
}
|
|
73
93
|
|
|
@@ -88,8 +108,9 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
88
108
|
removeSplit(name: string) {
|
|
89
109
|
return this.getSplit(name).then((split) => {
|
|
90
110
|
if (split) {
|
|
91
|
-
this._decrementCounts(split);
|
|
111
|
+
return this._decrementCounts(split).then(() => this._updateFlagSets(name, split.sets));
|
|
92
112
|
}
|
|
113
|
+
}).then(() => {
|
|
93
114
|
return this.wrapper.del(this.keys.buildSplitKey(name));
|
|
94
115
|
});
|
|
95
116
|
}
|
|
@@ -158,12 +179,13 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
158
179
|
/**
|
|
159
180
|
* Get list of split names related to a given flag set names list.
|
|
160
181
|
* The returned promise is resolved with the list of split names,
|
|
161
|
-
* or rejected if wrapper operation fails.
|
|
162
|
-
* @todo this is a no-op method to be implemented
|
|
182
|
+
* or rejected if any wrapper operation fails.
|
|
163
183
|
*/
|
|
164
|
-
getNamesByFlagSets(): Promise<ISet<string
|
|
165
|
-
|
|
166
|
-
|
|
184
|
+
getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]> {
|
|
185
|
+
return Promise.all(flagSets.map(flagSet => {
|
|
186
|
+
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
187
|
+
return this.wrapper.getItems(flagSetKey);
|
|
188
|
+
})).then(namesByFlagSets => namesByFlagSets.map(namesByFlagSet => new _Set(namesByFlagSet)));
|
|
167
189
|
}
|
|
168
190
|
|
|
169
191
|
/**
|
|
@@ -105,7 +105,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
return {
|
|
108
|
-
splits: new SplitsCachePluggable(log, keys, wrapper),
|
|
108
|
+
splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
|
|
109
109
|
segments: new SegmentsCachePluggable(log, keys, wrapper),
|
|
110
110
|
impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
|
|
111
111
|
impressionCounts: impressionCountsCache,
|
package/src/storages/types.ts
CHANGED
|
@@ -44,10 +44,10 @@ export interface IPluggableStorageWrapper {
|
|
|
44
44
|
*
|
|
45
45
|
* @function del
|
|
46
46
|
* @param {string} key Item to delete
|
|
47
|
-
* @returns {Promise<
|
|
47
|
+
* @returns {Promise<boolean>} A promise that resolves if the operation success, whether the key existed and was removed (resolves with true) or it didn't exist (resolves with false).
|
|
48
48
|
* The promise rejects if the operation fails, for example, if there is a connection error.
|
|
49
49
|
*/
|
|
50
|
-
del: (key: string) => Promise<boolean
|
|
50
|
+
del: (key: string) => Promise<boolean>
|
|
51
51
|
/**
|
|
52
52
|
* Returns all keys matching the given prefix.
|
|
53
53
|
*
|
|
@@ -210,7 +210,7 @@ export interface ISplitsCacheBase {
|
|
|
210
210
|
// should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
|
|
211
211
|
checkCache(): MaybeThenable<boolean>,
|
|
212
212
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
|
|
213
|
-
getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string
|
|
213
|
+
getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
@@ -227,7 +227,7 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
227
227
|
clear(): void,
|
|
228
228
|
checkCache(): boolean,
|
|
229
229
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
|
|
230
|
-
getNamesByFlagSets(flagSets: string[]): ISet<string>
|
|
230
|
+
getNamesByFlagSets(flagSets: string[]): ISet<string>[]
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
@@ -244,7 +244,7 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
244
244
|
clear(): Promise<boolean | void>,
|
|
245
245
|
checkCache(): Promise<boolean>,
|
|
246
246
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
|
|
247
|
-
getNamesByFlagSets(flagSets: string[]): Promise<ISet<string
|
|
247
|
+
getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
/** Segments cache */
|
package/src/types.ts
CHANGED
|
@@ -197,8 +197,6 @@ interface ISharedSettings {
|
|
|
197
197
|
* List of feature flag filters. These filters are used to fetch a subset of the feature flag definitions in your environment, in order to reduce the delay of the SDK to be ready.
|
|
198
198
|
* This configuration is only meaningful when the SDK is working in "standalone" mode.
|
|
199
199
|
*
|
|
200
|
-
* At the moment, only one type of feature flag filter is supported: by name.
|
|
201
|
-
*
|
|
202
200
|
* Example:
|
|
203
201
|
* `splitFilter: [
|
|
204
202
|
* { type: 'byName', values: ['my_feature_flag_1', 'my_feature_flag_2'] }, // will fetch feature flags named 'my_feature_flag_1' and 'my_feature_flag_2'
|
package/src/utils/lang/sets.ts
CHANGED
|
@@ -114,8 +114,16 @@ export const _Set = __getSetConstructor();
|
|
|
114
114
|
|
|
115
115
|
export function returnSetsUnion<T>(set: ISet<T>, set2: ISet<T>): ISet<T> {
|
|
116
116
|
const result = new _Set(setToArray(set));
|
|
117
|
-
set2.forEach(
|
|
117
|
+
set2.forEach(value => {
|
|
118
118
|
result.add(value);
|
|
119
119
|
});
|
|
120
120
|
return result;
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
export function returnListDifference<T>(list: T[] = [], list2: T[] = []): T[] {
|
|
124
|
+
const result = new _Set(list);
|
|
125
|
+
list2.forEach(item => {
|
|
126
|
+
result.delete(item);
|
|
127
|
+
});
|
|
128
|
+
return setToArray(result);
|
|
129
|
+
}
|
|
@@ -202,7 +202,7 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// validate the `splitFilters` settings and parse splits query
|
|
205
|
-
const splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters);
|
|
205
|
+
const splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
206
206
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
207
207
|
withDefaults.sync.__splitFiltersValidation = splitFiltersValidation;
|
|
208
208
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
|
|
1
2
|
import { validateSplits } from '../inputValidation/splits';
|
|
2
3
|
import { ISplitFiltersValidation } from '../../dtos/types';
|
|
3
4
|
import { SplitIO } from '../../types';
|
|
4
5
|
import { ILogger } from '../../logger/types';
|
|
5
|
-
import { WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
|
|
6
|
+
import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SETS_FILTER_EXCLUSIVE, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
|
|
6
7
|
import { objectAssign } from '../lang/objectAssign';
|
|
7
8
|
import { find, uniq } from '../lang';
|
|
8
9
|
|
|
@@ -102,15 +103,15 @@ function queryStringBuilder(groupedFilters: Record<SplitIO.SplitFilterType, stri
|
|
|
102
103
|
function sanitizeFlagSets(log: ILogger, flagSets: string[]) {
|
|
103
104
|
let sanitizedSets = flagSets
|
|
104
105
|
.map(flagSet => {
|
|
105
|
-
if (CAPITAL_LETTERS_REGEX.test(flagSet)){
|
|
106
|
-
log.warn(WARN_SPLITS_FILTER_LOWERCASE_SET,[flagSet]);
|
|
106
|
+
if (CAPITAL_LETTERS_REGEX.test(flagSet)) {
|
|
107
|
+
log.warn(WARN_SPLITS_FILTER_LOWERCASE_SET, [flagSet]);
|
|
107
108
|
flagSet = flagSet.toLowerCase();
|
|
108
109
|
}
|
|
109
110
|
return flagSet;
|
|
110
111
|
})
|
|
111
112
|
.filter(flagSet => {
|
|
112
|
-
if (!VALID_FLAGSET_REGEX.test(flagSet)){
|
|
113
|
-
log.warn(WARN_SPLITS_FILTER_INVALID_SET, [flagSet,VALID_FLAGSET_REGEX,flagSet]);
|
|
113
|
+
if (!VALID_FLAGSET_REGEX.test(flagSet)) {
|
|
114
|
+
log.warn(WARN_SPLITS_FILTER_INVALID_SET, [flagSet, VALID_FLAGSET_REGEX, flagSet]);
|
|
114
115
|
return false;
|
|
115
116
|
}
|
|
116
117
|
if (typeof flagSet !== 'string') return false;
|
|
@@ -128,6 +129,7 @@ function configuredFilter(validFilters: SplitIO.SplitFilter[], filterType: Split
|
|
|
128
129
|
*
|
|
129
130
|
* @param {ILogger} log logger
|
|
130
131
|
* @param {any} maybeSplitFilters split filters configuration param provided by the user
|
|
132
|
+
* @param {string} mode settings mode
|
|
131
133
|
* @returns it returns an object with the following properties:
|
|
132
134
|
* - `validFilters`: the validated `splitFilters` configuration object defined by the user.
|
|
133
135
|
* - `queryString`: the parsed split filter query. it is null if all filters are invalid or all values in filters are invalid.
|
|
@@ -135,7 +137,7 @@ function configuredFilter(validFilters: SplitIO.SplitFilter[], filterType: Split
|
|
|
135
137
|
*
|
|
136
138
|
* @throws Error if the some of the grouped list of values per filter exceeds the max allowed length
|
|
137
139
|
*/
|
|
138
|
-
export function validateSplitFilters(log: ILogger, maybeSplitFilters: any): ISplitFiltersValidation {
|
|
140
|
+
export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode: string): ISplitFiltersValidation {
|
|
139
141
|
// Validation result schema
|
|
140
142
|
const res = {
|
|
141
143
|
validFilters: [],
|
|
@@ -145,6 +147,11 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any): ISpl
|
|
|
145
147
|
|
|
146
148
|
// do nothing if `splitFilters` param is not a non-empty array or mode is not STANDALONE
|
|
147
149
|
if (!maybeSplitFilters) return res;
|
|
150
|
+
// Warn depending on the mode
|
|
151
|
+
if (mode === CONSUMER_MODE || mode === CONSUMER_PARTIAL_MODE) {
|
|
152
|
+
log.warn(WARN_SPLITS_FILTER_IGNORED);
|
|
153
|
+
return res;
|
|
154
|
+
}
|
|
148
155
|
// Check collection type
|
|
149
156
|
if (!Array.isArray(maybeSplitFilters) || maybeSplitFilters.length === 0) {
|
|
150
157
|
log.warn(WARN_SPLITS_FILTER_EMPTY);
|