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
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
3 changes: 2 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
2.8.1 (November 25, 2025)
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)
Expand Down
7 changes: 5 additions & 2 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
2 changes: 2 additions & 0 deletions src/sync/polling/syncTasks/segmentsSyncTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export function segmentsSyncTaskFactory(
segmentChangesFetcherFactory(fetchSegmentChanges),
storage.segments,
readiness,
settings.startup.requestTimeoutBeforeReady,
settings.startup.retriesOnFailureBeforeReady,
),
settings.scheduler.segmentsRefreshRate,
'segmentChangesUpdater'
Expand Down
21 changes: 17 additions & 4 deletions src/sync/polling/updaters/segmentChangesUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IReadinessManager } from '../../../readiness/types';
import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
import { ILogger } from '../../../logger/types';
import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
import { timeout } from '../../../utils/promise/timeout';

type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise<boolean>

Expand All @@ -23,25 +24,38 @@ export function segmentChangesUpdaterFactory(
segmentChangesFetcher: ISegmentChangesFetcher,
segments: ISegmentsCacheBase,
readiness?: IReadinessManager,
requestTimeoutBeforeReady?: number,
retriesOnFailureBeforeReady?: number,
): ISegmentChangesUpdater {

let readyOnAlreadyExistentState = true;

function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean): Promise<boolean> {
function _promiseDecorator<T>(promise: Promise<T>) {
if (readyOnAlreadyExistentState && requestTimeoutBeforeReady) promise = timeout(requestTimeoutBeforeReady, promise);
return promise;
}

function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean, retries?: number): Promise<boolean> {
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));

return sincePromise.then(since => {
// if fetchOnlyNew flag, avoid processing already fetched segments
return fetchOnlyNew && since !== undefined ?
false :
segmentChangesFetcher(since || -1, segmentName, noCache, till).then((changes) => {
segmentChangesFetcher(since || -1, segmentName, noCache, till, _promiseDecorator).then((changes) => {
return Promise.all(changes.map(x => {
log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
return segments.update(segmentName, x.added, x.removed, x.till);
})).then((updates) => {
return updates.some(update => update);
});
}).catch(error => {
if (retries) {
log.warn(`${LOG_PREFIX_SYNC_SEGMENTS}Retrying fetch of segment ${segmentName} (attempt #${retries}). Reason: ${error}`);
return updateSegment(segmentName, noCache, till, fetchOnlyNew, retries - 1);
}
throw error;
});
});
}
Expand All @@ -63,8 +77,7 @@ export function segmentChangesUpdaterFactory(
let segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());

return segmentsPromise.then(segmentNames => {
// Async fetchers
const updaters = segmentNames.map(segmentName => updateSegment(segmentName, noCache, till, fetchOnlyNew));
const updaters = segmentNames.map(segmentName => updateSegment(segmentName, noCache, till, fetchOnlyNew, readyOnAlreadyExistentState ? retriesOnFailureBeforeReady : 0));

return Promise.all(updaters).then(shouldUpdateFlags => {
// if at least one segment fetch succeeded, mark segments ready
Expand Down
9 changes: 4 additions & 5 deletions src/sync/polling/updaters/splitChangesUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ export function splitChangesUpdaterFactory(
storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments' | 'save'>,
splitFiltersValidation: ISplitFiltersValidation,
splitsEventEmitter?: ISplitsEventEmitter,
requestTimeoutBeforeReady: number = 0,
retriesOnFailureBeforeReady: number = 0,
requestTimeoutBeforeReady = 0,
retriesOnFailureBeforeReady = 0,
isClientSide?: boolean
): SplitChangesUpdater {
const { splits, rbSegments, segments } = storage;
Expand Down Expand Up @@ -201,14 +201,13 @@ export function splitChangesUpdaterFactory(
});
})
.catch(error => {
log.warn(SYNC_SPLITS_FETCH_FAILS, [error]);

if (startingUp && retriesOnFailureBeforeReady > retry) {
retry += 1;
log.info(SYNC_SPLITS_FETCH_RETRY, [retry, error]);
log.warn(SYNC_SPLITS_FETCH_RETRY, [retry, error]);
return _splitChangesUpdater(sinces, retry);
} else {
startingUp = false;
log.warn(SYNC_SPLITS_FETCH_FAILS, [error]);
}
return false;
});
Expand Down