Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
2.8.0 (Jul XX, 2025)
- Updated @splitsoftware/splitio package to version 11.4.1 that includes:
- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK.
- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.
- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on `SplitView` type objects. Read more in our docs.


2.7.2 (Jul 4, 2025)
- Updated base image to node:24.3.0-alpine3.21

Expand Down
17 changes: 17 additions & 0 deletions client/__tests__/allTreatments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,21 @@ describe('get-all-treatments', () => {
.set('Authorization', 'test');
expectOkAllTreatments(response, 200, expected, 2);
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-all-treatments?keys=[{"matchingKey":"test","trafficType":"localhost"}]&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-all-treatments?keys=[{"matchingKey":"test","trafficType":"localhost"}]')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'test');
expect(response.status).toBe(200);
});
});
17 changes: 17 additions & 0 deletions client/__tests__/allTreatmentsWithConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,21 @@ describe('get-all-treatments-with-config', () => {
.set('Authorization', 'test');
expectOkAllTreatments(response, 200, expected, 2);
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-all-treatments-with-config?keys=[{"matchingKey":"test","trafficType":"localhost"}]&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-all-treatments-with-config?keys=[{"matchingKey":"test","trafficType":"localhost"}]')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'test');
expect(response.status).toBe(200);
});
});
4 changes: 2 additions & 2 deletions client/__tests__/track.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('track', () => {

test('should be 400 if properties is invalid', async () => {
const expected = [
'properties must be a plain object.'
'properties must be a plain object with only boolean, string, number or null values.'
];
const response = await request(app)
.get('/client/track?key=my-key&event-type=my-event&traffic-type=my-traffic&value=1&properties=lalala')
Expand All @@ -158,7 +158,7 @@ describe('track', () => {
'key too long, key must be 250 characters or less.',
'you passed "@!test", event-type must adhere to the regular expression /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/g. This means an event_type must be alphanumeric, cannot be more than 80 characters long, and can only include a dash, underscore, period, or colon as separators of alphanumeric characters.',
'you passed an empty traffic-type, traffic-type must be a non-empty string.',
'properties must be a plain object.',
'properties must be a plain object with only boolean, string, number or null values.',
'value must be null or number.'
];
const key = getLongKey();
Expand Down
17 changes: 17 additions & 0 deletions client/__tests__/treatment.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,21 @@ describe('get-treatment', () => {
.set('Authorization', 'test');
expectOk(response, 200, 'control', 'nonexistant-experiment');
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatment?key=test&split-name=my-experiment&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment');
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatment?key=test&split-name=my-experiment')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment');
});
});
17 changes: 17 additions & 0 deletions client/__tests__/treatmentWithConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,21 @@ describe('get-treatment-with-config', () => {
.set('Authorization', 'test');
expectOk(response, 200, 'control', 'nonexistant-experiment', null);
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatment-with-config?key=test&split-name=my-experiment&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment', '{"desc" : "this applies only to ON treatment"}');
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatment-with-config?key=test&split-name=my-experiment')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment', '{"desc" : "this applies only to ON treatment"}');
});
});
25 changes: 25 additions & 0 deletions client/__tests__/treatments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,29 @@ describe('get-treatments', () => {
},
}, 3);
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments?key=test&split-names=my-experiment&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
},
}, 1);
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments?key=test&split-names=my-experiment')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
},
}, 1);
});
});
19 changes: 18 additions & 1 deletion client/__tests__/treatmentsByFlagSets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { expectedGreenResults, expectedPurpleResults, expectedPinkResults } = req
jest.mock('node-fetch', () => {
return jest.fn().mockImplementation((url) => {

const sdkUrl = 'https://sdk.test.io/api/splitChanges?s=1.1&since=-1';
const sdkUrl = 'https://sdk.test.io/api/splitChanges?s=1.3&since=-1';
const splitChange2 = require('../../utils/mocks/splitchanges.since.-1.till.1602796638344.json');
if (url.startsWith(sdkUrl)) return Promise.resolve({ status: 200, json: () => (splitChange2), ok: true });

Expand Down Expand Up @@ -273,4 +273,21 @@ describe('get-treatments-by-sets', () => {
.set('Authorization', 'key_pink');
expectOkMultipleResults(response, 200, expectedPinkResults, 5);
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments-by-sets?key=test&flag-sets=set_green&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'key_green');
expect(response.status).toBe(200);
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments-by-sets?key=test&flag-sets=set_green')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'key_green');
expect(response.status).toBe(200);
});
});
27 changes: 27 additions & 0 deletions client/__tests__/treatmentsWithConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,31 @@ describe('get-treatments-with-config', () => {
},
}, 3);
});

test('should be 200 if options.properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments-with-config?key=test&split-names=my-experiment&options={"properties":{"package":"premium","admin":true,"discount":50}}')
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
config: '{"desc" : "this applies only to ON treatment"}',
},
}, 1);
});

test('should be 200 if options.properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments-with-config?key=test&split-names=my-experiment')
.send({
options: { properties: { package: 'premium', admin: true, discount: 50 } },
})
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
config: '{"desc" : "this applies only to ON treatment"}',
},
}, 1);
});
});
2 changes: 1 addition & 1 deletion client/__tests__/treatmentsWithConfigByFlagSets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { expectedGreenResultsWithConfig, expectedPurpleResultsWithConfig, expecte

jest.mock('node-fetch', () => {
return jest.fn().mockImplementation((url) => {
const sdkUrl = 'https://sdk.test.io/api/splitChanges?s=1.1&since=-1';
const sdkUrl = 'https://sdk.test.io/api/splitChanges?s=1.3&since=-1';
const splitChange2 = require('../../utils/mocks/splitchanges.since.-1.till.1602796638344.json');
if (url.startsWith(sdkUrl)) return Promise.resolve({ status: 200, json: () => (splitChange2), ok: true});
return Promise.resolve({ status: 200, json: () => ({}), ok: true });
Expand Down
32 changes: 22 additions & 10 deletions client/client.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ const getTreatment = async (req, res) => {
const key = parseKey(req.splitio.matchingKey, req.splitio.bucketingKey);
const featureFlag = req.splitio.featureFlagName;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const evaluationResult = await client.getTreatment(key, featureFlag, attributes);
const evaluationResult = await client.getTreatment(key, featureFlag, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);

res.send({
Expand All @@ -36,9 +37,11 @@ const getTreatmentWithConfig = async (req, res) => {
const key = parseKey(req.splitio.matchingKey, req.splitio.bucketingKey);
const featureFlag = req.splitio.featureFlagName;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;


try {
const evaluationResult = await client.getTreatmentWithConfig(key, featureFlag, attributes);
const evaluationResult = await client.getTreatmentWithConfig(key, featureFlag, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);

res.send({
Expand All @@ -61,9 +64,10 @@ const getTreatments = async (req, res) => {
const key = parseKey(req.splitio.matchingKey, req.splitio.bucketingKey);
const featureFlags = req.splitio.featureFlagNames;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const evaluationResults = await client.getTreatments(key, featureFlags, attributes);
const evaluationResults = await client.getTreatments(key, featureFlags, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);

const result = {};
Expand All @@ -89,9 +93,10 @@ const getTreatmentsWithConfig = async (req, res) => {
const key = parseKey(req.splitio.matchingKey, req.splitio.bucketingKey);
const featureFlags = req.splitio.featureFlagNames;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const evaluationResults = await client.getTreatmentsWithConfig(key, featureFlags, attributes);
const evaluationResults = await client.getTreatmentsWithConfig(key, featureFlags, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);

res.send(evaluationResults);
Expand All @@ -110,9 +115,10 @@ const getTreatmentsByFlagSets = async (req, res) => {
const key = parseKey(req.splitio.matchingKey, req.splitio.bucketingKey);
const flagSets = req.splitio.flagSetNames;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const evaluationResults = await client.getTreatmentsByFlagSets(key, flagSets, attributes);
const evaluationResults = await client.getTreatmentsByFlagSets(key, flagSets, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);

const result = {};
Expand All @@ -138,9 +144,10 @@ const getTreatmentsWithConfigByFlagSets = async (req, res) => {
const key = parseKey(req.splitio.matchingKey, req.splitio.bucketingKey);
const flagSets = req.splitio.flagSetNames;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const evaluationResults = await client.getTreatmentsWithConfigByFlagSets(key, flagSets, attributes);
const evaluationResults = await client.getTreatmentsWithConfigByFlagSets(key, flagSets, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);

const result = evaluationResults;
Expand Down Expand Up @@ -176,8 +183,9 @@ const track = async (req, res) => {
* allTreatments matches featureFlags for passed trafficType and evaluates with passed key
* @param {Object} keys
* @param {Object} attributes
* @param {Object} evaluationOptions
*/
const allTreatments = async (authorization, keys, attributes) => {
const allTreatments = async (authorization, keys, attributes, evaluationOptions) => {
const manager = environmentManager.getManager(authorization);
const client = environmentManager.getClient(authorization);
try {
Expand All @@ -192,7 +200,9 @@ const allTreatments = async (authorization, keys, attributes) => {
const evaluation = await client.getTreatmentsWithConfig(
parseKey(key.matchingKey, key.bucketingKey),
featureFlagNames,
attributes);
attributes,
evaluationOptions
);
// Saves result for each trafficType
evaluations[key.trafficType] = evaluation;
}
Expand All @@ -212,9 +222,10 @@ const allTreatments = async (authorization, keys, attributes) => {
const getAllTreatmentsWithConfig = async (req, res) => {
const keys = req.splitio.keys;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const treatments = await allTreatments(req.headers.authorization, keys, attributes);
const treatments = await allTreatments(req.headers.authorization, keys, attributes, evaluationOptions);
environmentManager.updateLastEvaluation(req.headers.authorization);
res.send(treatments);
} catch (error) {
Expand All @@ -230,9 +241,10 @@ const getAllTreatmentsWithConfig = async (req, res) => {
const getAllTreatments = async (req, res) => {
const keys = req.splitio.keys;
const attributes = req.splitio.attributes;
const evaluationOptions = req.splitio.options;

try {
const treatments = await allTreatments(req.headers.authorization, keys, attributes);
const treatments = await allTreatments(req.headers.authorization, keys, attributes, evaluationOptions);
// Erases the config property for treatments
const trafficTypes = Object.keys(treatments);
trafficTypes.forEach(trafficType => {
Expand Down
Loading