Skip to content

Commit 62f2b2f

Browse files
Merge pull request #455 from splitio/development
Release v2.9.0
2 parents 9801bb9 + 64fc292 commit 62f2b2f

19 files changed

+146
-149
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
- 6379:6379
2020
steps:
2121
- name: Checkout code
22-
uses: actions/checkout@v4
22+
uses: actions/checkout@v5
2323

2424
- name: Set up nodejs
25-
uses: actions/setup-node@v4
25+
uses: actions/setup-node@v6
2626
with:
2727
# @TODO: rollback to 'lts/*'
2828
node-version: '22'

.github/workflows/update-license-year.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- name: Checkout
16-
uses: actions/checkout@v4
16+
uses: actions/checkout@v5
1717
with:
1818
fetch-depth: 0
1919

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2.9.0 (November 26, 2025)
2+
- Updated 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.
3+
- Updated 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.
4+
15
2.8.0 (October 30, 2025)
26
- 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.
37
- Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc).

package-lock.json

Lines changed: 23 additions & 20 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": "2.8.0",
3+
"version": "2.9.0",
44
"description": "Split JavaScript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/readiness/__tests__/readinessManager.spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@ import { EventEmitter } from '../../utils/MinEvents';
33
import { IReadinessManager } from '../types';
44
import { SDK_READY, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_READY_FROM_CACHE, SDK_SPLITS_CACHE_LOADED, SDK_READY_TIMED_OUT } from '../constants';
55
import { ISettings } from '../../types';
6-
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
76

87
const settings = {
98
startup: {
109
readyTimeout: 0,
11-
},
12-
storage: {
13-
type: STORAGE_LOCALSTORAGE
1410
}
1511
} as unknown as ISettings;
1612

src/storages/AbstractMySegmentsCacheSync.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,10 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
4949
* For client-side synchronizer: it resets or updates the cache.
5050
*/
5151
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
52-
this.setChangeNumber(segmentsData.cn);
53-
52+
let isDiff = false;
5453
const { added, removed } = segmentsData as MySegmentsData;
5554

5655
if (added && removed) {
57-
let isDiff = false;
5856

5957
added.forEach(segment => {
6058
isDiff = this.addSegment(segment) || isDiff;
@@ -63,32 +61,40 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
6361
removed.forEach(segment => {
6462
isDiff = this.removeSegment(segment) || isDiff;
6563
});
64+
} else {
6665

67-
return isDiff;
68-
}
66+
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
67+
const storedSegmentKeys = this.getRegisteredSegments().sort();
6968

70-
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
71-
const storedSegmentKeys = this.getRegisteredSegments().sort();
69+
// Extreme fast => everything is empty
70+
if (!names.length && !storedSegmentKeys.length) {
71+
isDiff = false;
72+
} else {
7273

73-
// Extreme fast => everything is empty
74-
if (!names.length && !storedSegmentKeys.length) return false;
74+
let index = 0;
7575

76-
let index = 0;
76+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
7777

78-
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
78+
// Quick path => no changes
79+
if (index === names.length && index === storedSegmentKeys.length) {
80+
isDiff = false;
81+
} else {
7982

80-
// Quick path => no changes
81-
if (index === names.length && index === storedSegmentKeys.length) return false;
83+
// Slowest path => add and/or remove segments
84+
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
85+
this.removeSegment(storedSegmentKeys[removeIndex]);
86+
}
8287

83-
// Slowest path => add and/or remove segments
84-
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
85-
this.removeSegment(storedSegmentKeys[removeIndex]);
86-
}
88+
for (let addIndex = index; addIndex < names.length; addIndex++) {
89+
this.addSegment(names[addIndex]);
90+
}
8791

88-
for (let addIndex = index; addIndex < names.length; addIndex++) {
89-
this.addSegment(names[addIndex]);
92+
isDiff = true;
93+
}
94+
}
9095
}
9196

92-
return true;
97+
this.setChangeNumber(segmentsData.cn);
98+
return isDiff;
9399
}
94100
}

src/storages/AbstractSplitsCacheSync.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
1414
protected abstract setChangeNumber(changeNumber: number): boolean | void
1515

1616
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
17+
let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
18+
updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
1719
this.setChangeNumber(changeNumber);
18-
const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
19-
return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
20+
return updated;
2021
}
2122

2223
abstract getSplit(name: string): ISplit | null

src/storages/inLocalStorage/MySegmentsCacheInLocal.ts

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ILogger } from '../../logger/types';
22
import { isNaNNumber } from '../../utils/lang';
33
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
44
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
5-
import { LOG_PREFIX, DEFINED } from './constants';
5+
import { DEFINED } from './constants';
66
import { StorageAdapter } from '../types';
77

88
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
@@ -16,33 +16,22 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
1616
this.log = log;
1717
this.keys = keys;
1818
this.storage = storage;
19-
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
2019
}
2120

2221
protected addSegment(name: string): boolean {
2322
const segmentKey = this.keys.buildSegmentNameKey(name);
2423

25-
try {
26-
if (this.storage.getItem(segmentKey) === DEFINED) return false;
27-
this.storage.setItem(segmentKey, DEFINED);
28-
return true;
29-
} catch (e) {
30-
this.log.error(LOG_PREFIX + e);
31-
return false;
32-
}
24+
if (this.storage.getItem(segmentKey) === DEFINED) return false;
25+
this.storage.setItem(segmentKey, DEFINED);
26+
return true;
3327
}
3428

3529
protected removeSegment(name: string): boolean {
3630
const segmentKey = this.keys.buildSegmentNameKey(name);
3731

38-
try {
39-
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
40-
this.storage.removeItem(segmentKey);
41-
return true;
42-
} catch (e) {
43-
this.log.error(LOG_PREFIX + e);
44-
return false;
45-
}
32+
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
33+
this.storage.removeItem(segmentKey);
34+
return true;
4635
}
4736

4837
isInSegment(name: string): boolean {
@@ -63,12 +52,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
6352
}
6453

6554
protected setChangeNumber(changeNumber?: number) {
66-
try {
67-
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
68-
else this.storage.removeItem(this.keys.buildTillKey());
69-
} catch (e) {
70-
this.log.error(e);
71-
}
55+
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
56+
else this.storage.removeItem(this.keys.buildTillKey());
7257
}
7358

7459
getChangeNumber() {

src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
2626
}
2727

2828
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
29+
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
30+
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
2931
this.setChangeNumber(changeNumber);
30-
const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
31-
return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
32+
return updated;
3233
}
3334

3435
private setChangeNumber(changeNumber: number) {
@@ -48,40 +49,30 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
4849
}
4950

5051
private add(rbSegment: IRBSegment): boolean {
51-
try {
52-
const name = rbSegment.name;
53-
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
54-
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
55-
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
52+
const name = rbSegment.name;
53+
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
54+
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
55+
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
5656

57-
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57+
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
5858

59-
let usesSegmentsDiff = 0;
60-
if (previous && usesSegments(previous)) usesSegmentsDiff--;
61-
if (usesSegments(rbSegment)) usesSegmentsDiff++;
62-
if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);
59+
let usesSegmentsDiff = 0;
60+
if (previous && usesSegments(previous)) usesSegmentsDiff--;
61+
if (usesSegments(rbSegment)) usesSegmentsDiff++;
62+
if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);
6363

64-
return true;
65-
} catch (e) {
66-
this.log.error(LOG_PREFIX + e);
67-
return false;
68-
}
64+
return true;
6965
}
7066

7167
private remove(name: string): boolean {
72-
try {
73-
const rbSegment = this.get(name);
74-
if (!rbSegment) return false;
68+
const rbSegment = this.get(name);
69+
if (!rbSegment) return false;
7570

76-
this.storage.removeItem(this.keys.buildRBSegmentKey(name));
71+
this.storage.removeItem(this.keys.buildRBSegmentKey(name));
7772

78-
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
73+
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
7974

80-
return true;
81-
} catch (e) {
82-
this.log.error(LOG_PREFIX + e);
83-
return false;
84-
}
75+
return true;
8576
}
8677

8778
private getNames(): string[] {

0 commit comments

Comments
 (0)