Skip to content

Commit 62fb0a4

Browse files
Merge pull request #305 from splitio/baseline_semver
Baseline - Semver matchers
2 parents 6b5bd86 + 6258f84 commit 62fb0a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+727
-300
lines changed

CHANGES.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
1.14.0 (April XX, 2024)
2-
- Updated impression label to 'unsupported matcher type' when the matcher type is not supported by the SDK.
1+
1.14.0 (May 6, 2024)
2+
- Added support for targeting rules based on semantic versions (https://semver.org/).
3+
- Added special impression label "targeting rule type unsupported by sdk" when the matcher type is not supported by the SDK, which returns 'control' treatment.
4+
- Updated Split API client to include the flags spec version query parameter for the `splitChanges` and `auth` endpoints.
35

46
1.13.1 (January 10, 2024)
57
- Updated client `destroy` method to release SDK key immediately and avoid unexpected warning logs when a factory is created with the same SDK key after the previous one was destroyed.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-commons",
3-
"version": "1.13.1",
3+
"version": "1.14.0",
44
"description": "Split JavaScript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/dtos/types.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export interface IBetweenMatcherData {
1717
end: number
1818
}
1919

20+
export interface IBetweenStringMatcherData {
21+
start: string
22+
end: string
23+
}
24+
2025
export interface IWhitelistMatcherData {
2126
whitelist: string[]
2227
}
@@ -44,6 +49,7 @@ interface ISplitMatcherBase {
4449
dependencyMatcherData?: null | IDependencyMatcherData
4550
booleanMatcherData?: null | boolean
4651
stringMatcherData?: null | string
52+
betweenStringMatcherData?: null | IBetweenStringMatcherData
4753
}
4854

4955
interface IAllKeysMatcher extends ISplitMatcherBase {
@@ -130,9 +136,36 @@ interface IMatchesStringMatcher extends ISplitMatcherBase {
130136
stringMatcherData: string
131137
}
132138

139+
interface IEqualToSemverMatcher extends ISplitMatcherBase {
140+
matcherType: 'EQUAL_TO_SEMVER',
141+
stringMatcherData: string
142+
}
143+
144+
interface IGreaterThanOrEqualToSemverMatcher extends ISplitMatcherBase {
145+
matcherType: 'GREATER_THAN_OR_EQUAL_TO_SEMVER',
146+
stringMatcherData: string
147+
}
148+
149+
150+
interface ILessThanOrEqualToSemverMatcher extends ISplitMatcherBase {
151+
matcherType: 'LESS_THAN_OR_EQUAL_TO_SEMVER',
152+
stringMatcherData: string
153+
}
154+
155+
interface IBetweenSemverMatcher extends ISplitMatcherBase {
156+
matcherType: 'BETWEEN_SEMVER'
157+
betweenStringMatcherData: IBetweenStringMatcherData
158+
}
159+
160+
interface IInListSemverMatcher extends ISplitMatcherBase {
161+
matcherType: 'IN_LIST_SEMVER',
162+
whitelistMatcherData: IWhitelistMatcherData
163+
}
164+
133165
export type ISplitMatcher = IAllKeysMatcher | IInSegmentMatcher | IWhitelistMatcher | IEqualToMatcher | IGreaterThanOrEqualToMatcher |
134166
ILessThanOrEqualToMatcher | IBetweenMatcher | IEqualToSetMatcher | IContainsAnyOfSetMatcher | IContainsAllOfSetMatcher | IPartOfSetMatcher |
135-
IStartsWithMatcher | IEndsWithMatcher | IContainsStringMatcher | IInSplitTreatmentMatcher | IEqualToBooleanMatcher | IMatchesStringMatcher
167+
IStartsWithMatcher | IEndsWithMatcher | IContainsStringMatcher | IInSplitTreatmentMatcher | IEqualToBooleanMatcher | IMatchesStringMatcher |
168+
IEqualToSemverMatcher | IGreaterThanOrEqualToSemverMatcher | ILessThanOrEqualToSemverMatcher | IBetweenSemverMatcher | IInListSemverMatcher
136169

137170
/** Split object */
138171
export interface ISplitPartition {

src/evaluator/Engine.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { get } from '../utils/lang';
22
import { parser } from './parser';
33
import { keyParser } from '../utils/key';
44
import { thenable } from '../utils/promise/thenable';
5-
import * as LabelsConstants from '../utils/labels';
5+
import { EXCEPTION, NO_CONDITION_MATCH, SPLIT_ARCHIVED, SPLIT_KILLED } from '../utils/labels';
66
import { CONTROL } from '../utils/constants';
77
import { ISplit, MaybeThenable } from '../dtos/types';
88
import { SplitIO } from '../types';
@@ -13,7 +13,7 @@ import { ILogger } from '../logger/types';
1313
function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluationResult {
1414
return {
1515
treatment: get(result, 'treatment', defaultTreatment),
16-
label: get(result, 'label', LabelsConstants.NO_CONDITION_MATCH)
16+
label: get(result, 'label', NO_CONDITION_MATCH)
1717
};
1818
}
1919

@@ -55,16 +55,16 @@ export class Engine {
5555
} catch (err) {
5656
return {
5757
treatment: CONTROL,
58-
label: LabelsConstants.EXCEPTION
58+
label: EXCEPTION
5959
};
6060
}
6161

6262
if (this.isGarbage()) {
6363
treatment = CONTROL;
64-
label = LabelsConstants.SPLIT_ARCHIVED;
64+
label = SPLIT_ARCHIVED;
6565
} else if (killed) {
6666
treatment = defaultTreatment;
67-
label = LabelsConstants.SPLIT_KILLED;
67+
label = SPLIT_KILLED;
6868
} else {
6969
const evaluation = this.evaluator(
7070
parsedKey,
@@ -98,4 +98,3 @@ export class Engine {
9898
return this.baseInfo.changeNumber;
9999
}
100100
}
101-

src/evaluator/__tests__/evaluate-feature.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @ts-nocheck
22
import { evaluateFeature } from '../index';
3-
import * as LabelsConstants from '../../utils/labels';
3+
import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, SPLIT_NOT_FOUND } from '../../utils/labels';
44
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
55

66
const splitsMock = {
@@ -28,7 +28,7 @@ const mockStorage = {
2828
test('EVALUATOR / should return label exception, treatment control and config null on error', async function () {
2929
const expectedOutput = {
3030
treatment: 'control',
31-
label: LabelsConstants.EXCEPTION,
31+
label: EXCEPTION,
3232
config: null
3333
};
3434
const evaluationPromise = evaluateFeature(
@@ -52,7 +52,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
5252
config: '{color:\'black\'}', changeNumber: 1487277320548
5353
};
5454
const expectedOutputControl = {
55-
treatment: 'control', label: LabelsConstants.SPLIT_NOT_FOUND, config: null
55+
treatment: 'control', label: SPLIT_NOT_FOUND, config: null
5656
};
5757

5858
const evaluationWithConfig = evaluateFeature(
@@ -89,7 +89,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
8989
null,
9090
mockStorage,
9191
);
92-
expect(evaluationKilled).toEqual({ ...expectedOutput, treatment: 'off', config: null, label: LabelsConstants.SPLIT_KILLED });
92+
expect(evaluationKilled).toEqual({ ...expectedOutput, treatment: 'off', config: null, label: SPLIT_KILLED });
9393
// If the split is retrieved but is killed, we should get the right evaluation result, label and config.
9494

9595
const evaluationArchived = evaluateFeature(
@@ -99,7 +99,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
9999
null,
100100
mockStorage,
101101
);
102-
expect(evaluationArchived).toEqual({ ...expectedOutput, treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
102+
expect(evaluationArchived).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, config: null });
103103
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
104104

105105
const evaluationtrafficAlocation1 = evaluateFeature(
@@ -109,7 +109,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
109109
null,
110110
mockStorage,
111111
);
112-
expect(evaluationtrafficAlocation1).toEqual({ ...expectedOutput, label: LabelsConstants.NOT_IN_SPLIT, config: null, treatment: 'off' });
112+
expect(evaluationtrafficAlocation1).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, config: null, treatment: 'off' });
113113
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and config.
114114

115115
const evaluationKilledWithConfig = evaluateFeature(
@@ -119,7 +119,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
119119
null,
120120
mockStorage,
121121
);
122-
expect(evaluationKilledWithConfig).toEqual({ ...expectedOutput, treatment: 'off', label: LabelsConstants.SPLIT_KILLED });
122+
expect(evaluationKilledWithConfig).toEqual({ ...expectedOutput, treatment: 'off', label: SPLIT_KILLED });
123123
// If the split is retrieved but is killed, we should get the right evaluation result, label and config.
124124

125125
const evaluationArchivedWithConfig = evaluateFeature(
@@ -129,7 +129,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
129129
null,
130130
mockStorage,
131131
);
132-
expect(evaluationArchivedWithConfig).toEqual({ ...expectedOutput, treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
132+
expect(evaluationArchivedWithConfig).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, config: null });
133133
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
134134

135135
const evaluationtrafficAlocation1WithConfig = evaluateFeature(
@@ -139,7 +139,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
139139
null,
140140
mockStorage,
141141
);
142-
expect(evaluationtrafficAlocation1WithConfig).toEqual({ ...expectedOutput, label: LabelsConstants.NOT_IN_SPLIT, treatment: 'off' });
142+
expect(evaluationtrafficAlocation1WithConfig).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, treatment: 'off' });
143143
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and config.
144144

145145
});

src/evaluator/__tests__/evaluate-features.spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @ts-nocheck
22
import { evaluateFeatures, evaluateFeaturesByFlagSets } from '../index';
3-
import * as LabelsConstants from '../../utils/labels';
3+
import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, SPLIT_NOT_FOUND } from '../../utils/labels';
44
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
55
import { _Set } from '../../utils/lang/sets';
66
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../../logger/constants';
@@ -47,7 +47,7 @@ test('EVALUATOR - Multiple evaluations at once / should return label exception,
4747
const expectedOutput = {
4848
throw_exception: {
4949
treatment: 'control',
50-
label: LabelsConstants.EXCEPTION,
50+
label: EXCEPTION,
5151
config: null
5252
}
5353
};
@@ -73,7 +73,7 @@ test('EVALUATOR - Multiple evaluations at once / should return right labels, tre
7373
config: '{color:\'black\'}', changeNumber: 1487277320548
7474
},
7575
not_existent_split: {
76-
treatment: 'control', label: LabelsConstants.SPLIT_NOT_FOUND, config: null
76+
treatment: 'control', label: SPLIT_NOT_FOUND, config: null
7777
},
7878
};
7979

@@ -91,27 +91,27 @@ test('EVALUATOR - Multiple evaluations at once / should return right labels, tre
9191
// assert regular
9292
expect(multipleEvaluationAtOnce['regular']).toEqual({ ...expectedOutput['config'], config: null }); // If the split is retrieved successfully we should get the right evaluation result, label and config. If Split has no config it should have config equal null.
9393
// assert killed
94-
expect(multipleEvaluationAtOnce['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: LabelsConstants.SPLIT_KILLED });
94+
expect(multipleEvaluationAtOnce['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: SPLIT_KILLED });
9595
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and config.
9696

9797
// assert archived
98-
expect(multipleEvaluationAtOnce['archived']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
98+
expect(multipleEvaluationAtOnce['archived']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: SPLIT_ARCHIVED, config: null });
9999
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
100100

101101
// assert trafficAllocation1
102-
expect(multipleEvaluationAtOnce['trafficAlocation1']).toEqual({ ...expectedOutput['config'], label: LabelsConstants.NOT_IN_SPLIT, config: null, treatment: 'off' });
102+
expect(multipleEvaluationAtOnce['trafficAlocation1']).toEqual({ ...expectedOutput['config'], label: NOT_IN_SPLIT, config: null, treatment: 'off' });
103103
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and config.
104104

105105
// assert killedWithConfig
106-
expect(multipleEvaluationAtOnce['killedWithConfig']).toEqual({ ...expectedOutput['config'], treatment: 'off', label: LabelsConstants.SPLIT_KILLED });
106+
expect(multipleEvaluationAtOnce['killedWithConfig']).toEqual({ ...expectedOutput['config'], treatment: 'off', label: SPLIT_KILLED });
107107
// If the split is retrieved but is killed, we should get the right evaluation result, label and config.
108108

109109
// assert archivedWithConfig
110-
expect(multipleEvaluationAtOnce['archivedWithConfig']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
110+
expect(multipleEvaluationAtOnce['archivedWithConfig']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: SPLIT_ARCHIVED, config: null });
111111
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
112112

113113
// assert trafficAlocation1WithConfig
114-
expect(multipleEvaluationAtOnce['trafficAlocation1WithConfig']).toEqual({ ...expectedOutput['config'], label: LabelsConstants.NOT_IN_SPLIT, treatment: 'off' });
114+
expect(multipleEvaluationAtOnce['trafficAlocation1WithConfig']).toEqual({ ...expectedOutput['config'], label: NOT_IN_SPLIT, treatment: 'off' });
115115
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and config.
116116

117117
});
@@ -124,7 +124,7 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => {
124124
config: '{color:\'black\'}', changeNumber: 1487277320548
125125
},
126126
not_existent_split: {
127-
treatment: 'control', label: LabelsConstants.SPLIT_NOT_FOUND, config: null
127+
treatment: 'control', label: SPLIT_NOT_FOUND, config: null
128128
},
129129
};
130130

@@ -150,11 +150,11 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => {
150150
// assert regular
151151
expect(multipleEvaluationAtOnceByFlagSets['regular']).toEqual({ ...expectedOutput['config'], config: null }); // If the split is retrieved successfully we should get the right evaluation result, label and config. If Split has no config it should have config equal null.
152152
// assert killed
153-
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: LabelsConstants.SPLIT_KILLED });
153+
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: SPLIT_KILLED });
154154
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and config.
155155

156156
// assert archived
157-
expect(multipleEvaluationAtOnceByFlagSets['archived']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: LabelsConstants.SPLIT_ARCHIVED, config: null });
157+
expect(multipleEvaluationAtOnceByFlagSets['archived']).toEqual({ ...expectedOutput['config'], treatment: 'control', label: SPLIT_ARCHIVED, config: null });
158158
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
159159

160160
// assert not_existent_split not in evaluation if it is not related to defined flag sets

src/evaluator/combiners/ifelseif.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { findIndex } from '../../utils/lang';
22
import { ILogger } from '../../logger/types';
33
import { thenable } from '../../utils/promise/thenable';
4-
import * as LabelsConstants from '../../utils/labels';
4+
import { UNSUPPORTED_MATCHER_TYPE } from '../../utils/labels';
55
import { CONTROL } from '../../utils/constants';
66
import { SplitIO } from '../../types';
77
import { IEvaluation, IEvaluator, ISplitEvaluator } from '../types';
@@ -14,7 +14,7 @@ export function ifElseIfCombinerContext(log: ILogger, predicates: IEvaluator[]):
1414

1515
return {
1616
treatment: CONTROL,
17-
label: LabelsConstants.UNSUPPORTED_MATCHER_TYPE
17+
label: UNSUPPORTED_MATCHER_TYPE
1818
};
1919
}
2020

src/evaluator/condition/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getTreatment, shouldApplyRollout } from './engineUtils';
22
import { thenable } from '../../utils/promise/thenable';
3-
import * as LabelsConstants from '../../utils/labels';
3+
import { NOT_IN_SPLIT } from '../../utils/labels';
44
import { MaybeThenable } from '../../dtos/types';
55
import { IEvaluation, IEvaluator, ISplitEvaluator } from '../types';
66
import { SplitIO } from '../../types';
@@ -30,7 +30,7 @@ export function conditionContext(log: ILogger, matcherEvaluator: (...args: any)
3030
if (conditionType === 'ROLLOUT' && !shouldApplyRollout(trafficAllocation as number, (key as SplitIO.SplitKeyObject).bucketingKey as string, trafficAllocationSeed as number)) {
3131
return {
3232
treatment: undefined, // treatment value is assigned later
33-
label: LabelsConstants.NOT_IN_SPLIT
33+
label: NOT_IN_SPLIT
3434
};
3535
}
3636

src/evaluator/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Engine } from './Engine';
22
import { thenable } from '../utils/promise/thenable';
3-
import * as LabelsConstants from '../utils/labels';
3+
import { EXCEPTION, SPLIT_NOT_FOUND } from '../utils/labels';
44
import { CONTROL } from '../utils/constants';
55
import { ISplit, MaybeThenable } from '../dtos/types';
66
import { IStorageAsync, IStorageSync } from '../storages/types';
@@ -12,7 +12,7 @@ import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
1212

1313
const treatmentException = {
1414
treatment: CONTROL,
15-
label: LabelsConstants.EXCEPTION,
15+
label: EXCEPTION,
1616
config: null
1717
};
1818

@@ -143,15 +143,15 @@ function getEvaluation(
143143
): MaybeThenable<IEvaluationResult> {
144144
let evaluation: MaybeThenable<IEvaluationResult> = {
145145
treatment: CONTROL,
146-
label: LabelsConstants.SPLIT_NOT_FOUND,
146+
label: SPLIT_NOT_FOUND,
147147
config: null
148148
};
149149

150150
if (splitJSON) {
151151
const split = Engine.parse(log, splitJSON, storage);
152152
evaluation = split.getTreatment(key, attributes, evaluateFeature);
153153

154-
// If the storage is async and the evaluated split uses segment, evaluation is thenable
154+
// If the storage is async and the evaluated flag uses segments or dependencies, evaluation is thenable
155155
if (thenable(evaluation)) {
156156
return evaluation.then(result => {
157157
result.changeNumber = split.getChangeNumber();

0 commit comments

Comments
 (0)