Skip to content

Commit eddf057

Browse files
Merge pull request #904 from splitio/development
Release v11.9.0
2 parents 07a65c1 + 574d814 commit eddf057

File tree

8 files changed

+86
-45
lines changed

8 files changed

+86
-45
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
11.9.0 (November 26, 2025)
2+
- Updated @splitsoftware/splitio-commons package to version 2.9.0, which:
3+
- updates the SDK’s initial synchronization in Node.js (server-side) to use the `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` options to control the timeout and retry behavior of segment requests.
4+
- updates the order of storage operations to prevent inconsistent states when using the `LOCALSTORAGE` storage type and the browser’s `localStorage` fails due to quota limits.
5+
16
11.8.0 (October 30, 2025)
27
- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs.
38
- Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc).

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio",
3-
"version": "11.8.0",
3+
"version": "11.9.0",
44
"description": "Split SDK",
55
"files": [
66
"README.md",
@@ -38,7 +38,7 @@
3838
"node": ">=14.0.0"
3939
},
4040
"dependencies": {
41-
"@splitsoftware/splitio-commons": "2.8.0",
41+
"@splitsoftware/splitio-commons": "2.9.0",
4242
"bloom-filters": "^3.0.4",
4343
"ioredis": "^4.28.0",
4444
"js-yaml": "^3.13.1",

src/__tests__/browserSuites/ready-from-cache.spec.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,34 @@ export const expectedHashWithFilter = '2ce5cc38'; // for SDK key '<fake-token-rf
108108

109109
export default function (fetchMock, assert) {
110110

111-
assert.test(t => { // Testing when we start from scratch
111+
assert.test(t => { // Testing when we start from scratch, with an initial localStorage write operation fail (should retry splitChanges with -1)
112112
const testUrls = {
113113
sdk: 'https://sdk.baseurl/readyFromCacheEmpty',
114114
events: 'https://events.baseurl/readyFromCacheEmpty'
115115
};
116116
localStorage.clear();
117+
118+
// simulate a localStorage failure when saving a FF and a membership
119+
const originalSetItem = localStorage.setItem;
120+
localStorage.setItem = (key, value) => {
121+
if (key.includes('.nicolas@split.io.')) {
122+
throw new Error('localStorage.setItem failed');
123+
}
124+
if (key.includes('.split.')) {
125+
localStorage.setItem = originalSetItem;
126+
throw new Error('localStorage.setItem failed');
127+
}
128+
return originalSetItem.call(localStorage, key, value);
129+
};
130+
117131
t.plan(4);
118132

119-
fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 });
120-
fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas });
121-
fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } });
122-
fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } });
133+
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 });
134+
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); // retry
135+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas });
136+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); // retry
137+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } });
138+
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } });
123139

124140
const splitio = SplitFactory({
125141
...baseConfig,

src/__tests__/consumer/node_redis.spec.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,14 @@ tape('Node.js Redis', function (t) {
550550
t.test('Connection error', assert => {
551551
initializeRedisServer()
552552
.then((server) => {
553-
const sdk = SplitFactory(config);
553+
const sdk = SplitFactory({
554+
...config,
555+
fallbackTreatments: {
556+
byFlag: {
557+
'always-on': 'control_always_on'
558+
}
559+
}
560+
});
554561
const client = sdk.client();
555562

556563
client.once(client.Event.SDK_READY_TIMED_OUT, assert.fail);
@@ -585,7 +592,7 @@ tape('Node.js Redis', function (t) {
585592
assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', {
586593
permissions: ['not_matching']
587594
}), 'control', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to "control".');
588-
assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'control', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to "control".');
595+
assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'control_always_on', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to fallback treatment or "control".');
589596

590597
assert.false(await client.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'In the event of a Redis error like a disconnection, track should resolve to false.');
591598

src/__tests__/nodeSuites/ready-promise.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ export default function readyPromiseAssertions(key, fetchMock, assert) {
105105
// /splitChanges takes longer than 'requestTimeoutBeforeReady' only for the first attempt
106106
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) + 20 });
107107
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) - 20 });
108-
mockSegmentChanges(fetchMock, new RegExp(config.urls.sdk + '/segmentChanges/*'), ['some_key']);
108+
mockSegmentChanges(fetchMock, new RegExp(config.urls.sdk + '/segmentChanges/(splitters|developers|segment_excluded_by_rbs)'), ['some_key']);
109+
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=-1', { status: 500, body: 'server error' });
110+
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=-1', { since: -1, till: 10, name: 'employees', added: ['some_key'], removed: [] });
111+
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=10', { since: 10, till: 10, name: 'employees', added: [], removed: [] });
112+
109113
fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', 200);
110114
fetchMock.postOnce(config.urls.events + '/testImpressions/count', 200);
111115

src/settings/defaults/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const packageVersion = '11.8.0';
1+
export const packageVersion = '11.9.0';

ts-tests/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ promise = client.destroy();
263263
promise = SDK.destroy();
264264
// @TODO not public yet
265265
// promise = client.flush();
266-
const promiseWhenReadyFromCache: Promise<boolean> = client.whenReadyFromCache();
266+
let promiseWhenReadyFromCache: Promise<boolean> = client.whenReadyFromCache();
267267

268268
// Get readiness status
269269
let status: SplitIO.ReadinessStatus = client.getStatus();
@@ -378,10 +378,15 @@ nodeEventEmitter = asyncClient;
378378

379379
// Ready, destroy and flush (same as for sync client, just for interface checking)
380380
promise = asyncClient.ready();
381+
promise = asyncClient.whenReady();
381382
promise = asyncClient.destroy();
382383
promise = AsyncSDK.destroy();
383384
// @TODO not public yet
384385
// promise = asyncClient.flush();
386+
promiseWhenReadyFromCache = asyncClient.whenReadyFromCache();
387+
388+
// Get readiness status
389+
status = asyncClient.getStatus();
385390

386391
// We can call getTreatment but always with a key.
387392
asyncTreatment = asyncClient.getTreatment(splitKey, 'mySplit');

0 commit comments

Comments
 (0)