From d51e8f1adbb7b49a8dc5e04f451e62f637b10f88 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 16 Oct 2025 10:54:01 -0300 Subject: [PATCH 01/10] Update segmentChangesUpdater to timeout and retry on startup --- CHANGES.txt | 3 +++ package-lock.json | 4 ++-- package.json | 2 +- src/logger/messages/info.ts | 2 +- src/logger/messages/warn.ts | 2 +- .../polling/syncTasks/segmentsSyncTask.ts | 2 ++ .../polling/updaters/segmentChangesUpdater.ts | 21 +++++++++++++++---- .../polling/updaters/splitChangesUpdater.ts | 9 ++++---- 8 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2b93b7fc..2060a5a0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.8.0 (October XX, 2025) + - Updated the SDK’s initial synchronization in Node.js (server-side) to use `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` to control the timeout and retry behavior of segment requests. + 2.7.1 (October 8, 2025) - Bugfix - Update `debug` option to support log levels when `logger` option is used. diff --git a/package-lock.json b/package-lock.json index 356a4e93..9faae9ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.7.1", + "version": "2.7.2-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.7.1", + "version": "2.7.2-rc.0", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index 30ea1784..c43f5994 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.7.1", + "version": "2.7.2-rc.0", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index fb017250..1e9b5f0d 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -22,7 +22,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'], [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'], [c.POLLING_STOP, c.LOG_PREFIX_SYNC_POLLING + 'Stopping polling'], - [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of feature flags #%s. Reason: %s'], + [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying fetch of feature flags (attempt #%s). Reason: %s'], [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and resetting timer.'], [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s.'], [c.SUBMITTERS_PUSH_PAGE_HIDDEN, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing %s because page became hidden.'], diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 81cfda1a..97a6a55f 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -6,7 +6,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.ENGINE_VALUE_INVALID, c.LOG_PREFIX_ENGINE_VALUE + 'Value %s doesn\'t match with expected type.'], [c.ENGINE_VALUE_NO_ATTRIBUTES, c.LOG_PREFIX_ENGINE_VALUE + 'Defined attribute `%s`. No attributes received.'], // synchronizer - [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying download of segments #%s. Reason: %s'], + [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying fetch of memberships (attempt #%s). Reason: %s'], [c.SYNC_SPLITS_FETCH_FAILS, c.LOG_PREFIX_SYNC_SPLITS + 'Error while doing fetch of feature flags. %s'], [c.STREAMING_PARSING_ERROR_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE error notification: %s'], [c.STREAMING_PARSING_MESSAGE_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE message notification: %s'], diff --git a/src/sync/polling/syncTasks/segmentsSyncTask.ts b/src/sync/polling/syncTasks/segmentsSyncTask.ts index f5a93711..b69d7087 100644 --- a/src/sync/polling/syncTasks/segmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/segmentsSyncTask.ts @@ -23,6 +23,8 @@ export function segmentsSyncTaskFactory( segmentChangesFetcherFactory(fetchSegmentChanges), storage.segments, readiness, + settings.startup.requestTimeoutBeforeReady, + settings.startup.retriesOnFailureBeforeReady, ), settings.scheduler.segmentsRefreshRate, 'segmentChangesUpdater' diff --git a/src/sync/polling/updaters/segmentChangesUpdater.ts b/src/sync/polling/updaters/segmentChangesUpdater.ts index ab951b24..7fe5b7b7 100644 --- a/src/sync/polling/updaters/segmentChangesUpdater.ts +++ b/src/sync/polling/updaters/segmentChangesUpdater.ts @@ -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 @@ -23,11 +24,18 @@ 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 { + function _promiseDecorator(promise: Promise) { + if (readyOnAlreadyExistentState && requestTimeoutBeforeReady) promise = timeout(requestTimeoutBeforeReady, promise); + return promise; + } + + function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean, retries?: number): Promise { log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`); let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName)); @@ -35,13 +43,19 @@ export function segmentChangesUpdaterFactory( // 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; }); }); } @@ -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 diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index 6c6371e3..cdff16af 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -120,8 +120,8 @@ export function splitChangesUpdaterFactory( storage: Pick, splitFiltersValidation: ISplitFiltersValidation, splitsEventEmitter?: ISplitsEventEmitter, - requestTimeoutBeforeReady: number = 0, - retriesOnFailureBeforeReady: number = 0, + requestTimeoutBeforeReady = 0, + retriesOnFailureBeforeReady = 0, isClientSide?: boolean ): SplitChangesUpdater { const { splits, rbSegments, segments } = storage; @@ -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; }); From 8f2fb26f11dc1119a4a0c0dff4c3b5c563b1f272 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 16 Oct 2025 12:33:18 -0300 Subject: [PATCH 02/10] Polishing --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2060a5a0..755f0f8b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 2.8.0 (October XX, 2025) - - Updated the SDK’s initial synchronization in Node.js (server-side) to use `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` to control the timeout and retry behavior of segment requests. + - 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. 2.7.1 (October 8, 2025) - Bugfix - Update `debug` option to support log levels when `logger` option is used. From ec15d2ccc79d16ea902dad3b5d0ba692a57dd79f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sun, 2 Nov 2025 17:46:55 -0300 Subject: [PATCH 03/10] fix: update import path for LOG_PREFIX constant in Redis storage --- src/readiness/__tests__/readinessManager.spec.ts | 4 ---- src/storages/inLocalStorage/__tests__/validateCache.spec.ts | 5 +++++ src/storages/inRedis/SegmentsCacheInRedis.ts | 2 +- types/splitio.d.ts | 2 -- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/readiness/__tests__/readinessManager.spec.ts b/src/readiness/__tests__/readinessManager.spec.ts index 174f1373..34eaf9a3 100644 --- a/src/readiness/__tests__/readinessManager.spec.ts +++ b/src/readiness/__tests__/readinessManager.spec.ts @@ -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; diff --git a/src/storages/inLocalStorage/__tests__/validateCache.spec.ts b/src/storages/inLocalStorage/__tests__/validateCache.spec.ts index 102444ba..7feea541 100644 --- a/src/storages/inLocalStorage/__tests__/validateCache.spec.ts +++ b/src/storages/inLocalStorage/__tests__/validateCache.spec.ts @@ -47,6 +47,7 @@ describe.each(storages)('validateCache', (storage) => { test('if there is cache and it must not be cleared, it should return true', async () => { storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + await storage.save && storage.save(); expect(await validateCache({}, storage, fullSettings, keys, splits, rbSegments, segments, largeSegments)).toBe(true); @@ -66,6 +67,7 @@ describe.each(storages)('validateCache', (storage) => { storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); storage.setItem(keys.buildLastUpdatedKey(), Date.now() - 1000 * 60 * 60 * 24 * 2 + ''); // 2 days ago + await storage.save && storage.save(); expect(await validateCache({ expirationDays: 1 }, storage, fullSettings, keys, splits, rbSegments, segments, largeSegments)).toBe(false); @@ -83,6 +85,7 @@ describe.each(storages)('validateCache', (storage) => { test('if there is cache and its hash has changed, it should clear cache and return false', async () => { storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + await storage.save && storage.save(); expect(await validateCache({}, storage, { ...fullSettings, core: { ...fullSettings.core, authorizationKey: 'another-sdk-key' } }, keys, splits, rbSegments, segments, largeSegments)).toBe(false); @@ -99,8 +102,10 @@ describe.each(storages)('validateCache', (storage) => { test('if there is cache and clearOnInit is true, it should clear cache and return false', async () => { // Older cache version (without last clear) + storage.removeItem(keys.buildLastClear()); storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + await storage.save && storage.save(); expect(await validateCache({ clearOnInit: true }, storage, fullSettings, keys, splits, rbSegments, segments, largeSegments)).toBe(false); diff --git a/src/storages/inRedis/SegmentsCacheInRedis.ts b/src/storages/inRedis/SegmentsCacheInRedis.ts index d645495f..5a81dede 100644 --- a/src/storages/inRedis/SegmentsCacheInRedis.ts +++ b/src/storages/inRedis/SegmentsCacheInRedis.ts @@ -1,6 +1,6 @@ import { ILogger } from '../../logger/types'; import { isNaNNumber } from '../../utils/lang'; -import { LOG_PREFIX } from '../inLocalStorage/constants'; +import { LOG_PREFIX } from './constants'; import { KeyBuilderSS } from '../KeyBuilderSS'; import { ISegmentsCacheAsync } from '../types'; import type { RedisAdapter } from './RedisAdapter'; diff --git a/types/splitio.d.ts b/types/splitio.d.ts index bd7da67c..ba2be58b 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -1335,8 +1335,6 @@ declare namespace SplitIO { /** * Defines the factory function to instantiate the storage. If not provided, the default in-memory storage is used. * - * NOTE: Currently, there is no persistent storage option available for the React Native SDK; only `InLocalStorage` for the Browser SDK. - * * Example: * ``` * SplitFactory({ From d35663b0f4d41e79d74889cb15439d80edbce118 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 6 Nov 2025 15:05:43 -0300 Subject: [PATCH 04/10] perf: avoid redundant setItem call if flagSetCache has not changed --- src/storages/inLocalStorage/SplitsCacheInLocal.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 3aa08452..51c8c485 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -206,6 +206,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { const flagSetFromStorage = this.storage.getItem(flagSetKey); const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []); + + if (flagSetCache.has(featureFlag.name)) return; + flagSetCache.add(featureFlag.name); this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache))); From d82f953c7cd00baa0739ab506b2761a8aa270e06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:42:59 +0000 Subject: [PATCH 05/10] Bump js-yaml Bumps and [js-yaml](https://github.com/nodeca/js-yaml). These dependencies needed to be updated together. Updates `js-yaml` from 4.1.0 to 4.1.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1) Updates `js-yaml` from 3.14.0 to 3.14.2 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.1.1 dependency-type: indirect - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6093d8d..f2fdbade 100644 --- a/package-lock.json +++ b/package-lock.json @@ -617,9 +617,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "dependencies": { "argparse": "^2.0.1" @@ -3381,9 +3381,9 @@ } }, "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "dependencies": { "argparse": "^2.0.1" @@ -6302,9 +6302,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "dependencies": { "argparse": "^1.0.7", @@ -8596,9 +8596,9 @@ } }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -10424,9 +10424,9 @@ "dev": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -12814,9 +12814,9 @@ "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "requires": { "argparse": "^1.0.7", From ba83154b0a4ad9ee820b824df3971638143a9339 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 19 Nov 2025 16:59:14 -0300 Subject: [PATCH 06/10] refactor: set change number only if update operations don't fail, for storage consistency --- CHANGES.txt | 3 ++ src/storages/AbstractMySegmentsCacheSync.ts | 46 ++++++++++-------- src/storages/AbstractSplitsCacheSync.ts | 5 +- .../inLocalStorage/MySegmentsCacheInLocal.ts | 39 +++++++-------- .../inLocalStorage/RBSegmentsCacheInLocal.ts | 45 +++++++---------- .../inLocalStorage/SplitsCacheInLocal.ts | 48 ++++++++----------- .../inMemory/RBSegmentsCacheInMemory.ts | 5 +- .../polling/updaters/splitChangesUpdater.ts | 4 +- 8 files changed, 91 insertions(+), 104 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4c71ee9f..e66a3f50 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.8.1 (November 21, 2025) + - 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). diff --git a/src/storages/AbstractMySegmentsCacheSync.ts b/src/storages/AbstractMySegmentsCacheSync.ts index 7d3dc304..289fdc0d 100644 --- a/src/storages/AbstractMySegmentsCacheSync.ts +++ b/src/storages/AbstractMySegmentsCacheSync.ts @@ -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; @@ -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; } } diff --git a/src/storages/AbstractSplitsCacheSync.ts b/src/storages/AbstractSplitsCacheSync.ts index 2a4b9b78..64194561 100644 --- a/src/storages/AbstractSplitsCacheSync.ts +++ b/src/storages/AbstractSplitsCacheSync.ts @@ -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 diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index fd038a07..1c54fdff 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -22,27 +22,17 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { 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 { @@ -63,12 +53,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() { @@ -84,4 +70,13 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { return n; } + registerSegments() { + try { + return super.registerSegments(); + } catch (e) { + this.log.error(LOG_PREFIX + e); + return false; + } + } + } diff --git a/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts b/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts index 56e8ed41..61bf13fa 100644 --- a/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts @@ -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) { @@ -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[] { diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 51c8c485..30945684 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -80,44 +80,34 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { } addSplit(split: ISplit) { - try { - const name = split.name; - const splitKey = this.keys.buildSplitKey(name); - const splitFromStorage = this.storage.getItem(splitKey); - const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null; - - if (previousSplit) { - this._decrementCounts(previousSplit); - this.removeFromFlagSets(previousSplit.name, previousSplit.sets); - } + const name = split.name; + const splitKey = this.keys.buildSplitKey(name); + const splitFromStorage = this.storage.getItem(splitKey); + const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null; + + if (previousSplit) { + this._decrementCounts(previousSplit); + this.removeFromFlagSets(previousSplit.name, previousSplit.sets); + } - this.storage.setItem(splitKey, JSON.stringify(split)); + this.storage.setItem(splitKey, JSON.stringify(split)); - this._incrementCounts(split); - this.addToFlagSets(split); + this._incrementCounts(split); + this.addToFlagSets(split); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + return true; } removeSplit(name: string): boolean { - try { - const split = this.getSplit(name); - if (!split) return false; + const split = this.getSplit(name); + if (!split) return false; - this.storage.removeItem(this.keys.buildSplitKey(name)); + this.storage.removeItem(this.keys.buildSplitKey(name)); - this._decrementCounts(split); - this.removeFromFlagSets(split.name, split.sets); + this._decrementCounts(split); + this.removeFromFlagSets(split.name, split.sets); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + return true; } getSplit(name: string): ISplit | null { diff --git a/src/storages/inMemory/RBSegmentsCacheInMemory.ts b/src/storages/inMemory/RBSegmentsCacheInMemory.ts index 2b876202..cd98dc62 100644 --- a/src/storages/inMemory/RBSegmentsCacheInMemory.ts +++ b/src/storages/inMemory/RBSegmentsCacheInMemory.ts @@ -16,9 +16,10 @@ export class RBSegmentsCacheInMemory 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.changeNumber = 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 add(rbSegment: IRBSegment): boolean { diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index 6c6371e3..b372fa46 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -163,8 +163,6 @@ export function splitChangesUpdaterFactory( splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator) ) .then((splitChanges: ISplitChangesResponse) => { - startingUp = false; - const usedSegments = new Set(); let ffUpdate: MaybeThenable = false; @@ -187,6 +185,8 @@ export function splitChangesUpdaterFactory( ]).then(([ffChanged, rbsChanged]) => { if (storage.save) storage.save(); + startingUp = false; + if (splitsEventEmitter) { // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments)))) From c09f14d6db7e3259576afd8e893560e2aaac41dd Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 20 Nov 2025 19:24:40 -0300 Subject: [PATCH 07/10] Add unit test --- CHANGES.txt | 2 +- .../inLocalStorage/MySegmentsCacheInLocal.ts | 7 ++++--- .../__tests__/MySegmentsCacheInLocal.spec.ts | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e66a3f50..3838af79 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -2.8.1 (November 21, 2025) +2.8.1 (November 25, 2025) - 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) diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index 1c54fdff..8aec126d 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -4,6 +4,8 @@ import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync'; import type { MySegmentsKeyBuilder } from '../KeyBuilderCS'; import { LOG_PREFIX, DEFINED } from './constants'; import { StorageAdapter } from '../types'; +import { MySegmentsData } from '../../sync/polling/types'; +import { IMySegmentsResponse } from '../../dtos/types'; export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { @@ -16,7 +18,6 @@ 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 { @@ -70,9 +71,9 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { return n; } - registerSegments() { + resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse) { try { - return super.registerSegments(); + return super.resetSegments(segmentsData); } catch (e) { this.log.error(LOG_PREFIX + e); return false; diff --git a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts index aa218423..f41ae671 100644 --- a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts +++ b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts @@ -39,3 +39,16 @@ test.each(storages)('SEGMENT CACHE / in LocalStorage', (storage) => { expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment-2')).toBe('1'); expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment')).toBe(null); }); + +test('SEGMENT CACHE / Special case: localStorage failure should not throw an exception', () => { + const cache = new MySegmentsCacheInLocal(loggerMock, new KeyBuilderCS(PREFIX, 'user2'), localStorage); + + // mock localStorage failure + const setItemSpy = jest.spyOn(localStorage, 'setItem').mockImplementation(() => { throw new Error('localStorage failure'); }); + setItemSpy.mockClear(); + + expect(cache.resetSegments({ k: [{ n: 'mocked-segment' }, { n: 'mocked-segment-2' }], cn: 123 })).toBe(false); + expect(setItemSpy).toHaveBeenCalledTimes(1); + + setItemSpy.mockRestore(); +}); From 0176b3bdf6ae2da185ffd2d5dc571fa24bbe062a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Nov 2025 18:23:24 -0300 Subject: [PATCH 08/10] refactor: move startingUp flag update after store in mySegmentsUpdater --- .../inLocalStorage/MySegmentsCacheInLocal.ts | 13 +------------ .../__tests__/MySegmentsCacheInLocal.spec.ts | 13 ------------- src/sync/polling/updaters/mySegmentsUpdater.ts | 6 +++--- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index 8aec126d..0a1cc258 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -2,10 +2,8 @@ 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'; -import { MySegmentsData } from '../../sync/polling/types'; -import { IMySegmentsResponse } from '../../dtos/types'; export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { @@ -71,13 +69,4 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { return n; } - resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse) { - try { - return super.resetSegments(segmentsData); - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } - } - } diff --git a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts index f41ae671..aa218423 100644 --- a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts +++ b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts @@ -39,16 +39,3 @@ test.each(storages)('SEGMENT CACHE / in LocalStorage', (storage) => { expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment-2')).toBe('1'); expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment')).toBe(null); }); - -test('SEGMENT CACHE / Special case: localStorage failure should not throw an exception', () => { - const cache = new MySegmentsCacheInLocal(loggerMock, new KeyBuilderCS(PREFIX, 'user2'), localStorage); - - // mock localStorage failure - const setItemSpy = jest.spyOn(localStorage, 'setItem').mockImplementation(() => { throw new Error('localStorage failure'); }); - setItemSpy.mockClear(); - - expect(cache.resetSegments({ k: [{ n: 'mocked-segment' }, { n: 'mocked-segment-2' }], cn: 123 })).toBe(false); - expect(setItemSpy).toHaveBeenCalledTimes(1); - - setItemSpy.mockRestore(); -}); diff --git a/src/sync/polling/updaters/mySegmentsUpdater.ts b/src/sync/polling/updaters/mySegmentsUpdater.ts index 4b6038c5..5421d3f9 100644 --- a/src/sync/polling/updaters/mySegmentsUpdater.ts +++ b/src/sync/polling/updaters/mySegmentsUpdater.ts @@ -66,10 +66,10 @@ export function mySegmentsUpdaterFactory( new Promise((res) => { updateSegments(segmentsData); res(true); }) : // If not provided, fetch mySegments mySegmentsFetcher(matchingKey, noCache, till, _promiseDecorator).then(segments => { - // Only when we have downloaded segments completely, we should not keep retrying anymore - startingUp = false; - updateSegments(segments); + + // Only when we have downloaded and stored segments completely, we should not keep retrying anymore + startingUp = false; return true; }); From e15e24fdadc8b3ae218b633226551eda9a85161a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Nov 2025 18:35:48 -0300 Subject: [PATCH 09/10] rc --- package-lock.json | 43 +++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6093d8d..79326f49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.0", + "version": "2.8.1-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.8.0", + "version": "2.8.1-rc.0", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -617,10 +617,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -3381,10 +3382,11 @@ } }, "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6302,10 +6304,11 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -8596,9 +8599,9 @@ } }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -10424,9 +10427,9 @@ "dev": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -12814,9 +12817,9 @@ "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "requires": { "argparse": "^1.0.7", diff --git a/package.json b/package.json index ab0a1d2f..638daadd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.0", + "version": "2.8.1-rc.0", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", From 001cdc2b433a0b1c487f69caff89523d9145c401 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 26 Nov 2025 13:54:03 -0300 Subject: [PATCH 10/10] stable --- .github/workflows/ci.yml | 4 ++-- .github/workflows/update-license-year.yml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2128f2b..dec7b190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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' diff --git a/.github/workflows/update-license-year.yml b/.github/workflows/update-license-year.yml index 7e0a945f..bfd31318 100644 --- a/.github/workflows/update-license-year.yml +++ b/.github/workflows/update-license-year.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/package-lock.json b/package-lock.json index 79326f49..7bf8ffce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.1-rc.0", + "version": "2.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.8.1-rc.0", + "version": "2.9.0", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index 638daadd..d68c8e55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.8.1-rc.0", + "version": "2.9.0", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js",