@splitsoftware/splitio-commons 1.13.2-rc.2 → 1.13.2-rc.4

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.
Files changed (82) hide show
  1. package/CHANGES.txt +0 -1
  2. package/cjs/evaluator/matchers/gte.js +3 -3
  3. package/cjs/evaluator/matchers/index.js +11 -1
  4. package/cjs/evaluator/matchers/matcherTypes.js +6 -1
  5. package/cjs/evaluator/matchers/semver_between.js +14 -0
  6. package/cjs/evaluator/matchers/semver_eq.js +13 -0
  7. package/cjs/evaluator/matchers/semver_gte.js +13 -0
  8. package/cjs/evaluator/matchers/semver_inlist.js +14 -0
  9. package/cjs/evaluator/matchers/semver_lte.js +13 -0
  10. package/cjs/evaluator/matchers/string.js +2 -9
  11. package/cjs/evaluator/matchersTransform/index.js +20 -13
  12. package/cjs/evaluator/parser/index.js +27 -10
  13. package/cjs/logger/constants.js +4 -4
  14. package/cjs/logger/messages/debug.js +0 -1
  15. package/cjs/logger/messages/error.js +1 -0
  16. package/cjs/services/splitApi.js +5 -5
  17. package/cjs/storages/KeyBuilder.js +2 -3
  18. package/cjs/utils/Semver.js +103 -0
  19. package/cjs/utils/constants/index.js +1 -2
  20. package/cjs/utils/settingsValidation/logger/pluggableLogger.js +1 -1
  21. package/esm/evaluator/matchers/gte.js +3 -3
  22. package/esm/evaluator/matchers/index.js +11 -1
  23. package/esm/evaluator/matchers/matcherTypes.js +6 -1
  24. package/esm/evaluator/matchers/semver_between.js +10 -0
  25. package/esm/evaluator/matchers/semver_eq.js +9 -0
  26. package/esm/evaluator/matchers/semver_gte.js +9 -0
  27. package/esm/evaluator/matchers/semver_inlist.js +10 -0
  28. package/esm/evaluator/matchers/semver_lte.js +9 -0
  29. package/esm/evaluator/matchers/string.js +3 -10
  30. package/esm/evaluator/matchersTransform/index.js +20 -13
  31. package/esm/evaluator/parser/index.js +27 -10
  32. package/esm/logger/constants.js +1 -1
  33. package/esm/logger/messages/debug.js +0 -1
  34. package/esm/logger/messages/error.js +1 -0
  35. package/esm/services/splitApi.js +6 -6
  36. package/esm/storages/KeyBuilder.js +2 -3
  37. package/esm/utils/Semver.js +100 -0
  38. package/esm/utils/constants/index.js +0 -1
  39. package/esm/utils/settingsValidation/logger/pluggableLogger.js +1 -1
  40. package/package.json +1 -2
  41. package/src/dtos/types.ts +34 -1
  42. package/src/evaluator/matchers/between.ts +1 -1
  43. package/src/evaluator/matchers/boolean.ts +1 -1
  44. package/src/evaluator/matchers/cont_all.ts +1 -1
  45. package/src/evaluator/matchers/cont_any.ts +1 -1
  46. package/src/evaluator/matchers/cont_str.ts +1 -1
  47. package/src/evaluator/matchers/eq.ts +1 -1
  48. package/src/evaluator/matchers/eq_set.ts +1 -1
  49. package/src/evaluator/matchers/ew.ts +1 -1
  50. package/src/evaluator/matchers/gte.ts +4 -4
  51. package/src/evaluator/matchers/index.ts +28 -18
  52. package/src/evaluator/matchers/lte.ts +1 -1
  53. package/src/evaluator/matchers/matcherTypes.ts +6 -1
  54. package/src/evaluator/matchers/part_of.ts +1 -1
  55. package/src/evaluator/matchers/semver_between.ts +16 -0
  56. package/src/evaluator/matchers/semver_eq.ts +14 -0
  57. package/src/evaluator/matchers/semver_gte.ts +14 -0
  58. package/src/evaluator/matchers/semver_inlist.ts +15 -0
  59. package/src/evaluator/matchers/semver_lte.ts +14 -0
  60. package/src/evaluator/matchers/string.ts +5 -13
  61. package/src/evaluator/matchers/sw.ts +1 -1
  62. package/src/evaluator/matchers/whitelist.ts +1 -1
  63. package/src/evaluator/matchersTransform/index.ts +32 -21
  64. package/src/evaluator/parser/index.ts +20 -8
  65. package/src/evaluator/types.ts +2 -2
  66. package/src/logger/constants.ts +1 -1
  67. package/src/logger/messages/debug.ts +0 -1
  68. package/src/logger/messages/error.ts +1 -0
  69. package/src/services/splitApi.ts +6 -5
  70. package/src/storages/KeyBuilder.ts +2 -3
  71. package/src/utils/Semver.ts +111 -0
  72. package/src/utils/constants/index.ts +0 -2
  73. package/src/utils/settingsValidation/logger/pluggableLogger.ts +1 -1
  74. package/types/dtos/types.d.ts +26 -1
  75. package/types/evaluator/matchers/semver_between.d.ts +2 -2
  76. package/types/evaluator/matchers/semver_inlist.d.ts +3 -0
  77. package/types/evaluator/matchersTransform/string.d.ts +7 -0
  78. package/types/evaluator/types.d.ts +2 -2
  79. package/types/logger/constants.d.ts +1 -1
  80. package/types/storages/KeyBuilder.d.ts +1 -1
  81. package/types/utils/constants/index.d.ts +0 -1
  82. package/types/utils/semVer.d.ts +8 -15
@@ -1,7 +1,7 @@
1
1
  import { ENGINE_MATCHER_LESS } from '../../logger/constants';
2
2
  import { ILogger } from '../../logger/types';
3
3
 
4
- export function lessThanEqualMatcherContext(log: ILogger, ruleAttr: number) /*: function */ {
4
+ export function lessThanEqualMatcherContext(log: ILogger, ruleAttr: number) {
5
5
  return function lessThanEqualMatcher(runtimeAttr: number): boolean {
6
6
  let isLessEqualThan = runtimeAttr <= ruleAttr;
7
7
 
@@ -16,7 +16,12 @@ export const matcherTypes: Record<string, number> = {
16
16
  CONTAINS_STRING: 14,
17
17
  IN_SPLIT_TREATMENT: 15,
18
18
  EQUAL_TO_BOOLEAN: 16,
19
- MATCHES_STRING: 17
19
+ MATCHES_STRING: 17,
20
+ EQUAL_TO_SEMVER: 18,
21
+ GREATER_THAN_OR_EQUAL_TO_SEMVER: 19,
22
+ LESS_THAN_OR_EQUAL_TO_SEMVER: 20,
23
+ BETWEEN_SEMVER: 21,
24
+ IN_LIST_SEMVER: 22,
20
25
  };
21
26
 
22
27
  export const matcherDataTypes = {
@@ -2,7 +2,7 @@ import { findIndex } from '../../utils/lang';
2
2
  import { ILogger } from '../../logger/types';
3
3
  import { ENGINE_MATCHER_PART_OF } from '../../logger/constants';
4
4
 
5
- export function partOfSetMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ {
5
+ export function partOfSetMatcherContext(log: ILogger, ruleAttr: string[]) {
6
6
  return function partOfMatcher(runtimeAttr: string[]): boolean {
7
7
  // To be part of the length should be minor or equal.
8
8
  let isPartOf = runtimeAttr.length <= ruleAttr.length;
@@ -0,0 +1,16 @@
1
+ import { IBetweenStringMatcherData } from '../../dtos/types';
2
+ import { ILogger } from '../../logger/types';
3
+ import { Semver } from '../../utils/Semver';
4
+
5
+ export function betweenSemverMatcherContext(log: ILogger, ruleAttr: IBetweenStringMatcherData) {
6
+ const startSemver = new Semver(ruleAttr.start);
7
+ const endSemver = new Semver(ruleAttr.end);
8
+
9
+ return function betweenSemverMatcher(key: string): boolean {
10
+ const runtimeSemver = new Semver(key);
11
+
12
+ const isBetween = startSemver.compare(runtimeSemver) <= 0 && endSemver.compare(runtimeSemver) >= 0;
13
+
14
+ return isBetween;
15
+ };
16
+ }
@@ -0,0 +1,14 @@
1
+ import { ILogger } from '../../logger/types';
2
+ import { Semver } from '../../utils/Semver';
3
+
4
+ export function equalToSemverMatcherContext(log: ILogger, ruleAttr: string) {
5
+ const ruleSemver = new Semver(ruleAttr);
6
+
7
+ return function equalToSemverMatcher(runtimeAttr: string): boolean {
8
+ const runtimeSemver = new Semver(runtimeAttr);
9
+
10
+ const isEqual = ruleSemver.version === runtimeSemver.version;
11
+
12
+ return isEqual;
13
+ };
14
+ }
@@ -0,0 +1,14 @@
1
+ import { ILogger } from '../../logger/types';
2
+ import { Semver } from '../../utils/Semver';
3
+
4
+ export function greaterThanEqualToSemverMatcherContext(log: ILogger, ruleAttr: string) {
5
+ const ruleSemver = new Semver(ruleAttr);
6
+
7
+ return function greaterThanEqualToSemverMatcher(runtimeAttr: string): boolean {
8
+ const runtimeSemver = new Semver(runtimeAttr);
9
+
10
+ const isGreaterThanEqual = runtimeSemver.compare(ruleSemver) >= 0;
11
+
12
+ return isGreaterThanEqual;
13
+ };
14
+ }
@@ -0,0 +1,15 @@
1
+ import { setToArray, ISet, _Set } from '../../utils/lang/sets';
2
+ import { ILogger } from '../../logger/types';
3
+ import { Semver } from '../../utils/Semver';
4
+
5
+ export function inListSemverMatcherContext(log: ILogger, ruleAttr: ISet<string>) {
6
+
7
+ const listOfSemvers = new _Set(setToArray(ruleAttr).map((version) => new Semver(version).version));
8
+
9
+ return function inListSemverMatcher(runtimeAttr: string): boolean {
10
+ const runtimeSemver = new Semver(runtimeAttr).version;
11
+ const isInList = listOfSemvers.has(runtimeSemver);
12
+
13
+ return isInList;
14
+ };
15
+ }
@@ -0,0 +1,14 @@
1
+ import { ILogger } from '../../logger/types';
2
+ import { Semver } from '../../utils/Semver';
3
+
4
+ export function lessThanEqualToSemverMatcherContext(log: ILogger, ruleAttr: string) {
5
+ const ruleSemver = new Semver(ruleAttr);
6
+
7
+ return function lessThanEqualToSemverMatcher(runtimeAttr: string): boolean {
8
+ const runtimeSemver = new Semver(runtimeAttr);
9
+
10
+ const isLessThenEqual = runtimeSemver.compare(ruleSemver) <= 0;
11
+
12
+ return isLessThenEqual;
13
+ };
14
+ }
@@ -1,19 +1,11 @@
1
- import { ENGINE_MATCHER_STRING_INVALID, ENGINE_MATCHER_STRING } from '../../logger/constants';
1
+ import { ENGINE_MATCHER_STRING } from '../../logger/constants';
2
2
  import { ILogger } from '../../logger/types';
3
3
 
4
- export function stringMatcherContext(log: ILogger, ruleAttr: string) /*: Function */ {
5
- return function stringMatcher(runtimeAttr: string): boolean {
6
- let re;
7
-
8
- try {
9
- re = new RegExp(ruleAttr);
10
- } catch (e) {
11
- log.debug(ENGINE_MATCHER_STRING_INVALID, [ruleAttr]);
4
+ export function stringMatcherContext(log: ILogger, ruleAttr: string) {
5
+ const regex = new RegExp(ruleAttr);
12
6
 
13
- return false;
14
- }
15
-
16
- let regexMatches = re.test(runtimeAttr);
7
+ return function stringMatcher(runtimeAttr: string): boolean {
8
+ const regexMatches = regex.test(runtimeAttr);
17
9
 
18
10
  log.debug(ENGINE_MATCHER_STRING, [runtimeAttr, ruleAttr, regexMatches ? 'yes' : 'no']);
19
11
 
@@ -2,7 +2,7 @@ import { ENGINE_MATCHER_STARTS_WITH } from '../../logger/constants';
2
2
  import { ILogger } from '../../logger/types';
3
3
  import { startsWith } from '../../utils/lang';
4
4
 
5
- export function startsWithMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ {
5
+ export function startsWithMatcherContext(log: ILogger, ruleAttr: string[]) {
6
6
  return function startsWithMatcher(runtimeAttr: string): boolean {
7
7
  let matches = ruleAttr.some(e => startsWith(runtimeAttr, e));
8
8
 
@@ -2,7 +2,7 @@ import { setToArray, ISet } 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: ISet<string>) /*: Function */ {
5
+ export function whitelistMatcherContext(log: ILogger, ruleAttr: ISet<string>) {
6
6
  return function whitelistMatcher(runtimeAttr: string): boolean {
7
7
  let isInWhitelist = ruleAttr.has(runtimeAttr);
8
8
 
@@ -15,16 +15,17 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
15
15
 
16
16
  let parsedMatchers = matchers.map(matcher => {
17
17
  let {
18
- matcherType /* string */,
19
- negate /* boolean */,
20
- keySelector /* keySelectorObject */,
21
- userDefinedSegmentMatcherData: segmentObject /* segmentObject */,
22
- whitelistMatcherData: whitelistObject /* whiteListObject, provided by 'WHITELIST', set and string matchers */,
23
- unaryNumericMatcherData: unaryNumericObject /* unaryNumericObject */,
24
- betweenMatcherData: betweenObject /* betweenObject */,
25
- dependencyMatcherData: dependencyObject /* dependencyObject */,
18
+ matcherType,
19
+ negate,
20
+ keySelector,
21
+ userDefinedSegmentMatcherData,
22
+ whitelistMatcherData, /* whitelistObject, provided by 'WHITELIST', 'IN_LIST_SEMVER', set and string matchers */
23
+ unaryNumericMatcherData,
24
+ betweenMatcherData,
25
+ dependencyMatcherData,
26
26
  booleanMatcherData,
27
- stringMatcherData
27
+ stringMatcherData,
28
+ betweenStringMatcherData
28
29
  } = matcher;
29
30
 
30
31
  let attribute = keySelector && keySelector.attribute;
@@ -34,28 +35,31 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
34
35
  let value = undefined;
35
36
 
36
37
  if (type === matcherTypes.IN_SEGMENT) {
37
- value = segmentTransform(segmentObject as IInSegmentMatcherData);
38
- } else if (type === matcherTypes.WHITELIST) {
39
- value = whitelistTransform(whitelistObject as IWhitelistMatcherData);
38
+ value = segmentTransform(userDefinedSegmentMatcherData as IInSegmentMatcherData);
39
+ } else if (
40
+ type === matcherTypes.WHITELIST ||
41
+ type === matcherTypes.IN_LIST_SEMVER
42
+ ) {
43
+ value = whitelistTransform(whitelistMatcherData as IWhitelistMatcherData);
40
44
  } else if (type === matcherTypes.EQUAL_TO) {
41
- value = numericTransform(unaryNumericObject as IUnaryNumericMatcherData);
45
+ value = numericTransform(unaryNumericMatcherData as IUnaryNumericMatcherData);
42
46
  dataType = matcherDataTypes.NUMBER;
43
47
 
44
- if ((unaryNumericObject as IUnaryNumericMatcherData).dataType === 'DATETIME') {
48
+ if ((unaryNumericMatcherData as IUnaryNumericMatcherData).dataType === 'DATETIME') {
45
49
  value = zeroSinceHH(value);
46
50
  dataType = matcherDataTypes.DATETIME;
47
51
  }
48
52
  } else if (type === matcherTypes.GREATER_THAN_OR_EQUAL_TO ||
49
53
  type === matcherTypes.LESS_THAN_OR_EQUAL_TO) {
50
- value = numericTransform(unaryNumericObject as IUnaryNumericMatcherData);
54
+ value = numericTransform(unaryNumericMatcherData as IUnaryNumericMatcherData);
51
55
  dataType = matcherDataTypes.NUMBER;
52
56
 
53
- if ((unaryNumericObject as IUnaryNumericMatcherData).dataType === 'DATETIME') {
57
+ if ((unaryNumericMatcherData as IUnaryNumericMatcherData).dataType === 'DATETIME') {
54
58
  value = zeroSinceSS(value);
55
59
  dataType = matcherDataTypes.DATETIME;
56
60
  }
57
61
  } else if (type === matcherTypes.BETWEEN) {
58
- value = betweenObject as IBetweenMatcherData;
62
+ value = betweenMatcherData as IBetweenMatcherData;
59
63
  dataType = matcherDataTypes.NUMBER;
60
64
 
61
65
  if (value.dataType === 'DATETIME') {
@@ -63,27 +67,34 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
63
67
  value.end = zeroSinceSS(value.end);
64
68
  dataType = matcherDataTypes.DATETIME;
65
69
  }
70
+ } else if (type === matcherTypes.BETWEEN_SEMVER) {
71
+ value = betweenStringMatcherData;
66
72
  } else if (
67
73
  type === matcherTypes.EQUAL_TO_SET ||
68
74
  type === matcherTypes.CONTAINS_ANY_OF_SET ||
69
75
  type === matcherTypes.CONTAINS_ALL_OF_SET ||
70
76
  type === matcherTypes.PART_OF_SET
71
77
  ) {
72
- value = setTransform(whitelistObject as IWhitelistMatcherData);
78
+ value = setTransform(whitelistMatcherData as IWhitelistMatcherData);
73
79
  dataType = matcherDataTypes.SET;
74
80
  } else if (
75
81
  type === matcherTypes.STARTS_WITH ||
76
82
  type === matcherTypes.ENDS_WITH ||
77
83
  type === matcherTypes.CONTAINS_STRING
78
84
  ) {
79
- value = setTransform(whitelistObject as IWhitelistMatcherData);
85
+ value = setTransform(whitelistMatcherData as IWhitelistMatcherData);
80
86
  } else if (type === matcherTypes.IN_SPLIT_TREATMENT) {
81
- value = dependencyObject;
87
+ value = dependencyMatcherData;
82
88
  dataType = matcherDataTypes.NOT_SPECIFIED;
83
89
  } else if (type === matcherTypes.EQUAL_TO_BOOLEAN) {
84
90
  dataType = matcherDataTypes.BOOLEAN;
85
91
  value = booleanMatcherData;
86
- } else if (type === matcherTypes.MATCHES_STRING) {
92
+ } else if (
93
+ type === matcherTypes.MATCHES_STRING ||
94
+ type === matcherTypes.EQUAL_TO_SEMVER ||
95
+ type === matcherTypes.GREATER_THAN_OR_EQUAL_TO_SEMVER ||
96
+ type === matcherTypes.LESS_THAN_OR_EQUAL_TO_SEMVER
97
+ ) {
87
98
  value = stringMatcherData;
88
99
  }
89
100
 
@@ -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 = [];
@@ -27,19 +28,30 @@ export function parser(log: ILogger, conditions: ISplitCondition[], storage: ISt
27
28
  const matchers = matchersTransform(matcherGroup.matchers);
28
29
 
29
30
  // create a set of pure functions from the matcher's dto
30
- const expressions = matchers.map((matcherDto: IMatcherDto) => {
31
- const matcher = matcherFactory(log, matcherDto, storage);
31
+ const expressions = matchers.map((matcherDto: IMatcherDto, index: number) => {
32
+ let matcher: ReturnType<typeof matcherFactory>;
33
+ try {
34
+ matcher = matcherFactory(log, matcherDto, storage);
35
+ } catch (error) {
36
+ log.error(ENGINE_MATCHER_ERROR, [matcherGroup.matchers[index].matcherType, 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
- const result = value !== undefined && matcher ? matcher(value, splitEvaluator) : false;
42
+ let result: MaybeThenable<boolean> = false;
37
43
 
38
- if (thenable(result)) {
39
- // @ts-ignore
40
- return result.then(res => Boolean(res ^ matcherDto.negate));
44
+ if (value !== undefined && matcher) {
45
+ try {
46
+ result = matcher(value, splitEvaluator);
47
+ } catch (error) {
48
+ log.error(ENGINE_MATCHER_ERROR, [matcherGroup.matchers[index].matcherType, error]);
49
+ }
41
50
  }
42
- // @ts-ignore
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
  });
@@ -1,4 +1,4 @@
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
3
  import { ISet } from '../utils/lang/sets';
4
4
  import { SplitIO } from '../types';
@@ -11,7 +11,7 @@ export interface IDependencyMatcherValue {
11
11
 
12
12
  export interface IMatcherDto {
13
13
  type: number
14
- value?: string | number | boolean | string[] | IDependencyMatcherData | ISet<string> | IBetweenMatcherData | null
14
+ value?: string | number | boolean | string[] | IDependencyMatcherData | ISet<string> | IBetweenMatcherData | IBetweenStringMatcherData | null
15
15
 
16
16
  attribute: string | null
17
17
  negate: boolean
@@ -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';
@@ -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.'],
@@ -3,6 +3,7 @@ import * as c from '../constants';
3
3
  export const codesError: [number, string][] = [
4
4
  // evaluator
5
5
  [c.ERROR_ENGINE_COMBINER_IFELSEIF, c.LOG_PREFIX_ENGINE_COMBINER + 'Invalid feature flag, no valid rules or unsupported matcher 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'],
@@ -4,7 +4,7 @@ import { splitHttpClientFactory } from './splitHttpClient';
4
4
  import { ISplitApi } from './types';
5
5
  import { objectAssign } from '../utils/lang/objectAssign';
6
6
  import { ITelemetryTracker } from '../trackers/types';
7
- import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT, FLAGS_SPEC } from '../utils/constants';
7
+ import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT } from '../utils/constants';
8
8
  import { ERROR_TOO_MANY_SETS } from '../logger/constants';
9
9
 
10
10
  const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
@@ -44,16 +44,17 @@ export function splitApiFactory(
44
44
  },
45
45
 
46
46
  fetchAuth(userMatchingKeys?: string[]) {
47
- let url = `${urls.auth}/v2/auth?s=${FLAGS_SPEC}`;
48
- if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
47
+ let url = `${urls.auth}/v2/auth`;
48
+ if (userMatchingKeys) { // accounting the possibility that `userMatchingKeys` is undefined (server-side API)
49
49
  const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
50
- if (queryParams) url += '&' + queryParams;
50
+ if (queryParams) // accounting the possibility that `userKeys` and thus `queryParams` are empty
51
+ url += '?' + queryParams;
51
52
  }
52
53
  return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
53
54
  },
54
55
 
55
56
  fetchSplitChanges(since: number, noCache?: boolean, till?: number) {
56
- const url = `${urls.sdk}/splitChanges?s=${FLAGS_SPEC}&since=${since}${till ? '&till=' + till : ''}${filterQueryString || ''}`;
57
+ const url = `${urls.sdk}/splitChanges?since=${since}${till ? '&till=' + till : ''}${filterQueryString || ''}`;
57
58
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
58
59
  .catch((err) => {
59
60
  if (err.statusCode === 414) settings.log.error(ERROR_TOO_MANY_SETS);
@@ -1,5 +1,4 @@
1
1
  import { ISettings } from '../types';
2
- import { FLAGS_SPEC } from '../utils/constants';
3
2
  import { startsWith } from '../utils/lang';
4
3
  import { hash } from '../utils/murmur3/murmur3';
5
4
 
@@ -82,9 +81,9 @@ export class KeyBuilder {
82
81
  }
83
82
 
84
83
  /**
85
- * Generates a murmur32 hash based on the authorization key, the feature flags filter query, and version of SplitChanges API.
84
+ * Generates a murmur32 hash based on the authorization key and the feature flags filter query.
86
85
  * The hash is in hexadecimal format (8 characters max, 32 bits).
87
86
  */
88
87
  export function getStorageHash(settings: ISettings) {
89
- return hash(`${settings.core.authorizationKey}::${settings.sync.__splitFiltersValidation.queryString}::${FLAGS_SPEC}`).toString(16);
88
+ return hash(`${settings.core.authorizationKey}::${settings.sync.__splitFiltersValidation.queryString}`).toString(16);
90
89
  }
@@ -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
+ }
@@ -104,5 +104,3 @@ export const NON_REQUESTED = 1;
104
104
  export const DISABLED = 0;
105
105
  export const ENABLED = 1;
106
106
  export const PAUSED = 2;
107
-
108
- export const FLAGS_SPEC = '1.1';
@@ -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.
@@ -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
- export declare type ISplitMatcher = IAllKeysMatcher | IInSegmentMatcher | IWhitelistMatcher | IEqualToMatcher | IGreaterThanOrEqualToMatcher | ILessThanOrEqualToMatcher | IBetweenMatcher | IEqualToSetMatcher | IContainsAnyOfSetMatcher | IContainsAllOfSetMatcher | IPartOfSetMatcher | IStartsWithMatcher | IEndsWithMatcher | IContainsStringMatcher | IInSplitTreatmentMatcher | IEqualToBooleanMatcher | IMatchesStringMatcher;
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,3 @@
1
- import { IBetweenMatcherData } from '../../dtos/types';
1
+ import { IBetweenStringMatcherData } from '../../dtos/types';
2
2
  import { ILogger } from '../../logger/types';
3
- export declare function betweenSemverMatcherContext(log: ILogger, ruleVO: IBetweenMatcherData): (key: string) => boolean;
3
+ export declare function betweenSemverMatcherContext(log: ILogger, ruleAttr: IBetweenStringMatcherData): (key: string) => boolean;
@@ -0,0 +1,3 @@
1
+ import { ISet } from '../../utils/lang/sets';
2
+ import { ILogger } from '../../logger/types';
3
+ export declare function inListSemverMatcherContext(log: ILogger, ruleAttr: ISet<string>): (runtimeAttr: string) => boolean;
@@ -0,0 +1,7 @@
1
+ import { ISplitMatcher } from '../../dtos/types';
2
+ /**
3
+ * Extract value from string matcher data.
4
+ */
5
+ export declare function stringTransform({ stringMatcherData }: ISplitMatcher): {
6
+ value: string | null | undefined;
7
+ };
@@ -1,4 +1,4 @@
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
3
  import { ISet } from '../utils/lang/sets';
4
4
  import { SplitIO } from '../types';
@@ -9,7 +9,7 @@ export interface IDependencyMatcherValue {
9
9
  }
10
10
  export interface IMatcherDto {
11
11
  type: number;
12
- value?: string | number | boolean | string[] | IDependencyMatcherData | ISet<string> | IBetweenMatcherData | null;
12
+ value?: string | number | boolean | string[] | IDependencyMatcherData | ISet<string> | IBetweenMatcherData | IBetweenStringMatcherData | null;
13
13
  attribute: string | null;
14
14
  negate: boolean;
15
15
  dataType: string;