@splitsoftware/splitio-commons 1.13.2-rc.1 → 1.13.2-rc.11
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 -3
- package/cjs/evaluator/Engine.js +5 -6
- package/cjs/evaluator/combiners/ifelseif.js +2 -3
- package/cjs/evaluator/condition/index.js +2 -3
- package/cjs/evaluator/index.js +4 -5
- package/cjs/evaluator/matchers/gte.js +3 -3
- package/cjs/evaluator/matchers/index.js +11 -1
- package/cjs/evaluator/matchers/lte.js +3 -3
- package/cjs/evaluator/matchers/matcherTypes.js +6 -1
- package/cjs/evaluator/matchers/semver_between.js +14 -0
- package/cjs/evaluator/matchers/semver_eq.js +13 -0
- package/cjs/evaluator/matchers/semver_gte.js +13 -0
- package/cjs/evaluator/matchers/semver_inlist.js +17 -0
- package/cjs/evaluator/matchers/semver_lte.js +13 -0
- package/cjs/evaluator/matchers/string.js +2 -9
- package/cjs/evaluator/matchers/whitelist.js +3 -2
- package/cjs/evaluator/matchersTransform/index.js +21 -16
- package/cjs/evaluator/matchersTransform/whitelist.js +2 -3
- package/cjs/evaluator/parser/index.js +19 -6
- package/cjs/logger/constants.js +4 -4
- package/cjs/logger/index.js +8 -1
- package/cjs/logger/messages/debug.js +0 -1
- package/cjs/logger/messages/error.js +2 -1
- package/cjs/services/splitApi.js +5 -5
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -1
- package/cjs/storages/pluggable/index.js +1 -1
- package/cjs/utils/Semver.js +103 -0
- package/cjs/utils/labels/index.js +1 -1
- package/cjs/utils/settingsValidation/logger/pluggableLogger.js +1 -1
- package/esm/evaluator/Engine.js +5 -5
- package/esm/evaluator/combiners/ifelseif.js +2 -2
- package/esm/evaluator/condition/index.js +2 -2
- package/esm/evaluator/index.js +4 -4
- package/esm/evaluator/matchers/gte.js +3 -3
- package/esm/evaluator/matchers/index.js +11 -1
- package/esm/evaluator/matchers/lte.js +3 -3
- package/esm/evaluator/matchers/matcherTypes.js +6 -1
- package/esm/evaluator/matchers/semver_between.js +10 -0
- package/esm/evaluator/matchers/semver_eq.js +9 -0
- package/esm/evaluator/matchers/semver_gte.js +9 -0
- package/esm/evaluator/matchers/semver_inlist.js +13 -0
- package/esm/evaluator/matchers/semver_lte.js +9 -0
- package/esm/evaluator/matchers/string.js +3 -10
- package/esm/evaluator/matchers/whitelist.js +4 -3
- package/esm/evaluator/matchersTransform/index.js +21 -16
- package/esm/evaluator/matchersTransform/whitelist.js +2 -3
- package/esm/evaluator/parser/index.js +19 -6
- package/esm/logger/constants.js +1 -1
- package/esm/logger/index.js +9 -2
- package/esm/logger/messages/debug.js +0 -1
- package/esm/logger/messages/error.js +2 -1
- package/esm/services/splitApi.js +5 -5
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -1
- package/esm/storages/pluggable/index.js +1 -1
- package/esm/utils/Semver.js +100 -0
- package/esm/utils/labels/index.js +1 -1
- package/esm/utils/settingsValidation/logger/pluggableLogger.js +1 -1
- package/package.json +1 -2
- package/src/dtos/types.ts +34 -1
- package/src/evaluator/Engine.ts +5 -6
- package/src/evaluator/combiners/ifelseif.ts +2 -2
- package/src/evaluator/condition/index.ts +2 -2
- package/src/evaluator/index.ts +4 -4
- package/src/evaluator/matchers/between.ts +1 -1
- package/src/evaluator/matchers/boolean.ts +1 -1
- package/src/evaluator/matchers/cont_all.ts +1 -1
- package/src/evaluator/matchers/cont_any.ts +1 -1
- package/src/evaluator/matchers/cont_str.ts +1 -1
- package/src/evaluator/matchers/eq.ts +1 -1
- package/src/evaluator/matchers/eq_set.ts +1 -1
- package/src/evaluator/matchers/ew.ts +1 -1
- package/src/evaluator/matchers/gte.ts +4 -4
- package/src/evaluator/matchers/index.ts +28 -18
- package/src/evaluator/matchers/lte.ts +4 -4
- package/src/evaluator/matchers/matcherTypes.ts +6 -1
- package/src/evaluator/matchers/part_of.ts +1 -1
- package/src/evaluator/matchers/semver_between.ts +16 -0
- package/src/evaluator/matchers/semver_eq.ts +14 -0
- package/src/evaluator/matchers/semver_gte.ts +14 -0
- package/src/evaluator/matchers/semver_inlist.ts +17 -0
- package/src/evaluator/matchers/semver_lte.ts +14 -0
- package/src/evaluator/matchers/string.ts +5 -13
- package/src/evaluator/matchers/sw.ts +1 -1
- package/src/evaluator/matchers/whitelist.ts +6 -4
- package/src/evaluator/matchersTransform/index.ts +31 -23
- package/src/evaluator/matchersTransform/whitelist.ts +4 -5
- package/src/evaluator/parser/index.ts +19 -7
- package/src/evaluator/types.ts +3 -3
- package/src/logger/constants.ts +1 -1
- package/src/logger/index.ts +8 -2
- package/src/logger/messages/debug.ts +0 -1
- package/src/logger/messages/error.ts +2 -1
- package/src/services/splitApi.ts +4 -5
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -1
- package/src/storages/pluggable/index.ts +1 -1
- package/src/utils/Semver.ts +111 -0
- package/src/utils/labels/index.ts +1 -1
- package/src/utils/settingsValidation/logger/pluggableLogger.ts +1 -1
- package/types/dtos/types.d.ts +26 -1
- package/types/evaluator/matchers/sember_inlist.d.ts +3 -0
- package/types/evaluator/matchers/semver_between.d.ts +3 -0
- package/types/evaluator/matchers/semver_eq.d.ts +2 -0
- package/types/evaluator/matchers/semver_gte.d.ts +2 -0
- package/types/evaluator/matchers/semver_inlist.d.ts +2 -0
- package/types/evaluator/matchers/semver_lte.d.ts +2 -0
- package/types/evaluator/matchers/whitelist.d.ts +1 -2
- package/types/evaluator/matchersTransform/set.d.ts +2 -2
- package/types/evaluator/matchersTransform/string.d.ts +7 -0
- package/types/evaluator/matchersTransform/whitelist.d.ts +3 -3
- package/types/evaluator/types.d.ts +3 -3
- package/types/logger/constants.d.ts +1 -1
- package/types/utils/labels/index.d.ts +1 -1
- package/types/utils/semVer.d.ts +15 -0
- package/cjs/evaluator/matchersTransform/set.js +0 -10
- package/esm/evaluator/matchersTransform/set.js +0 -6
- package/src/evaluator/matchersTransform/set.ts +0 -8
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _Set } from '../../utils/lang/sets';
|
|
2
2
|
import { ILogger } from '../../logger/types';
|
|
3
3
|
import { ENGINE_MATCHER_WHITELIST } from '../../logger/constants';
|
|
4
4
|
|
|
5
|
-
export function whitelistMatcherContext(log: ILogger, ruleAttr:
|
|
5
|
+
export function whitelistMatcherContext(log: ILogger, ruleAttr: string[]) {
|
|
6
|
+
const whitelistSet = new _Set(ruleAttr);
|
|
7
|
+
|
|
6
8
|
return function whitelistMatcher(runtimeAttr: string): boolean {
|
|
7
|
-
|
|
9
|
+
const isInWhitelist = whitelistSet.has(runtimeAttr);
|
|
8
10
|
|
|
9
|
-
log.debug(ENGINE_MATCHER_WHITELIST, [runtimeAttr,
|
|
11
|
+
log.debug(ENGINE_MATCHER_WHITELIST, [runtimeAttr, ruleAttr.join(','), isInWhitelist]);
|
|
10
12
|
|
|
11
13
|
return isInWhitelist;
|
|
12
14
|
};
|
|
@@ -2,10 +2,9 @@ import { findIndex } from '../../utils/lang';
|
|
|
2
2
|
import { matcherTypes, matcherTypesMapper, matcherDataTypes } from '../matchers/matcherTypes';
|
|
3
3
|
import { segmentTransform } from './segment';
|
|
4
4
|
import { whitelistTransform } from './whitelist';
|
|
5
|
-
import { setTransform } from './set';
|
|
6
5
|
import { numericTransform } from './unaryNumeric';
|
|
7
6
|
import { zeroSinceHH, zeroSinceSS } from '../convertions';
|
|
8
|
-
import { IBetweenMatcherData, IInSegmentMatcherData, ISplitMatcher, IUnaryNumericMatcherData
|
|
7
|
+
import { IBetweenMatcherData, IInSegmentMatcherData, ISplitMatcher, IUnaryNumericMatcherData } from '../../dtos/types';
|
|
9
8
|
import { IMatcherDto } from '../types';
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -15,16 +14,17 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
|
|
|
15
14
|
|
|
16
15
|
let parsedMatchers = matchers.map(matcher => {
|
|
17
16
|
let {
|
|
18
|
-
matcherType
|
|
19
|
-
negate
|
|
20
|
-
keySelector
|
|
21
|
-
userDefinedSegmentMatcherData
|
|
22
|
-
whitelistMatcherData
|
|
23
|
-
unaryNumericMatcherData
|
|
24
|
-
betweenMatcherData
|
|
25
|
-
dependencyMatcherData
|
|
17
|
+
matcherType,
|
|
18
|
+
negate,
|
|
19
|
+
keySelector,
|
|
20
|
+
userDefinedSegmentMatcherData,
|
|
21
|
+
whitelistMatcherData, /* whitelistObject, provided by 'WHITELIST', 'IN_LIST_SEMVER', set and string matchers */
|
|
22
|
+
unaryNumericMatcherData,
|
|
23
|
+
betweenMatcherData,
|
|
24
|
+
dependencyMatcherData,
|
|
26
25
|
booleanMatcherData,
|
|
27
|
-
stringMatcherData
|
|
26
|
+
stringMatcherData,
|
|
27
|
+
betweenStringMatcherData
|
|
28
28
|
} = matcher;
|
|
29
29
|
|
|
30
30
|
let attribute = keySelector && keySelector.attribute;
|
|
@@ -34,28 +34,26 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
|
|
|
34
34
|
let value = undefined;
|
|
35
35
|
|
|
36
36
|
if (type === matcherTypes.IN_SEGMENT) {
|
|
37
|
-
value = segmentTransform(
|
|
38
|
-
} else if (type === matcherTypes.WHITELIST) {
|
|
39
|
-
value = whitelistTransform(whitelistObject as IWhitelistMatcherData);
|
|
37
|
+
value = segmentTransform(userDefinedSegmentMatcherData as IInSegmentMatcherData);
|
|
40
38
|
} else if (type === matcherTypes.EQUAL_TO) {
|
|
41
|
-
value = numericTransform(
|
|
39
|
+
value = numericTransform(unaryNumericMatcherData as IUnaryNumericMatcherData);
|
|
42
40
|
dataType = matcherDataTypes.NUMBER;
|
|
43
41
|
|
|
44
|
-
if ((
|
|
42
|
+
if ((unaryNumericMatcherData as IUnaryNumericMatcherData).dataType === 'DATETIME') {
|
|
45
43
|
value = zeroSinceHH(value);
|
|
46
44
|
dataType = matcherDataTypes.DATETIME;
|
|
47
45
|
}
|
|
48
46
|
} else if (type === matcherTypes.GREATER_THAN_OR_EQUAL_TO ||
|
|
49
47
|
type === matcherTypes.LESS_THAN_OR_EQUAL_TO) {
|
|
50
|
-
value = numericTransform(
|
|
48
|
+
value = numericTransform(unaryNumericMatcherData as IUnaryNumericMatcherData);
|
|
51
49
|
dataType = matcherDataTypes.NUMBER;
|
|
52
50
|
|
|
53
|
-
if ((
|
|
51
|
+
if ((unaryNumericMatcherData as IUnaryNumericMatcherData).dataType === 'DATETIME') {
|
|
54
52
|
value = zeroSinceSS(value);
|
|
55
53
|
dataType = matcherDataTypes.DATETIME;
|
|
56
54
|
}
|
|
57
55
|
} else if (type === matcherTypes.BETWEEN) {
|
|
58
|
-
value =
|
|
56
|
+
value = betweenMatcherData as IBetweenMatcherData;
|
|
59
57
|
dataType = matcherDataTypes.NUMBER;
|
|
60
58
|
|
|
61
59
|
if (value.dataType === 'DATETIME') {
|
|
@@ -63,27 +61,36 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
|
|
|
63
61
|
value.end = zeroSinceSS(value.end);
|
|
64
62
|
dataType = matcherDataTypes.DATETIME;
|
|
65
63
|
}
|
|
64
|
+
} else if (type === matcherTypes.BETWEEN_SEMVER) {
|
|
65
|
+
value = betweenStringMatcherData;
|
|
66
66
|
} else if (
|
|
67
67
|
type === matcherTypes.EQUAL_TO_SET ||
|
|
68
68
|
type === matcherTypes.CONTAINS_ANY_OF_SET ||
|
|
69
69
|
type === matcherTypes.CONTAINS_ALL_OF_SET ||
|
|
70
70
|
type === matcherTypes.PART_OF_SET
|
|
71
71
|
) {
|
|
72
|
-
value =
|
|
72
|
+
value = whitelistTransform(whitelistMatcherData);
|
|
73
73
|
dataType = matcherDataTypes.SET;
|
|
74
74
|
} else if (
|
|
75
|
+
type === matcherTypes.WHITELIST ||
|
|
76
|
+
type === matcherTypes.IN_LIST_SEMVER ||
|
|
75
77
|
type === matcherTypes.STARTS_WITH ||
|
|
76
78
|
type === matcherTypes.ENDS_WITH ||
|
|
77
79
|
type === matcherTypes.CONTAINS_STRING
|
|
78
80
|
) {
|
|
79
|
-
value =
|
|
81
|
+
value = whitelistTransform(whitelistMatcherData);
|
|
80
82
|
} else if (type === matcherTypes.IN_SPLIT_TREATMENT) {
|
|
81
|
-
value =
|
|
83
|
+
value = dependencyMatcherData;
|
|
82
84
|
dataType = matcherDataTypes.NOT_SPECIFIED;
|
|
83
85
|
} else if (type === matcherTypes.EQUAL_TO_BOOLEAN) {
|
|
84
86
|
dataType = matcherDataTypes.BOOLEAN;
|
|
85
87
|
value = booleanMatcherData;
|
|
86
|
-
} else if (
|
|
88
|
+
} else if (
|
|
89
|
+
type === matcherTypes.MATCHES_STRING ||
|
|
90
|
+
type === matcherTypes.EQUAL_TO_SEMVER ||
|
|
91
|
+
type === matcherTypes.GREATER_THAN_OR_EQUAL_TO_SEMVER ||
|
|
92
|
+
type === matcherTypes.LESS_THAN_OR_EQUAL_TO_SEMVER
|
|
93
|
+
) {
|
|
87
94
|
value = stringMatcherData;
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -91,6 +98,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
|
|
|
91
98
|
attribute, // attribute over we should do the matching, undefined means 'use the key'
|
|
92
99
|
negate, // should we negate the result?
|
|
93
100
|
type, // which kind of matcher we should evaluate
|
|
101
|
+
name: matcherType,// name of the matcher for logging purposes
|
|
94
102
|
value, // metadata used for the matching
|
|
95
103
|
dataType // runtime input data type
|
|
96
104
|
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _Set } from '../../utils/lang/sets';
|
|
1
|
+
import { ISplitMatcher } from '../../dtos/types';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
|
-
* Extract whitelist
|
|
4
|
+
* Extract whitelist array.
|
|
6
5
|
*/
|
|
7
|
-
export function whitelistTransform(whitelistObject:
|
|
8
|
-
return
|
|
6
|
+
export function whitelistTransform(whitelistObject: ISplitMatcher['whitelistMatcherData']) {
|
|
7
|
+
return whitelistObject && whitelistObject.whitelist;
|
|
9
8
|
}
|
|
@@ -7,10 +7,11 @@ import { ifElseIfCombinerContext } from '../combiners/ifelseif';
|
|
|
7
7
|
import { andCombinerContext } from '../combiners/and';
|
|
8
8
|
import { thenable } from '../../utils/promise/thenable';
|
|
9
9
|
import { IEvaluator, IMatcherDto, ISplitEvaluator } from '../types';
|
|
10
|
-
import { ISplitCondition } from '../../dtos/types';
|
|
10
|
+
import { ISplitCondition, MaybeThenable } from '../../dtos/types';
|
|
11
11
|
import { IStorageAsync, IStorageSync } from '../../storages/types';
|
|
12
12
|
import { SplitIO } from '../../types';
|
|
13
13
|
import { ILogger } from '../../logger/types';
|
|
14
|
+
import { ENGINE_MATCHER_ERROR } from '../../logger/constants';
|
|
14
15
|
|
|
15
16
|
export function parser(log: ILogger, conditions: ISplitCondition[], storage: IStorageSync | IStorageAsync): IEvaluator {
|
|
16
17
|
let predicates = [];
|
|
@@ -28,18 +29,29 @@ export function parser(log: ILogger, conditions: ISplitCondition[], storage: ISt
|
|
|
28
29
|
|
|
29
30
|
// create a set of pure functions from the matcher's dto
|
|
30
31
|
const expressions = matchers.map((matcherDto: IMatcherDto) => {
|
|
31
|
-
|
|
32
|
+
let matcher: ReturnType<typeof matcherFactory>;
|
|
33
|
+
try {
|
|
34
|
+
matcher = matcherFactory(log, matcherDto, storage);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
log.error(ENGINE_MATCHER_ERROR, [matcherDto.name, error]);
|
|
37
|
+
}
|
|
32
38
|
|
|
33
39
|
// Evaluator function.
|
|
34
40
|
return (key: string, attributes: SplitIO.Attributes | undefined, splitEvaluator: ISplitEvaluator) => {
|
|
35
41
|
const value = sanitizeValue(log, key, matcherDto, attributes);
|
|
36
|
-
|
|
42
|
+
let result: MaybeThenable<boolean> = false;
|
|
37
43
|
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
if (value !== undefined && matcher) {
|
|
45
|
+
try {
|
|
46
|
+
result = matcher(value, splitEvaluator);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
log.error(ENGINE_MATCHER_ERROR, [matcherDto.name, error]);
|
|
49
|
+
}
|
|
41
50
|
}
|
|
42
|
-
|
|
51
|
+
|
|
52
|
+
if (thenable(result)) { // @ts-ignore
|
|
53
|
+
return result.then(res => Boolean(res ^ matcherDto.negate));
|
|
54
|
+
} // @ts-ignore
|
|
43
55
|
return Boolean(result ^ matcherDto.negate);
|
|
44
56
|
};
|
|
45
57
|
});
|
package/src/evaluator/types.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { IBetweenMatcherData, IDependencyMatcherData, MaybeThenable } from '../dtos/types';
|
|
1
|
+
import { IBetweenMatcherData, IBetweenStringMatcherData, IDependencyMatcherData, MaybeThenable } from '../dtos/types';
|
|
2
2
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
3
|
-
import { ISet } from '../utils/lang/sets';
|
|
4
3
|
import { SplitIO } from '../types';
|
|
5
4
|
import { ILogger } from '../logger/types';
|
|
6
5
|
|
|
@@ -11,7 +10,8 @@ export interface IDependencyMatcherValue {
|
|
|
11
10
|
|
|
12
11
|
export interface IMatcherDto {
|
|
13
12
|
type: number
|
|
14
|
-
|
|
13
|
+
name: string
|
|
14
|
+
value?: string | number | boolean | string[] | IDependencyMatcherData | IBetweenMatcherData | IBetweenStringMatcherData | null
|
|
15
15
|
|
|
16
16
|
attribute: string | null
|
|
17
17
|
negate: boolean
|
package/src/logger/constants.ts
CHANGED
|
@@ -25,7 +25,6 @@ export const ENGINE_MATCHER_LESS = 16;
|
|
|
25
25
|
export const ENGINE_MATCHER_PART_OF = 17;
|
|
26
26
|
export const ENGINE_MATCHER_SEGMENT = 18;
|
|
27
27
|
export const ENGINE_MATCHER_STRING = 19;
|
|
28
|
-
export const ENGINE_MATCHER_STRING_INVALID = 20;
|
|
29
28
|
export const ENGINE_MATCHER_STARTS_WITH = 21;
|
|
30
29
|
export const ENGINE_MATCHER_WHITELIST = 22;
|
|
31
30
|
export const ENGINE_VALUE = 23;
|
|
@@ -131,6 +130,7 @@ export const ERROR_NOT_BOOLEAN = 325;
|
|
|
131
130
|
export const ERROR_MIN_CONFIG_PARAM = 326;
|
|
132
131
|
export const ERROR_TOO_MANY_SETS = 327;
|
|
133
132
|
export const ERROR_SETS_FILTER_EXCLUSIVE = 328;
|
|
133
|
+
export const ENGINE_MATCHER_ERROR = 329;
|
|
134
134
|
|
|
135
135
|
// Log prefixes (a.k.a. tags or categories)
|
|
136
136
|
export const LOG_PREFIX_SETTINGS = 'settings';
|
package/src/logger/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { ILoggerOptions, ILogger } from './types';
|
|
3
|
-
import { find } from '../utils/lang';
|
|
3
|
+
import { find, isObject } from '../utils/lang';
|
|
4
4
|
import { LogLevel } from '../types';
|
|
5
5
|
import { IMap, _Map } from '../utils/lang/maps';
|
|
6
6
|
|
|
@@ -28,7 +28,13 @@ export function isLogLevelString(str: string): str is LogLevel {
|
|
|
28
28
|
export function _sprintf(format: string = '', args: any[] = []): string {
|
|
29
29
|
let i = 0;
|
|
30
30
|
return format.replace(/%s/g, function () {
|
|
31
|
-
|
|
31
|
+
let arg = args[i++];
|
|
32
|
+
if (isObject(arg) || Array.isArray(arg)) {
|
|
33
|
+
try {
|
|
34
|
+
arg = JSON.stringify(arg);
|
|
35
|
+
} catch (e) { /* empty */ }
|
|
36
|
+
}
|
|
37
|
+
return arg;
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -23,7 +23,6 @@ export const codesDebug: [number, string][] = codesInfo.concat([
|
|
|
23
23
|
[c.ENGINE_MATCHER_PART_OF, c.LOG_PREFIX_ENGINE_MATCHER + '[partOfMatcher] %s is part of %s? %s'],
|
|
24
24
|
[c.ENGINE_MATCHER_SEGMENT, c.LOG_PREFIX_ENGINE_MATCHER + '[segmentMatcher] evaluated %s / %s => %s'],
|
|
25
25
|
[c.ENGINE_MATCHER_STRING, c.LOG_PREFIX_ENGINE_MATCHER + '[stringMatcher] does %s matches with %s? %s'],
|
|
26
|
-
[c.ENGINE_MATCHER_STRING_INVALID, c.LOG_PREFIX_ENGINE_MATCHER + '[stringMatcher] %s is an invalid regex'],
|
|
27
26
|
[c.ENGINE_MATCHER_STARTS_WITH, c.LOG_PREFIX_ENGINE_MATCHER + '[startsWithMatcher] %s starts with %s? %s'],
|
|
28
27
|
[c.ENGINE_MATCHER_WHITELIST, c.LOG_PREFIX_ENGINE_MATCHER + '[whitelistMatcher] evaluated %s in [%s] => %s'],
|
|
29
28
|
[c.ENGINE_VALUE, c.LOG_PREFIX_ENGINE_VALUE + 'Extracted attribute [%s], [%s] will be used for matching.'],
|
|
@@ -2,7 +2,8 @@ import * as c from '../constants';
|
|
|
2
2
|
|
|
3
3
|
export const codesError: [number, string][] = [
|
|
4
4
|
// evaluator
|
|
5
|
-
[c.ERROR_ENGINE_COMBINER_IFELSEIF, c.LOG_PREFIX_ENGINE_COMBINER + 'Invalid feature flag, no valid rules or unsupported
|
|
5
|
+
[c.ERROR_ENGINE_COMBINER_IFELSEIF, c.LOG_PREFIX_ENGINE_COMBINER + 'Invalid feature flag, no valid rules or unsupported targeting rule type found'],
|
|
6
|
+
[c.ENGINE_MATCHER_ERROR, c.LOG_PREFIX_ENGINE_MATCHER + '[%s] %s'],
|
|
6
7
|
// SDK
|
|
7
8
|
[c.ERROR_LOGLEVEL_INVALID, 'logger: Invalid Log Level - No changes to the logs will be applied.'],
|
|
8
9
|
[c.ERROR_CLIENT_CANNOT_GET_READY, 'The SDK will not get ready. Reason: %s'],
|
package/src/services/splitApi.ts
CHANGED
|
@@ -44,17 +44,16 @@ export function splitApiFactory(
|
|
|
44
44
|
},
|
|
45
45
|
|
|
46
46
|
fetchAuth(userMatchingKeys?: string[]) {
|
|
47
|
-
let url = `${urls.auth}/v2/auth`;
|
|
48
|
-
if (userMatchingKeys) { //
|
|
47
|
+
let url = `${urls.auth}/v2/auth?s=${FLAGS_SPEC}`;
|
|
48
|
+
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
49
49
|
const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
50
|
-
if (queryParams)
|
|
51
|
-
url += '?' + queryParams;
|
|
50
|
+
if (queryParams) url += '&' + queryParams;
|
|
52
51
|
}
|
|
53
52
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
|
|
54
53
|
},
|
|
55
54
|
|
|
56
55
|
fetchSplitChanges(since: number, noCache?: boolean, till?: number) {
|
|
57
|
-
const url = `${urls.sdk}/splitChanges?s=${FLAGS_SPEC}&since=${since}${till ? '&till=' + till : ''}
|
|
56
|
+
const url = `${urls.sdk}/splitChanges?s=${FLAGS_SPEC}&since=${since}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
|
|
58
57
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
|
|
59
58
|
.catch((err) => {
|
|
60
59
|
if (err.statusCode === 414) settings.log.error(ERROR_TOO_MANY_SETS);
|
|
@@ -146,7 +146,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
146
146
|
|
|
147
147
|
// when using a new split query, we must update it at the store
|
|
148
148
|
if (this.updateNewFilter) {
|
|
149
|
-
this.log.info(LOG_PREFIX + 'SDK key or
|
|
149
|
+
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
|
|
150
150
|
const storageHashKey = this.keys.buildHashKey();
|
|
151
151
|
try {
|
|
152
152
|
localStorage.setItem(storageHashKey, this.storageHash);
|
|
@@ -96,7 +96,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
96
96
|
return wrapper.get(keys.buildHashKey()).then((hash) => {
|
|
97
97
|
const currentHash = getStorageHash(settings);
|
|
98
98
|
if (hash !== currentHash) {
|
|
99
|
-
log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key or
|
|
99
|
+
log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
|
|
100
100
|
return wrapper.getKeysByPrefix(`${keys.prefix}.`).then(storageKeys => {
|
|
101
101
|
return Promise.all(storageKeys.map(storageKey => wrapper.del(storageKey)));
|
|
102
102
|
}).then(() => wrapper.set(keys.buildHashKey(), currentHash));
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isString } from '../utils/lang';
|
|
2
|
+
|
|
3
|
+
const NUMERIC_IDENTIFIER_REGEX = /^[0-9]+$/;
|
|
4
|
+
|
|
5
|
+
const METADATA_DELIMITER = '+';
|
|
6
|
+
const PRERELEASE_DELIMITER = '-';
|
|
7
|
+
const VALUE_DELIMITER = '.';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Compares two strings. If both strings are numeric identifiers, they are compared numerically. Otherwise, they are compared lexicographically.
|
|
11
|
+
* This could be implemented using `a.localeCompare(b, undefined, { numeric: true })` but locale options are not broadly supported.
|
|
12
|
+
*/
|
|
13
|
+
function compareStrings(a: string, b: string): number {
|
|
14
|
+
if (NUMERIC_IDENTIFIER_REGEX.test(a) && NUMERIC_IDENTIFIER_REGEX.test(b)) {
|
|
15
|
+
const result = a.length - b.length;
|
|
16
|
+
if (result !== 0) {
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Sanitizes a numeric identifier by removing leading zeros
|
|
24
|
+
function sanitizeNumericIdentifier(value: string): string {
|
|
25
|
+
return value.replace(/^0+(?=\d)/, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function throwError(version: string) {
|
|
29
|
+
throw new Error('Unable to convert to Semver, incorrect format: ' + version);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class Semver {
|
|
33
|
+
|
|
34
|
+
private readonly _major: string;
|
|
35
|
+
private readonly _minor: string;
|
|
36
|
+
private readonly _patch: string;
|
|
37
|
+
private readonly _preRelease: string[];
|
|
38
|
+
private readonly _isStable: boolean;
|
|
39
|
+
|
|
40
|
+
// Version string for 'equal' and 'in list' comparisons
|
|
41
|
+
public readonly version: string;
|
|
42
|
+
|
|
43
|
+
public constructor(version: string) {
|
|
44
|
+
if (!isString(version)) throwError(version);
|
|
45
|
+
|
|
46
|
+
// Separate metadata if exists
|
|
47
|
+
let index = version.indexOf(METADATA_DELIMITER);
|
|
48
|
+
let [vWithoutMetadata, metadata] = index === -1 ? [version] : [version.slice(0, index), version.slice(index + 1)];
|
|
49
|
+
if (metadata === '') throwError(version);
|
|
50
|
+
|
|
51
|
+
// Set pre-release versions if exists
|
|
52
|
+
index = vWithoutMetadata.indexOf(PRERELEASE_DELIMITER);
|
|
53
|
+
if (index === -1) {
|
|
54
|
+
this._isStable = true;
|
|
55
|
+
this._preRelease = [];
|
|
56
|
+
} else {
|
|
57
|
+
this._isStable = false;
|
|
58
|
+
this._preRelease = vWithoutMetadata.slice(index + 1).split(VALUE_DELIMITER).map((value) => {
|
|
59
|
+
if (!value) throwError(version);
|
|
60
|
+
return NUMERIC_IDENTIFIER_REGEX.test(value) ?
|
|
61
|
+
sanitizeNumericIdentifier(value) :
|
|
62
|
+
value;
|
|
63
|
+
});
|
|
64
|
+
vWithoutMetadata = vWithoutMetadata.slice(0, index);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set major, minor, and patch versions
|
|
68
|
+
const vParts = vWithoutMetadata.split(VALUE_DELIMITER).map((value) => {
|
|
69
|
+
if (!value || !NUMERIC_IDENTIFIER_REGEX.test(value)) throwError(version);
|
|
70
|
+
return sanitizeNumericIdentifier(value);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (vParts.length !== 3) throwError(version);
|
|
74
|
+
this._major = vParts[0];
|
|
75
|
+
this._minor = vParts[1];
|
|
76
|
+
this._patch = vParts[2];
|
|
77
|
+
|
|
78
|
+
// Set version string
|
|
79
|
+
this.version = vParts.join(VALUE_DELIMITER);
|
|
80
|
+
if (this._preRelease.length) this.version += PRERELEASE_DELIMITER + this._preRelease.join(VALUE_DELIMITER);
|
|
81
|
+
if (metadata) this.version += METADATA_DELIMITER + metadata;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Precedence comparision between 2 Semver objects.
|
|
86
|
+
*
|
|
87
|
+
* @return `0` if `this === toCompare`, `-1` if `this < toCompare`, and `1` if `this > toCompare`
|
|
88
|
+
*/
|
|
89
|
+
public compare(toCompare: Semver): number {
|
|
90
|
+
if (this.version === toCompare.version) return 0;
|
|
91
|
+
|
|
92
|
+
let result = compareStrings(this._major, toCompare._major);
|
|
93
|
+
if (result !== 0) return result;
|
|
94
|
+
|
|
95
|
+
result = compareStrings(this._minor, toCompare._minor);
|
|
96
|
+
if (result !== 0) return result;
|
|
97
|
+
|
|
98
|
+
result = compareStrings(this._patch, toCompare._patch);
|
|
99
|
+
if (result !== 0) return result;
|
|
100
|
+
|
|
101
|
+
if (!this._isStable && toCompare._isStable) return -1;
|
|
102
|
+
if (this._isStable && !toCompare._isStable) return 1;
|
|
103
|
+
|
|
104
|
+
for (let i = 0, length = Math.min(this._preRelease.length, toCompare._preRelease.length); i < length; i++) {
|
|
105
|
+
const result = compareStrings(this._preRelease[i], toCompare._preRelease[i]);
|
|
106
|
+
if (result !== 0) return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return this._preRelease.length - toCompare._preRelease.length;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -5,4 +5,4 @@ export const SDK_NOT_READY = 'not ready';
|
|
|
5
5
|
export const EXCEPTION = 'exception';
|
|
6
6
|
export const SPLIT_ARCHIVED = 'archived';
|
|
7
7
|
export const NOT_IN_SPLIT = 'not in split';
|
|
8
|
-
export const UNSUPPORTED_MATCHER_TYPE = 'unsupported
|
|
8
|
+
export const UNSUPPORTED_MATCHER_TYPE = 'targeting rule type unsupported by sdk';
|
|
@@ -4,7 +4,7 @@ import { LogLevel } from '../../../types';
|
|
|
4
4
|
import { getLogLevel } from './commons';
|
|
5
5
|
|
|
6
6
|
function isLogger(log: any): log is ILogger {
|
|
7
|
-
return log && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function' && typeof log.setLogLevel === 'function';
|
|
7
|
+
return log !== null && typeof log === 'object' && typeof log.debug === 'function' && typeof log.info === 'function' && typeof log.warn === 'function' && typeof log.error === 'function' && typeof log.setLogLevel === 'function';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
// By default it starts disabled.
|
package/types/dtos/types.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface IBetweenMatcherData {
|
|
|
11
11
|
start: number;
|
|
12
12
|
end: number;
|
|
13
13
|
}
|
|
14
|
+
export interface IBetweenStringMatcherData {
|
|
15
|
+
start: string;
|
|
16
|
+
end: string;
|
|
17
|
+
}
|
|
14
18
|
export interface IWhitelistMatcherData {
|
|
15
19
|
whitelist: string[];
|
|
16
20
|
}
|
|
@@ -35,6 +39,7 @@ interface ISplitMatcherBase {
|
|
|
35
39
|
dependencyMatcherData?: null | IDependencyMatcherData;
|
|
36
40
|
booleanMatcherData?: null | boolean;
|
|
37
41
|
stringMatcherData?: null | string;
|
|
42
|
+
betweenStringMatcherData?: null | IBetweenStringMatcherData;
|
|
38
43
|
}
|
|
39
44
|
interface IAllKeysMatcher extends ISplitMatcherBase {
|
|
40
45
|
matcherType: 'ALL_KEYS';
|
|
@@ -103,7 +108,27 @@ interface IMatchesStringMatcher extends ISplitMatcherBase {
|
|
|
103
108
|
matcherType: 'MATCHES_STRING';
|
|
104
109
|
stringMatcherData: string;
|
|
105
110
|
}
|
|
106
|
-
|
|
111
|
+
interface IEqualToSemverMatcher extends ISplitMatcherBase {
|
|
112
|
+
matcherType: 'EQUAL_TO_SEMVER';
|
|
113
|
+
stringMatcherData: string;
|
|
114
|
+
}
|
|
115
|
+
interface IGreaterThanOrEqualToSemverMatcher extends ISplitMatcherBase {
|
|
116
|
+
matcherType: 'GREATER_THAN_OR_EQUAL_TO_SEMVER';
|
|
117
|
+
stringMatcherData: string;
|
|
118
|
+
}
|
|
119
|
+
interface ILessThanOrEqualToSemverMatcher extends ISplitMatcherBase {
|
|
120
|
+
matcherType: 'LESS_THAN_OR_EQUAL_TO_SEMVER';
|
|
121
|
+
stringMatcherData: string;
|
|
122
|
+
}
|
|
123
|
+
interface IBetweenSemverMatcher extends ISplitMatcherBase {
|
|
124
|
+
matcherType: 'BETWEEN_SEMVER';
|
|
125
|
+
betweenStringMatcherData: IBetweenStringMatcherData;
|
|
126
|
+
}
|
|
127
|
+
interface IInListSemverMatcher extends ISplitMatcherBase {
|
|
128
|
+
matcherType: 'IN_LIST_SEMVER';
|
|
129
|
+
whitelistMatcherData: IWhitelistMatcherData;
|
|
130
|
+
}
|
|
131
|
+
export declare type ISplitMatcher = IAllKeysMatcher | IInSegmentMatcher | IWhitelistMatcher | IEqualToMatcher | IGreaterThanOrEqualToMatcher | ILessThanOrEqualToMatcher | IBetweenMatcher | IEqualToSetMatcher | IContainsAnyOfSetMatcher | IContainsAllOfSetMatcher | IPartOfSetMatcher | IStartsWithMatcher | IEndsWithMatcher | IContainsStringMatcher | IInSplitTreatmentMatcher | IEqualToBooleanMatcher | IMatchesStringMatcher | IEqualToSemverMatcher | IGreaterThanOrEqualToSemverMatcher | ILessThanOrEqualToSemverMatcher | IBetweenSemverMatcher | IInListSemverMatcher;
|
|
107
132
|
/** Split object */
|
|
108
133
|
export interface ISplitPartition {
|
|
109
134
|
treatment: string;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import { ISet } from '../../utils/lang/sets';
|
|
2
1
|
import { ILogger } from '../../logger/types';
|
|
3
|
-
export declare function whitelistMatcherContext(log: ILogger, ruleAttr:
|
|
2
|
+
export declare function whitelistMatcherContext(log: ILogger, ruleAttr: string[]): (runtimeAttr: string) => boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ISplitMatcher } from '../../dtos/types';
|
|
2
2
|
/**
|
|
3
3
|
* Extract whitelist array. Used by set and string matchers
|
|
4
4
|
*/
|
|
5
|
-
export declare function setTransform(whitelistObject:
|
|
5
|
+
export declare function setTransform(whitelistObject: ISplitMatcher['whitelistMatcherData']): string[] | null | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ISplitMatcher } from '../../dtos/types';
|
|
2
2
|
/**
|
|
3
|
-
* Extract whitelist
|
|
3
|
+
* Extract whitelist array.
|
|
4
4
|
*/
|
|
5
|
-
export declare function whitelistTransform(whitelistObject:
|
|
5
|
+
export declare function whitelistTransform(whitelistObject: ISplitMatcher['whitelistMatcherData']): string[] | null | undefined;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { IBetweenMatcherData, IDependencyMatcherData, MaybeThenable } from '../dtos/types';
|
|
1
|
+
import { IBetweenMatcherData, IBetweenStringMatcherData, IDependencyMatcherData, MaybeThenable } from '../dtos/types';
|
|
2
2
|
import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
3
|
-
import { ISet } from '../utils/lang/sets';
|
|
4
3
|
import { SplitIO } from '../types';
|
|
5
4
|
import { ILogger } from '../logger/types';
|
|
6
5
|
export interface IDependencyMatcherValue {
|
|
@@ -9,7 +8,8 @@ export interface IDependencyMatcherValue {
|
|
|
9
8
|
}
|
|
10
9
|
export interface IMatcherDto {
|
|
11
10
|
type: number;
|
|
12
|
-
|
|
11
|
+
name: string;
|
|
12
|
+
value?: string | number | boolean | string[] | IDependencyMatcherData | IBetweenMatcherData | IBetweenStringMatcherData | null;
|
|
13
13
|
attribute: string | null;
|
|
14
14
|
negate: boolean;
|
|
15
15
|
dataType: string;
|
|
@@ -25,7 +25,6 @@ export declare const ENGINE_MATCHER_LESS = 16;
|
|
|
25
25
|
export declare const ENGINE_MATCHER_PART_OF = 17;
|
|
26
26
|
export declare const ENGINE_MATCHER_SEGMENT = 18;
|
|
27
27
|
export declare const ENGINE_MATCHER_STRING = 19;
|
|
28
|
-
export declare const ENGINE_MATCHER_STRING_INVALID = 20;
|
|
29
28
|
export declare const ENGINE_MATCHER_STARTS_WITH = 21;
|
|
30
29
|
export declare const ENGINE_MATCHER_WHITELIST = 22;
|
|
31
30
|
export declare const ENGINE_VALUE = 23;
|
|
@@ -128,6 +127,7 @@ export declare const ERROR_NOT_BOOLEAN = 325;
|
|
|
128
127
|
export declare const ERROR_MIN_CONFIG_PARAM = 326;
|
|
129
128
|
export declare const ERROR_TOO_MANY_SETS = 327;
|
|
130
129
|
export declare const ERROR_SETS_FILTER_EXCLUSIVE = 328;
|
|
130
|
+
export declare const ENGINE_MATCHER_ERROR = 329;
|
|
131
131
|
export declare const LOG_PREFIX_SETTINGS = "settings";
|
|
132
132
|
export declare const LOG_PREFIX_INSTANTIATION = "Factory instantiation";
|
|
133
133
|
export declare const LOG_PREFIX_ENGINE = "engine";
|
|
@@ -5,4 +5,4 @@ export declare const SDK_NOT_READY = "not ready";
|
|
|
5
5
|
export declare const EXCEPTION = "exception";
|
|
6
6
|
export declare const SPLIT_ARCHIVED = "archived";
|
|
7
7
|
export declare const NOT_IN_SPLIT = "not in split";
|
|
8
|
-
export declare const UNSUPPORTED_MATCHER_TYPE = "unsupported
|
|
8
|
+
export declare const UNSUPPORTED_MATCHER_TYPE = "targeting rule type unsupported by sdk";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class Semver {
|
|
2
|
+
private readonly _major;
|
|
3
|
+
private readonly _minor;
|
|
4
|
+
private readonly _patch;
|
|
5
|
+
private readonly _preRelease;
|
|
6
|
+
private readonly _isStable;
|
|
7
|
+
readonly version: string;
|
|
8
|
+
constructor(version: string);
|
|
9
|
+
/**
|
|
10
|
+
* Precedence comparision between 2 Semver objects.
|
|
11
|
+
*
|
|
12
|
+
* @return `0` if `this === toCompare`, `-1` if `this < toCompare`, and `1` if `this > toCompare`
|
|
13
|
+
*/
|
|
14
|
+
compare(toCompare: Semver): number;
|
|
15
|
+
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setTransform = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Extract whitelist array. Used by set and string matchers
|
|
6
|
-
*/
|
|
7
|
-
function setTransform(whitelistObject) {
|
|
8
|
-
return whitelistObject.whitelist;
|
|
9
|
-
}
|
|
10
|
-
exports.setTransform = setTransform;
|