Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
3.2.0 (August 10, 2026)
- Added support for AI configs.
- Removed Configs SDK-related types and modules and moved them to the Configs SDK repository.

3.1.0 (July 23, 2026)
- Added support for rule-based segments in /api/v1/configs endpoint.
- Updated polling flow to fetch new referenced segments immediately.
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "3.1.0",
"version": "3.1.1-rc.1",
"description": "Split JavaScript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
7 changes: 7 additions & 0 deletions src/dtos/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ export interface IRBSegment extends TargetingEntity {
} | null
}

export type ConfigType = 'standard' | 'ai';
export type ConfigSubtype = 'llm_call';

export interface IDefinition extends TargetingEntity {
trafficTypeName: string;
sets?: string[] | null;
Expand All @@ -231,6 +234,10 @@ export interface IDefinition extends TargetingEntity {
configurations?: {
[treatmentName: string]: string | SplitIO.JsonObject
} | null;
/** Definition classification. Absent means a feature flag. */
type?: ConfigType;
/** Only meaningful when `type === 'AI'`. */
subtype?: ConfigSubtype;
}

/** Interface of the parsed JSON response of `/splitChanges` */
Expand Down
6 changes: 3 additions & 3 deletions src/evaluator/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { CONTROL } from '../utils/constants';
import { IDefinition, MaybeThenable } from '../dtos/types';
import SplitIO from '../../types/splitio';
import { IStorageAsync, IStorageSync } from '../storages/types';
import { IEvaluation, IEvaluationResult, IDefinitionEvaluator } from './types';
import { IEvaluation, IDefinitionEvaluator } from './types';
import { ILogger } from '../logger/types';
import { ENGINE_DEFAULT } from '../logger/constants';
import { prerequisitesMatcherContext } from './matchers/prerequisites';

function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluationResult {
function evaluationResult(result: IEvaluation | undefined, defaultTreatment: string): IEvaluation {
return {
treatment: get(result, 'treatment', defaultTreatment),
label: get(result, 'label', NO_CONDITION_MATCH)
Expand All @@ -29,7 +29,7 @@ export function engineParser(log: ILogger, split: IDefinition, storage: IStorage

return {

getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<IEvaluationResult> {
getTreatment(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: IDefinitionEvaluator): MaybeThenable<IEvaluation> {

const parsedKey = keyParser(key);

Expand Down
33 changes: 16 additions & 17 deletions src/evaluator/__tests__/evaluate-feature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ test('EVALUATOR / should return label exception, treatment control and config nu
});


test('EVALUATOR / should return right label, treatment and config if storage returns without errors.', async () => {
test('EVALUATOR / should return right label, treatment and definition if storage returns without errors.', async () => {
const expectedOutput = {
treatment: 'on', label: 'in segment all',
config: '{color:\'black\'}', changeNumber: 1487277320548
treatment: 'on', label: 'in segment all', definition: splitsMock['config']
};
const expectedOutputControl = {
treatment: 'control', label: DEFINITION_NOT_FOUND, config: null
Expand All @@ -63,7 +62,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationWithConfig).toEqual(expectedOutput); // If the split is retrieved successfully we should get the right evaluation result, label and config.
expect(evaluationWithConfig).toEqual(expectedOutput); // If the split is retrieved successfully we should get the right evaluation result, label and definition.

const evaluationNotFound = evaluateFeature(
loggerMock,
Expand All @@ -81,7 +80,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluation).toEqual({ ...expectedOutput, 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.
expect(evaluation).toEqual({ ...expectedOutput, definition: splitsMock['regular'] }); // If the split is retrieved successfully we should get the right evaluation result, label and definition.

const evaluationKilled = evaluateFeature(
loggerMock,
Expand All @@ -90,8 +89,8 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationKilled).toEqual({ ...expectedOutput, treatment: 'off', config: null, label: SPLIT_KILLED });
// If the split is retrieved but is killed, we should get the right evaluation result, label and config.
expect(evaluationKilled).toEqual({ ...expectedOutput, treatment: 'off', label: SPLIT_KILLED, definition: splitsMock['killed'] });
// If the split is retrieved but is killed, we should get the right evaluation result, label and definition.

const evaluationArchived = evaluateFeature(
loggerMock,
Expand All @@ -100,8 +99,8 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationArchived).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, config: null });
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
expect(evaluationArchived).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, definition: splitsMock['archived'] });
// If the split is retrieved but is archived, we should get the right evaluation result, label and definition.

const evaluationtrafficAlocation1 = evaluateFeature(
loggerMock,
Expand All @@ -110,8 +109,8 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationtrafficAlocation1).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, config: null, treatment: 'off' });
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and config.
expect(evaluationtrafficAlocation1).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, treatment: 'off', definition: splitsMock['trafficAlocation1'] });
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and definition.

const evaluationKilledWithConfig = evaluateFeature(
loggerMock,
Expand All @@ -120,8 +119,8 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationKilledWithConfig).toEqual({ ...expectedOutput, treatment: 'off', label: SPLIT_KILLED });
// If the split is retrieved but is killed, we should get the right evaluation result, label and config.
expect(evaluationKilledWithConfig).toEqual({ ...expectedOutput, treatment: 'off', label: SPLIT_KILLED, definition: splitsMock['killedWithConfig'] });
// If the split is retrieved but is killed, we should get the right evaluation result, label and definition.

const evaluationArchivedWithConfig = evaluateFeature(
loggerMock,
Expand All @@ -130,8 +129,8 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationArchivedWithConfig).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, config: null });
// If the split is retrieved but is archived, we should get the right evaluation result, label and config.
expect(evaluationArchivedWithConfig).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, definition: splitsMock['archivedWithConfig'] });
// If the split is retrieved but is archived, we should get the right evaluation result, label and definition.

const evaluationtrafficAlocation1WithConfig = evaluateFeature(
loggerMock,
Expand All @@ -140,7 +139,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret
undefined,
mockStorage,
);
expect(evaluationtrafficAlocation1WithConfig).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, treatment: 'off' });
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and config.
expect(evaluationtrafficAlocation1WithConfig).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, treatment: 'off', definition: splitsMock['trafficAlocation1WithConfig'] });
// If the split is retrieved but is not in split (out of Traffic Allocation), we should get the right evaluation result, label and definition.

});
48 changes: 23 additions & 25 deletions src/evaluator/__tests__/evaluate-features.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ test('EVALUATOR - Multiple evaluations at once / should return label exception,
});


test('EVALUATOR - Multiple evaluations at once / should return right labels, treatments and configs if storage returns without errors.', async () => {
test('EVALUATOR - Multiple evaluations at once / should return right labels, treatments and definitions if storage returns without errors.', async () => {
const expectedOutput = {
config: {
treatment: 'on', label: 'in segment all',
config: '{color:\'black\'}', changeNumber: 1487277320548
treatment: 'on', label: 'in segment all', definition: splitsMock['config']
},
not_existent_split: {
treatment: 'control', label: DEFINITION_NOT_FOUND, config: null
Expand All @@ -83,43 +82,42 @@ test('EVALUATOR - Multiple evaluations at once / should return right labels, tre
mockStorage,
);
// assert evaluationWithConfig
expect(multipleEvaluationAtOnce['config']).toEqual(expectedOutput['config']); // If the split is retrieved successfully we should get the right evaluation result, label and config.
expect(multipleEvaluationAtOnce['config']).toEqual(expectedOutput['config']); // If the split is retrieved successfully we should get the right evaluation result, label and definition.
// assert evaluationNotFound
expect(multipleEvaluationAtOnce['not_existent_split']).toEqual(expectedOutput['not_existent_split']); // If the split is not retrieved successfully because it does not exist, we should get the right evaluation result, label and config.
// assert regular
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.
expect(multipleEvaluationAtOnce['regular']).toEqual({ ...expectedOutput['config'], definition: splitsMock['regular'] }); // If the split is retrieved successfully we should get the right evaluation result, label and definition.
// assert killed
expect(multipleEvaluationAtOnce['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: SPLIT_KILLED });
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and config.
expect(multipleEvaluationAtOnce['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', label: SPLIT_KILLED, definition: splitsMock['killed'] });
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and definition.

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

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

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

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

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

});

describe('EVALUATOR - Multiple evaluations at once by flag sets', () => {

const expectedOutput = {
config: {
treatment: 'on', label: 'in segment all',
config: '{color:\'black\'}', changeNumber: 1487277320548
treatment: 'on', label: 'in segment all', definition: splitsMock['config']
},
not_existent_split: {
treatment: 'control', label: DEFINITION_NOT_FOUND, config: null
Expand All @@ -146,14 +144,14 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => {
// @todo assert flag set not found - for input validations

// assert regular
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.
expect(multipleEvaluationAtOnceByFlagSets['regular']).toEqual({ ...expectedOutput['config'], definition: splitsMock['regular'] }); // If the split is retrieved successfully we should get the right evaluation result, label and definition.
// assert killed
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', config: null, label: SPLIT_KILLED });
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and config.
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual({ ...expectedOutput['config'], treatment: 'off', label: SPLIT_KILLED, definition: splitsMock['killed'] });
// 'If the split is retrieved but is killed, we should get the right evaluation result, label and definition.

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

// assert not_existent_split not in evaluation if it is not related to defined flag sets
expect(multipleEvaluationAtOnceByFlagSets['not_existent_split']).toEqual(undefined);
Expand All @@ -163,7 +161,7 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => {

multipleEvaluationAtOnceByFlagSets = await getResultsByFlagSets(['reg_and_config']);
expect(multipleEvaluationAtOnceByFlagSets['config']).toEqual(expectedOutput['config']);
expect(multipleEvaluationAtOnceByFlagSets['regular']).toEqual({ ...expectedOutput['config'], config: null });
expect(multipleEvaluationAtOnceByFlagSets['regular']).toEqual({ ...expectedOutput['config'], definition: splitsMock['regular'] });
expect(multipleEvaluationAtOnceByFlagSets['killed']).toEqual(undefined);
expect(multipleEvaluationAtOnceByFlagSets['archived']).toEqual(undefined);
});
Expand Down
4 changes: 2 additions & 2 deletions src/evaluator/condition/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export function conditionContext(log: ILogger, matcherEvaluator: (key: SplitIO.S
// Whitelisting has more priority than traffic allocation, so we don't apply this filtering to those conditions.
if (conditionType === 'ROLLOUT' && !shouldApplyRollout(trafficAllocation!, key.bucketingKey, trafficAllocationSeed!)) {
return {
treatment: undefined, // treatment value is assigned later
// treatment value is assigned later, at Engine's evaluationResult
label: NOT_IN_SPLIT
};
} as IEvaluation;
}

// matcherEvaluator could be Async, this relays on matchers return value, so we need
Expand Down
Loading