Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d51e8f1
Update segmentChangesUpdater to timeout and retry on startup
EmilianoSanchez Oct 16, 2025
8f2fb26
Polishing
EmilianoSanchez Oct 16, 2025
043e96c
Merge branch 'refactor_checkIfServerSide_util' into handle-segmentcha…
EmilianoSanchez Oct 28, 2025
ec15d2c
fix: update import path for LOG_PREFIX constant in Redis storage
EmilianoSanchez Nov 2, 2025
d35663b
perf: avoid redundant setItem call if flagSetCache has not changed
EmilianoSanchez Nov 6, 2025
d82f953
Bump js-yaml
dependabot[bot] Nov 18, 2025
ba83154
refactor: set change number only if update operations don't fail, for…
EmilianoSanchez Nov 19, 2025
c09f14d
Add unit test
EmilianoSanchez Nov 20, 2025
f02988c
Merge branch 'main' into handle-segmentchanges-timeout
EmilianoSanchez Nov 20, 2025
0176b3b
refactor: move startingUp flag update after store in mySegmentsUpdater
EmilianoSanchez Nov 25, 2025
ad23063
Merge pull request #454 from splitio/polishing
EmilianoSanchez Nov 25, 2025
d2d3983
Merge branch 'development' into handle-segmentchanges-timeout
EmilianoSanchez Nov 25, 2025
e15e24f
rc
EmilianoSanchez Nov 25, 2025
001cdc2
stable
EmilianoSanchez Nov 26, 2025
e7f5b21
Merge branch 'development' into dependabot/npm_and_yarn/multi-75e6bc5210
EmilianoSanchez Nov 26, 2025
12a4672
Merge pull request #453 from splitio/dependabot/npm_and_yarn/multi-75…
EmilianoSanchez Nov 26, 2025
7b6a968
Merge branch 'development' into handle-segmentchanges-timeout
EmilianoSanchez Nov 26, 2025
64fc292
Merge pull request #440 from splitio/handle-segmentchanges-timeout
EmilianoSanchez Nov 26, 2025
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: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:
- 6379:6379
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Set up nodejs
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
# @TODO: rollback to 'lts/*'
node-version: '22'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-license-year.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0

Expand Down
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2.9.0 (November 26, 2025)
- 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.
- 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.

2.8.0 (October 30, 2025)
- 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.
- Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc).
Expand Down
43 changes: 23 additions & 20 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": "2.8.0",
"version": "2.9.0",
"description": "Split JavaScript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
4 changes: 0 additions & 4 deletions src/readiness/__tests__/readinessManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@ import { EventEmitter } from '../../utils/MinEvents';
import { IReadinessManager } from '../types';
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';
import { ISettings } from '../../types';
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';

const settings = {
startup: {
readyTimeout: 0,
},
storage: {
type: STORAGE_LOCALSTORAGE
}
} as unknown as ISettings;

Expand Down
46 changes: 26 additions & 20 deletions src/storages/AbstractMySegmentsCacheSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,10 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
* For client-side synchronizer: it resets or updates the cache.
*/
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
this.setChangeNumber(segmentsData.cn);

let isDiff = false;
const { added, removed } = segmentsData as MySegmentsData;

if (added && removed) {
let isDiff = false;

added.forEach(segment => {
isDiff = this.addSegment(segment) || isDiff;
Expand All @@ -63,32 +61,40 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
removed.forEach(segment => {
isDiff = this.removeSegment(segment) || isDiff;
});
} else {

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

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

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

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

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

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

// Slowest path => add and/or remove segments
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
this.removeSegment(storedSegmentKeys[removeIndex]);
}
for (let addIndex = index; addIndex < names.length; addIndex++) {
this.addSegment(names[addIndex]);
}

for (let addIndex = index; addIndex < names.length; addIndex++) {
this.addSegment(names[addIndex]);
isDiff = true;
}
}
}

return true;
this.setChangeNumber(segmentsData.cn);
return isDiff;
}
}
5 changes: 3 additions & 2 deletions src/storages/AbstractSplitsCacheSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
protected abstract setChangeNumber(changeNumber: number): boolean | void

update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
this.setChangeNumber(changeNumber);
const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
return updated;
}

abstract getSplit(name: string): ISplit | null
Expand Down
33 changes: 9 additions & 24 deletions src/storages/inLocalStorage/MySegmentsCacheInLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ILogger } from '../../logger/types';
import { isNaNNumber } from '../../utils/lang';
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
import { LOG_PREFIX, DEFINED } from './constants';
import { DEFINED } from './constants';
import { StorageAdapter } from '../types';

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

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

try {
if (this.storage.getItem(segmentKey) === DEFINED) return false;
this.storage.setItem(segmentKey, DEFINED);
return true;
} catch (e) {
this.log.error(LOG_PREFIX + e);
return false;
}
if (this.storage.getItem(segmentKey) === DEFINED) return false;
this.storage.setItem(segmentKey, DEFINED);
return true;
}

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

try {
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
this.storage.removeItem(segmentKey);
return true;
} catch (e) {
this.log.error(LOG_PREFIX + e);
return false;
}
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
this.storage.removeItem(segmentKey);
return true;
}

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

protected setChangeNumber(changeNumber?: number) {
try {
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
else this.storage.removeItem(this.keys.buildTillKey());
} catch (e) {
this.log.error(e);
}
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
else this.storage.removeItem(this.keys.buildTillKey());
}

getChangeNumber() {
Expand Down
45 changes: 18 additions & 27 deletions src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
}

update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
this.setChangeNumber(changeNumber);
const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
return updated;
}

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

private add(rbSegment: IRBSegment): boolean {
try {
const name = rbSegment.name;
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
const name = rbSegment.name;
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;

this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));

let usesSegmentsDiff = 0;
if (previous && usesSegments(previous)) usesSegmentsDiff--;
if (usesSegments(rbSegment)) usesSegmentsDiff++;
if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);
let usesSegmentsDiff = 0;
if (previous && usesSegments(previous)) usesSegmentsDiff--;
if (usesSegments(rbSegment)) usesSegmentsDiff++;
if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);

return true;
} catch (e) {
this.log.error(LOG_PREFIX + e);
return false;
}
return true;
}

private remove(name: string): boolean {
try {
const rbSegment = this.get(name);
if (!rbSegment) return false;
const rbSegment = this.get(name);
if (!rbSegment) return false;

this.storage.removeItem(this.keys.buildRBSegmentKey(name));
this.storage.removeItem(this.keys.buildRBSegmentKey(name));

if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);

return true;
} catch (e) {
this.log.error(LOG_PREFIX + e);
return false;
}
return true;
}

private getNames(): string[] {
Expand Down
Loading