From 82487b73312d66d4a81e82fad35ac715e1b6ed1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:04:06 +0000 Subject: [PATCH 01/14] Bump minimatch from 3.1.2 to 3.1.5 Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index a49477a9..73d9628f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -1554,7 +1553,6 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", "dev": true, - "peer": true, "dependencies": { "jest-diff": "^27.0.0", "pretty-format": "^27.0.0" @@ -1622,6 +1620,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.21.0", @@ -1672,7 +1671,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, - "peer": true, + "license": "BSD-2-Clause", "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -1883,7 +1882,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2268,7 +2266,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -2917,7 +2914,6 @@ "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4613,7 +4609,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -6633,10 +6628,11 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6667,7 +6663,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -7794,7 +7789,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 9cb3ccf124190ab3fe35cdd58251ec67b10a0850 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 5 Mar 2026 00:21:36 -0300 Subject: [PATCH 02/14] Handle null conditions and excluded fields in IRBSegment parsing --- src/dtos/types.ts | 4 ++-- src/sync/polling/updaters/splitChangesUpdater.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/dtos/types.ts b/src/dtos/types.ts index 6e21e831..bfd7269a 100644 --- a/src/dtos/types.ts +++ b/src/dtos/types.ts @@ -208,11 +208,11 @@ export interface IRBSegment { name: string, changeNumber: number, status: 'ACTIVE' | 'ARCHIVED', - conditions?: ISplitCondition[], + conditions?: ISplitCondition[] | null, excluded?: { keys?: string[] | null, segments?: IExcludedSegment[] | null - } + } | null } export interface ISplit { diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index 27a68313..b5786bbf 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -31,7 +31,7 @@ function checkAllSegmentsExist(segments: ISegmentsCacheBase): Promise { * Exported for testing purposes. */ export function parseSegments(ruleEntity: ISplit | IRBSegment, matcherType: typeof IN_SEGMENT | typeof IN_RULE_BASED_SEGMENT = IN_SEGMENT): Set { - const { conditions = [], excluded } = ruleEntity as IRBSegment; + const { conditions, excluded } = ruleEntity as IRBSegment; const segments = new Set(); if (excluded && excluded.segments) { @@ -42,12 +42,14 @@ export function parseSegments(ruleEntity: ISplit | IRBSegment, matcherType: type }); } - for (let i = 0; i < conditions.length; i++) { - const matchers = conditions[i].matcherGroup.matchers; + if (conditions) { + for (let i = 0; i < conditions.length; i++) { + const matchers = conditions[i].matcherGroup.matchers; - matchers.forEach(matcher => { - if (matcher.matcherType === matcherType) segments.add(matcher.userDefinedSegmentMatcherData.segmentName); - }); + matchers.forEach(matcher => { + if (matcher.matcherType === matcherType) segments.add(matcher.userDefinedSegmentMatcherData.segmentName); + }); + } } return segments; From bfab4960faa4505039275a02c973c0cc6fc2024b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 5 Mar 2026 02:03:59 -0300 Subject: [PATCH 03/14] =?UTF-8?q?Remove=20whenInit=20callback=20mechanism?= =?UTF-8?q?=20from=20trackers.=20It=20was=20originally=20added=20to=20hand?= =?UTF-8?q?le=20React=20SDK=20side=20effects,=20but=20it=E2=80=99s=20unnec?= =?UTF-8?q?essary=20if=20the=20user=20calls=20client.track=20inside=20an?= =?UTF-8?q?=20effect.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sdkFactory/index.ts | 13 ++---------- src/trackers/__tests__/eventTracker.spec.ts | 8 +++---- .../__tests__/impressionsTracker.spec.ts | 14 ++++++------- src/trackers/eventTracker.ts | 17 +++++++-------- src/trackers/impressionsTracker.ts | 21 ++++++++----------- 5 files changed, 27 insertions(+), 46 deletions(-) diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index c7c0c5fb..0a24360d 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -35,12 +35,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA // initialization let hasInit = false; - const initCallbacks: (() => void)[] = []; - - function whenInit(cb: () => void) { - if (hasInit) cb(); - else initCallbacks.push(cb); - } const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings); const readiness = sdkReadinessManager.readinessManager; @@ -82,8 +76,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA strategyDebugFactory(observer) : noneStrategy; - const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry); - const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry); + const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, integrationsManager, storage.telemetry); + const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry); // splitApi is used by SyncManager and Browser signal listener const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker); @@ -111,9 +105,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA uniqueKeysTracker.start(); syncManager && syncManager.start(); signalListener && signalListener.start(); - - initCallbacks.forEach((cb) => cb()); - initCallbacks.length = 0; } log.info(NEW_FACTORY, [settings.version]); diff --git a/src/trackers/__tests__/eventTracker.spec.ts b/src/trackers/__tests__/eventTracker.spec.ts index 95ce3e33..29336994 100644 --- a/src/trackers/__tests__/eventTracker.spec.ts +++ b/src/trackers/__tests__/eventTracker.spec.ts @@ -29,15 +29,13 @@ const fakeEvent = { } }; -const fakeWhenInit = (cb: () => void) => cb(); - /* Tests */ describe('Event Tracker', () => { test('Tracker API', () => { expect(typeof eventTrackerFactory).toBe('function'); // The module should return a function which acts as a factory. - const instance = eventTrackerFactory(fullSettings, fakeEventsCache, fakeWhenInit, fakeIntegrationsManager); + const instance = eventTrackerFactory(fullSettings, fakeEventsCache, fakeIntegrationsManager); expect(typeof instance.track).toBe('function'); // The instance should implement the track method. }); @@ -53,7 +51,7 @@ describe('Event Tracker', () => { } }); // @ts-ignore - const tracker = eventTrackerFactory(fullSettings, fakeEventsCache, fakeWhenInit, fakeIntegrationsManager, fakeTelemetryCache); + const tracker = eventTrackerFactory(fullSettings, fakeEventsCache, fakeIntegrationsManager, fakeTelemetryCache); const result1 = tracker.track(fakeEvent, 1); expect(fakeEventsCache.track.mock.calls[0]).toEqual([fakeEvent, 1]); // Should be present in the event cache. @@ -94,7 +92,7 @@ describe('Event Tracker', () => { const settings = { ...fullSettings }; const fakeEventsCache = { track: jest.fn(() => true) }; - const tracker = eventTrackerFactory(settings, fakeEventsCache, fakeWhenInit); + const tracker = eventTrackerFactory(settings, fakeEventsCache); expect(tracker.track(fakeEvent)).toBe(true); expect(fakeEventsCache.track).toBeCalledTimes(1); // event should be tracked if userConsent is undefined diff --git a/src/trackers/__tests__/impressionsTracker.spec.ts b/src/trackers/__tests__/impressionsTracker.spec.ts index 1db3875f..631db845 100644 --- a/src/trackers/__tests__/impressionsTracker.spec.ts +++ b/src/trackers/__tests__/impressionsTracker.spec.ts @@ -34,8 +34,6 @@ const fakeSettingsWithListener = { ...fakeSettings, impressionListener: fakeListener }; -const fakeWhenInit = (cb: () => void) => cb(); - const fakeNoneStrategy = { process: jest.fn(() => false) }; @@ -53,7 +51,7 @@ describe('Impressions Tracker', () => { const strategy = strategyDebugFactory(impressionObserverCSFactory()); test('Should be able to track impressions (in DEBUG mode without Previous Time).', () => { - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit); + const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy); const imp1 = { feature: '10', @@ -73,7 +71,7 @@ describe('Impressions Tracker', () => { }); test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', (done) => { - const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit, fakeIntegrationsManager); + const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeIntegrationsManager); const fakeImpression = { feature: 'impression' @@ -147,8 +145,8 @@ describe('Impressions Tracker', () => { impression3.time = 1234567891; const trackers = [ - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined), - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined) + impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), undefined), + impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), undefined) ]; expect(fakeImpressionsCache.track).not.toBeCalled(); // storage method should not be called until impressions are tracked. @@ -175,7 +173,7 @@ describe('Impressions Tracker', () => { impression3.time = Date.now(); const impressionCountsCache = new ImpressionCountsCacheInMemory(); - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any); + const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), undefined, fakeTelemetryCache as any); expect(fakeImpressionsCache.track).not.toBeCalled(); // cache method should not be called by just creating a tracker @@ -198,7 +196,7 @@ describe('Impressions Tracker', () => { test('Should track or not impressions depending on user consent status', () => { const settings = { ...fullSettings }; - const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit); + const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy); tracker.track([{ imp: impression }]); expect(fakeImpressionsCache.track).toBeCalledTimes(1); // impression should be tracked if userConsent is undefined diff --git a/src/trackers/eventTracker.ts b/src/trackers/eventTracker.ts index 8bf9a133..24a8e360 100644 --- a/src/trackers/eventTracker.ts +++ b/src/trackers/eventTracker.ts @@ -17,7 +17,6 @@ import { isConsumerMode } from '../utils/settingsValidation/mode'; export function eventTrackerFactory( settings: ISettings, eventsCache: IEventsCacheBase, - whenInit: (cb: () => void) => void, integrationsManager?: IEventsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync ): IEventTracker { @@ -33,15 +32,13 @@ export function eventTrackerFactory( if (tracked) { log.info(EVENTS_TRACKER_SUCCESS, [msg]); if (integrationsManager) { - whenInit(() => { - // Wrap in a timeout because we don't want it to be blocking. - setTimeout(() => { - // copy of event, to avoid unexpected behavior if modified by integrations - const eventDataCopy = objectAssign({}, eventData); - if (properties) eventDataCopy.properties = objectAssign({}, properties); - // integrationsManager does not throw errors (they are internally handled by each integration module) - integrationsManager.handleEvent(eventDataCopy); - }); + // Wrap in a timeout because we don't want it to be blocking. + setTimeout(() => { + // copy of event, to avoid unexpected behavior if modified by integrations + const eventDataCopy = objectAssign({}, eventData); + if (properties) eventDataCopy.properties = objectAssign({}, properties); + // integrationsManager does not throw errors (they are internally handled by each integration module) + integrationsManager.handleEvent(eventDataCopy); }); } } else { diff --git a/src/trackers/impressionsTracker.ts b/src/trackers/impressionsTracker.ts index 0d64a8ed..5694af26 100644 --- a/src/trackers/impressionsTracker.ts +++ b/src/trackers/impressionsTracker.ts @@ -15,7 +15,6 @@ export function impressionsTrackerFactory( impressionsCache: IImpressionsCacheBase, noneStrategy: IStrategy, strategy: IStrategy, - whenInit: (cb: () => void) => void, integrationsManager?: IImpressionsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync, ): IImpressionsTracker { @@ -67,18 +66,16 @@ export function impressionsTrackerFactory( sdkLanguageVersion: version }; - whenInit(() => { - // Wrap in a timeout because we don't want it to be blocking. - setTimeout(() => { - // integrationsManager.handleImpression does not throw errors - if (integrationsManager) integrationsManager.handleImpression(impressionData); + // Wrap in a timeout because we don't want it to be blocking. + setTimeout(() => { + // integrationsManager.handleImpression does not throw errors + if (integrationsManager) integrationsManager.handleImpression(impressionData); - try { // @ts-ignore. An exception on the listeners should not break the SDK. - if (impressionListener) impressionListener.logImpression(impressionData); - } catch (err) { - log.error(ERROR_IMPRESSIONS_LISTENER, [err]); - } - }); + try { // @ts-ignore. An exception on the listeners should not break the SDK. + if (impressionListener) impressionListener.logImpression(impressionData); + } catch (err) { + log.error(ERROR_IMPRESSIONS_LISTENER, [err]); + } }); } } From 6b495bbc352889c5e5910f6429b9800afb4d9916 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 5 Mar 2026 02:11:59 -0300 Subject: [PATCH 04/14] Move SDK initialization logic together with destroy, since both use the same modules. --- src/sdkClient/sdkClient.ts | 21 ++++++++++++++++++--- src/sdkFactory/__tests__/index.spec.ts | 9 +-------- src/sdkFactory/index.ts | 18 +++--------------- src/sdkFactory/types.ts | 6 +++++- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/sdkClient/sdkClient.ts b/src/sdkClient/sdkClient.ts index 01fee12b..f72cb4ea 100644 --- a/src/sdkClient/sdkClient.ts +++ b/src/sdkClient/sdkClient.ts @@ -1,6 +1,6 @@ import { objectAssign } from '../utils/lang/objectAssign'; import SplitIO from '../../types/splitio'; -import { releaseApiKey } from '../utils/inputValidation/apiKey'; +import { releaseApiKey, validateAndTrackApiKey } from '../utils/inputValidation/apiKey'; import { clientFactory } from './client'; import { clientInputValidationDecorator } from './clientInputValidation'; import { ISdkFactoryContext } from '../sdkFactory/types'; @@ -8,11 +8,12 @@ import { ISdkFactoryContext } from '../sdkFactory/types'; const COOLDOWN_TIME_IN_MILLIS = 1000; /** - * Creates an Sdk client, i.e., a base client with status and destroy interface + * Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface */ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient { const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params; + let hasInit = false; let lastActionTime = 0; function __cooldown(func: Function, time: number) { @@ -47,13 +48,27 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo params.fallbackTreatmentsCalculator ), - // Sdk destroy { + init() { + if (hasInit) return; + hasInit = true; + + if (!isSharedClient) { + validateAndTrackApiKey(settings.log, settings.core.authorizationKey); + sdkReadinessManager.readinessManager.init(); + uniqueKeysTracker.start(); + syncManager && syncManager.start(); + signalListener && signalListener.start(); + } + }, + flush() { // @TODO define cooldown time return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS); }, + destroy() { + hasInit = false; // Mark the SDK as destroyed immediately sdkReadinessManager.readinessManager.destroy(); diff --git a/src/sdkFactory/__tests__/index.spec.ts b/src/sdkFactory/__tests__/index.spec.ts index 4b9f58ab..cbf64e78 100644 --- a/src/sdkFactory/__tests__/index.spec.ts +++ b/src/sdkFactory/__tests__/index.spec.ts @@ -7,7 +7,7 @@ import { FallbackTreatmentsCalculator } from '../../evaluator/fallbackTreatments /** Mocks */ -const clientInstance = { destroy: jest.fn() }; +const clientInstance = { init: jest.fn(), destroy: jest.fn() }; const managerInstance = 'manager'; const mockStorage = { splits: jest.fn(), @@ -40,13 +40,10 @@ const paramsForAsyncSDK = { fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator() }; -const SignalListenerInstanceMock = { start: jest.fn() }; - // IBrowserSDK, full params const fullParamsForSyncSDK = { ...paramsForAsyncSDK, syncManagerFactory: jest.fn(), - SignalListener: jest.fn(() => SignalListenerInstanceMock), impressionsObserverFactory: jest.fn(), platform: { getEventSource: jest.fn(), @@ -79,10 +76,6 @@ function assertModulesCalled(params: any) { if (params.impressionsObserverFactory) { expect(params.impressionsObserverFactory).toBeCalledTimes(1); } - if (params.SignalListener) { - expect(params.SignalListener).toBeCalledTimes(1); - expect(SignalListenerInstanceMock.start).toBeCalledTimes(1); - } if (params.splitApiFactory) { expect(params.splitApiFactory.mock.calls).toEqual([[params.settings, params.platform, telemetryTrackerMock]]); } diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index 0a24360d..0247e093 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -4,7 +4,6 @@ import { impressionsTrackerFactory } from '../trackers/impressionsTracker'; import { eventTrackerFactory } from '../trackers/eventTracker'; import { telemetryTrackerFactory } from '../trackers/telemetryTracker'; import SplitIO from '../../types/splitio'; -import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey'; import { createLoggerAPI } from '../logger/sdkLogger'; import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants'; import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants'; @@ -33,9 +32,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc. // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization. - // initialization - let hasInit = false; - const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings); const readiness = sdkReadinessManager.readinessManager; @@ -62,7 +58,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED, { initialCacheLoad: false /* Not an initial load, cache exists */ }); } - const clients: Record = {}; + const clients: Record void }> = {}; const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now); const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker }); @@ -87,6 +83,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync); ctx.syncManager = syncManager; + // @TODO: move into platform, and call inside sdkClientFactory (if it's used only there) const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi); ctx.signalListener = signalListener; @@ -96,15 +93,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA function init() { - if (hasInit) return; - hasInit = true; - - // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle. - validateAndTrackApiKey(log, settings.core.authorizationKey); - readiness.init(); - uniqueKeysTracker.start(); - syncManager && syncManager.start(); - signalListener && signalListener.start(); + Object.keys(clients).map(key => clients[key].init()); } log.info(NEW_FACTORY, [settings.version]); @@ -126,7 +115,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA settings, destroy() { - hasInit = false; return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { }); } }, extraProps && extraProps(ctx), lazyInit ? { init } : init()); diff --git a/src/sdkFactory/types.ts b/src/sdkFactory/types.ts index 00890c8e..bd408591 100644 --- a/src/sdkFactory/types.ts +++ b/src/sdkFactory/types.ts @@ -98,7 +98,11 @@ export interface ISdkFactoryParams { // Sdk client method factory. // It Allows to distinguish SDK clients with the client-side API (`IBrowserSDK` and `IBrowserAsyncSDK`) or server-side API (`ISDK` and `IAsyncSDK`). - sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.IBrowserClient; (key: SplitIO.SplitKey): SplitIO.IBrowserClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient)) + sdkClientMethodFactory: (params: ISdkFactoryContext) => ( + { (): SplitIO.IBrowserClient & { init(): void }; (key: SplitIO.SplitKey): SplitIO.IBrowserClient & { init(): void }; } | + (() => SplitIO.IClient & { init(): void }) | + (() => SplitIO.IAsyncClient & { init(): void }) + ) // Impression observer factory. impressionsObserverFactory: () => IImpressionObserver From 16dea9faffcef6c231b28234d3cd29c8865b3d50 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 9 Mar 2026 22:28:04 -0300 Subject: [PATCH 05/14] Refactor impressionsTracker to handle its own initialization and cleanup, moving strategy and uniqueKeysTracker instantiation inside --- .../__tests__/sdkClientMethod.spec.ts | 6 +- .../__tests__/sdkClientMethodCS.spec.ts | 4 +- src/sdkClient/sdkClient.ts | 6 +- src/sdkFactory/index.ts | 26 ++----- src/sdkFactory/types.ts | 3 +- .../__tests__/impressionsTracker.spec.ts | 77 +++++++++---------- src/trackers/impressionsTracker.ts | 47 +++++++---- src/trackers/types.ts | 4 +- 8 files changed, 87 insertions(+), 86 deletions(-) diff --git a/src/sdkClient/__tests__/sdkClientMethod.spec.ts b/src/sdkClient/__tests__/sdkClientMethod.spec.ts index 9120f20e..bc18bd85 100644 --- a/src/sdkClient/__tests__/sdkClientMethod.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethod.spec.ts @@ -18,7 +18,7 @@ const paramMocks = [ settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} }, telemetryTracker: telemetryTrackerFactory(), clients: {}, - uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }, + impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() }, fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator({}) }, // SyncManager (i.e., Sync SDK) and Signal listener @@ -30,7 +30,7 @@ const paramMocks = [ settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} }, telemetryTracker: telemetryTrackerFactory(), clients: {}, - uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }, + impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() }, fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator({}) } ]; @@ -75,7 +75,7 @@ test.each(paramMocks)('sdkClientMethodFactory', (params, done: any) => { client.destroy().then(() => { expect(params.sdkReadinessManager.readinessManager.destroy).toBeCalledTimes(1); expect(params.storage.destroy).toBeCalledTimes(1); - expect(params.uniqueKeysTracker.stop).toBeCalledTimes(1); + expect(params.impressionsTracker.stop).toBeCalledTimes(1); if (params.syncManager) { expect(params.syncManager.stop).toBeCalledTimes(1); diff --git a/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts b/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts index 5924358f..1209d766 100644 --- a/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethodCS.spec.ts @@ -46,7 +46,7 @@ const params = { settings: settingsWithKey, telemetryTracker: telemetryTrackerFactory(), clients: {}, - uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() } + impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() } }; const invalidAttributes = [ @@ -96,7 +96,7 @@ describe('sdkClientMethodCSFactory', () => { expect(params.syncManager.stop).toBeCalledTimes(1); expect(params.syncManager.flush).toBeCalledTimes(1); expect(params.signalListener.stop).toBeCalledTimes(1); - expect(params.uniqueKeysTracker.stop).toBeCalledTimes(1); + expect(params.impressionsTracker.stop).toBeCalledTimes(1); }); }); diff --git a/src/sdkClient/sdkClient.ts b/src/sdkClient/sdkClient.ts index f72cb4ea..c50b8f72 100644 --- a/src/sdkClient/sdkClient.ts +++ b/src/sdkClient/sdkClient.ts @@ -11,7 +11,7 @@ const COOLDOWN_TIME_IN_MILLIS = 1000; * Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface */ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient { - const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params; + const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, impressionsTracker } = params; let hasInit = false; let lastActionTime = 0; @@ -56,7 +56,7 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo if (!isSharedClient) { validateAndTrackApiKey(settings.log, settings.core.authorizationKey); sdkReadinessManager.readinessManager.init(); - uniqueKeysTracker.start(); + impressionsTracker.start(); syncManager && syncManager.start(); signalListener && signalListener.start(); } @@ -77,7 +77,7 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo releaseApiKey(settings.core.authorizationKey); telemetryTracker.sessionLength(); signalListener && signalListener.stop(); - uniqueKeysTracker.stop(); + impressionsTracker.stop(); } // Stop background jobs diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index 0247e093..31a16c1c 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -8,11 +8,6 @@ import { createLoggerAPI } from '../logger/sdkLogger'; import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants'; import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants'; import { objectAssign } from '../utils/lang/objectAssign'; -import { strategyDebugFactory } from '../trackers/strategy/strategyDebug'; -import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized'; -import { strategyNoneFactory } from '../trackers/strategy/strategyNone'; -import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker'; -import { DEBUG, OPTIMIZED } from '../utils/constants'; import { setRolloutPlan } from '../storages/setRolloutPlan'; import { IStorageSync } from '../storages/types'; import { getMatching } from '../utils/key'; @@ -24,10 +19,9 @@ import { FallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCal export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IAsyncSDK | SplitIO.IBrowserSDK | SplitIO.IBrowserAsyncSDK { const { settings, platform, storageFactory, splitApiFactory, extraProps, - syncManagerFactory, SignalListener, impressionsObserverFactory, - integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory, - filterAdapterFactory, lazyInit } = params; - const { log, sync: { impressionsMode }, initialRolloutPlan, core: { key } } = settings; + syncManagerFactory, SignalListener, + integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory, lazyInit } = params; + const { log, initialRolloutPlan, core: { key } } = settings; // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc. // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization. @@ -62,23 +56,13 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now); const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker }); - const observer = impressionsObserverFactory(); - const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory()); - - const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker); - const strategy = impressionsMode === OPTIMIZED ? - strategyOptimizedFactory(observer, storage.impressionCounts) : - impressionsMode === DEBUG ? - strategyDebugFactory(observer) : - noneStrategy; - - const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, integrationsManager, storage.telemetry); + const impressionsTracker = impressionsTrackerFactory(params, storage, integrationsManager); const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry); // splitApi is used by SyncManager and Browser signal listener const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker); - const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform, fallbackTreatmentsCalculator }; + const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, readiness, settings, storage, platform, fallbackTreatmentsCalculator }; const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync); ctx.syncManager = syncManager; diff --git a/src/sdkFactory/types.ts b/src/sdkFactory/types.ts index bd408591..dcccc2bf 100644 --- a/src/sdkFactory/types.ts +++ b/src/sdkFactory/types.ts @@ -8,7 +8,7 @@ import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types'; import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/types'; import { ISyncManager } from '../sync/types'; import { IImpressionObserver } from '../trackers/impressionObserver/types'; -import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types'; +import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter } from '../trackers/types'; import { ISettings } from '../types'; import SplitIO from '../../types/splitio'; @@ -47,7 +47,6 @@ export interface ISdkFactoryContext { eventTracker: IEventTracker, telemetryTracker: ITelemetryTracker, storage: IStorageSync | IStorageAsync, - uniqueKeysTracker: IUniqueKeysTracker, signalListener?: ISignalListener splitApi?: ISplitApi syncManager?: ISyncManager, diff --git a/src/trackers/__tests__/impressionsTracker.spec.ts b/src/trackers/__tests__/impressionsTracker.spec.ts index 631db845..6e571b88 100644 --- a/src/trackers/__tests__/impressionsTracker.spec.ts +++ b/src/trackers/__tests__/impressionsTracker.spec.ts @@ -2,10 +2,8 @@ import { impressionsTrackerFactory } from '../impressionsTracker'; import { ImpressionCountsCacheInMemory } from '../../storages/inMemory/ImpressionCountsCacheInMemory'; import { impressionObserverSSFactory } from '../impressionObserver/impressionObserverSS'; import { impressionObserverCSFactory } from '../impressionObserver/impressionObserverCS'; -import SplitIO from '../../../types/splitio'; +import SplitIO, { ImpressionsMode } from '../../../types/splitio'; import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; -import { strategyDebugFactory } from '../strategy/strategyDebug'; -import { strategyOptimizedFactory } from '../strategy/strategyOptimized'; import { DEDUPED, QUEUED } from '../../utils/constants'; /* Mocks */ @@ -22,20 +20,22 @@ const fakeListener = { const fakeIntegrationsManager = { handleImpression: jest.fn() }; -const fakeSettings = { - ...fullSettings, - runtime: { - hostname: 'fake-hostname', - ip: 'fake-ip' - }, - version: 'jest-test' +const fakeStorage = { + impressions: fakeImpressionsCache, + impressionCounts: new ImpressionCountsCacheInMemory(), + uniqueKeys: { track: jest.fn() }, + telemetry: undefined }; -const fakeSettingsWithListener = { - ...fakeSettings, - impressionListener: fakeListener +const fakeParams = { + settings: fullSettings, + impressionsObserverFactory: impressionObserverCSFactory, }; -const fakeNoneStrategy = { - process: jest.fn(() => false) +const fakeParamsWithListener = { + settings: { + ...fullSettings, + impressionListener: fakeListener + }, + impressionsObserverFactory: impressionObserverCSFactory, }; /* Tests */ @@ -48,10 +48,8 @@ describe('Impressions Tracker', () => { fakeIntegrationsManager.handleImpression.mockClear(); }); - const strategy = strategyDebugFactory(impressionObserverCSFactory()); - test('Should be able to track impressions (in DEBUG mode without Previous Time).', () => { - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy); + const tracker = impressionsTrackerFactory(fakeParams, fakeStorage); const imp1 = { feature: '10', @@ -70,8 +68,8 @@ describe('Impressions Tracker', () => { expect(fakeImpressionsCache.track.mock.calls[0][0]).toEqual([imp1, imp2]); // Should call the storage track method once we invoke .track() method, passing impressions with `track` enabled }); - test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', (done) => { - const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeIntegrationsManager); + test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', async () => { + const tracker = impressionsTrackerFactory(fakeParamsWithListener, fakeStorage, fakeIntegrationsManager); const fakeImpression = { feature: 'impression' @@ -94,25 +92,23 @@ describe('Impressions Tracker', () => { expect(fakeListener.logImpression).not.toBeCalled(); // The listener should not be executed synchronously. expect(fakeIntegrationsManager.handleImpression).not.toBeCalled(); // The integrations manager handleImpression method should not be executed synchronously. - setTimeout(() => { - expect(fakeListener.logImpression).toBeCalledTimes(2); // The listener should be executed after the timeout wrapping make it to the queue stack, once per each tracked impression. - expect(fakeIntegrationsManager.handleImpression).toBeCalledTimes(2); // The integrations manager handleImpression method should be executed after the timeout wrapping make it to the queue stack, once per each tracked impression. + await new Promise(resolve => setTimeout(resolve, 0)); - const impressionData1 = { impression: fakeImpression, attributes: fakeAttributes, sdkLanguageVersion: fakeSettings.version, ip: fakeSettings.runtime.ip, hostname: fakeSettings.runtime.hostname }; - const impressionData2 = { impression: fakeImpression2, attributes: fakeAttributes, sdkLanguageVersion: fakeSettings.version, ip: fakeSettings.runtime.ip, hostname: fakeSettings.runtime.hostname }; + expect(fakeListener.logImpression).toBeCalledTimes(2); // The listener should be executed after the timeout wrapping make it to the queue stack, once per each tracked impression. + expect(fakeIntegrationsManager.handleImpression).toBeCalledTimes(2); // The integrations manager handleImpression method should be executed after the timeout wrapping make it to the queue stack, once per each tracked impression. - expect(fakeListener.logImpression.mock.calls[0][0]).toEqual(impressionData1); // The listener should be executed with the corresponding map for each of the impressions. - expect(fakeListener.logImpression.mock.calls[1][0]).toEqual(impressionData2); // The listener should be executed with the corresponding map for each of the impressions. - expect(fakeListener.logImpression.mock.calls[0][0].impression).not.toBe(fakeImpression); // but impression should be a copy - expect(fakeListener.logImpression.mock.calls[1][0].impression).not.toBe(fakeImpression2); // but impression should be a copy + const impressionData1 = { impression: fakeImpression, attributes: fakeAttributes, sdkLanguageVersion: fullSettings.version, ip: fullSettings.runtime.ip, hostname: fullSettings.runtime.hostname }; + const impressionData2 = { impression: fakeImpression2, attributes: fakeAttributes, sdkLanguageVersion: fullSettings.version, ip: fullSettings.runtime.ip, hostname: fullSettings.runtime.hostname }; - expect(fakeIntegrationsManager.handleImpression.mock.calls[0][0]).toEqual(impressionData1); // The integration manager handleImpression method should be executed with the corresponding map for each of the impressions. - expect(fakeIntegrationsManager.handleImpression.mock.calls[1][0]).toEqual(impressionData2); // The integration manager handleImpression method should be executed with the corresponding map for each of the impressions. - expect(fakeIntegrationsManager.handleImpression.mock.calls[0][0].impression).not.toBe(fakeImpression); // but impression should be a copy - expect(fakeIntegrationsManager.handleImpression.mock.calls[1][0].impression).not.toBe(fakeImpression2); // but impression should be a copy + expect(fakeListener.logImpression.mock.calls[0][0]).toEqual(impressionData1); // The listener should be executed with the corresponding map for each of the impressions. + expect(fakeListener.logImpression.mock.calls[1][0]).toEqual(impressionData2); // The listener should be executed with the corresponding map for each of the impressions. + expect(fakeListener.logImpression.mock.calls[0][0].impression).not.toBe(fakeImpression); // but impression should be a copy + expect(fakeListener.logImpression.mock.calls[1][0].impression).not.toBe(fakeImpression2); // but impression should be a copy - done(); - }, 0); + expect(fakeIntegrationsManager.handleImpression.mock.calls[0][0]).toEqual(impressionData1); // The integration manager handleImpression method should be executed with the corresponding map for each of the impressions. + expect(fakeIntegrationsManager.handleImpression.mock.calls[1][0]).toEqual(impressionData2); // The integration manager handleImpression method should be executed with the corresponding map for each of the impressions. + expect(fakeIntegrationsManager.handleImpression.mock.calls[0][0].impression).not.toBe(fakeImpression); // but impression should be a copy + expect(fakeIntegrationsManager.handleImpression.mock.calls[1][0].impression).not.toBe(fakeImpression2); // but impression should be a copy }); const impression = { @@ -145,8 +141,8 @@ describe('Impressions Tracker', () => { impression3.time = 1234567891; const trackers = [ - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), undefined), - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), undefined) + impressionsTrackerFactory({ ...fakeParams, impressionsObserverFactory: impressionObserverSSFactory }, fakeStorage), + impressionsTrackerFactory(fakeParams, fakeStorage) ]; expect(fakeImpressionsCache.track).not.toBeCalled(); // storage method should not be called until impressions are tracked. @@ -173,7 +169,7 @@ describe('Impressions Tracker', () => { impression3.time = Date.now(); const impressionCountsCache = new ImpressionCountsCacheInMemory(); - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), undefined, fakeTelemetryCache as any); + const tracker = impressionsTrackerFactory(fakeParams, { ...fakeStorage, impressionCounts: impressionCountsCache, telemetry: fakeTelemetryCache } as any); expect(fakeImpressionsCache.track).not.toBeCalled(); // cache method should not be called by just creating a tracker @@ -194,9 +190,10 @@ describe('Impressions Tracker', () => { }); test('Should track or not impressions depending on user consent status', () => { - const settings = { ...fullSettings }; + const settings = { ...fullSettings, sync: { ...fullSettings.sync, impressionsMode: 'DEBUG' as ImpressionsMode } }; + const params = { settings, impressionsObserverFactory: impressionObserverCSFactory }; - const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy); + const tracker = impressionsTrackerFactory(params, fakeStorage); tracker.track([{ imp: impression }]); expect(fakeImpressionsCache.track).toBeCalledTimes(1); // impression should be tracked if userConsent is undefined diff --git a/src/trackers/impressionsTracker.ts b/src/trackers/impressionsTracker.ts index 5694af26..9f4663a2 100644 --- a/src/trackers/impressionsTracker.ts +++ b/src/trackers/impressionsTracker.ts @@ -1,27 +1,46 @@ import { objectAssign } from '../utils/lang/objectAssign'; import { thenable } from '../utils/promise/thenable'; -import { IImpressionsCacheBase, ITelemetryCacheSync, ITelemetryCacheAsync } from '../storages/types'; -import { IImpressionsHandler, IImpressionsTracker, ImpressionDecorated, IStrategy } from './types'; -import { ISettings } from '../types'; +import { ITelemetryCacheSync, IStorageBase } from '../storages/types'; +import { IImpressionsHandler, IImpressionsTracker, ImpressionDecorated } from './types'; import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants'; -import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants'; +import { CONSENT_DECLINED, DEBUG, DEDUPED, OPTIMIZED, QUEUED } from '../utils/constants'; import SplitIO from '../../types/splitio'; +import { ISdkFactoryParams } from '../sdkFactory/types'; +import { strategyDebugFactory } from './strategy/strategyDebug'; +import { strategyNoneFactory } from './strategy/strategyNone'; +import { strategyOptimizedFactory } from './strategy/strategyOptimized'; +import { uniqueKeysTrackerFactory } from './uniqueKeysTracker'; /** * Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided. */ export function impressionsTrackerFactory( - settings: ISettings, - impressionsCache: IImpressionsCacheBase, - noneStrategy: IStrategy, - strategy: IStrategy, + params: Pick, + storage: Pick, integrationsManager?: IImpressionsHandler, - telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync, ): IImpressionsTracker { - const { log, impressionListener, runtime: { ip, hostname }, version } = settings; + const { settings, impressionsObserverFactory, filterAdapterFactory } = params; + const { log, impressionListener, runtime: { ip, hostname }, version, sync: { impressionsMode } } = settings; + const observer = impressionsObserverFactory(); + const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory()); + + const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker); + const strategy = impressionsMode === OPTIMIZED ? + strategyOptimizedFactory(observer, storage.impressionCounts) : + impressionsMode === DEBUG ? + strategyDebugFactory(observer) : + noneStrategy; return { + start() { + uniqueKeysTracker.start(); + }, + + stop() { + uniqueKeysTracker.stop(); + }, + track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes) { if (settings.userConsent === CONSENT_DECLINED) return; @@ -35,7 +54,7 @@ export function impressionsTrackerFactory( const impressionsToStoreLength = impressionsToStore.length; if (impressionsToStoreLength) { - const res = impressionsCache.track(impressionsToStore.map((item) => item.imp)); + const res = storage.impressions.track(impressionsToStore.map((item) => item.imp)); // If we're on an async storage, handle error and log it. if (thenable(res)) { @@ -47,9 +66,9 @@ export function impressionsTrackerFactory( } else { // Record when impressionsCache is sync only (standalone mode) // @TODO we are not dropping impressions on full queue yet, so DROPPED stats are not recorded - if (telemetryCache) { - (telemetryCache as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStoreLength); - (telemetryCache as ITelemetryCacheSync).recordImpressionStats(DEDUPED, impressionsLength - impressionsToStoreLength); + if (storage.telemetry) { + (storage.telemetry as ITelemetryCacheSync).recordImpressionStats(QUEUED, impressionsToStoreLength); + (storage.telemetry as ITelemetryCacheSync).recordImpressionStats(DEDUPED, impressionsLength - impressionsToStoreLength); } } } diff --git a/src/trackers/types.ts b/src/trackers/types.ts index a0dd2dd4..87c3279c 100644 --- a/src/trackers/types.ts +++ b/src/trackers/types.ts @@ -29,7 +29,9 @@ export type ImpressionDecorated = { }; export interface IImpressionsTracker { - track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes): void + start(): void; + stop(): void; + track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes): void; } /** Telemetry tracker */ From dbf697817125edef7b2566e58016ff09b04401bc Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 11 Mar 2026 17:56:46 -0300 Subject: [PATCH 06/14] Refactor /splitChanges DTO to make the SDK more robust in case nullable field are not defined --- src/dtos/types.ts | 10 +++++----- src/evaluator/types.ts | 4 ++-- src/evaluator/value/index.ts | 2 +- src/sync/polling/updaters/splitChangesUpdater.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/dtos/types.ts b/src/dtos/types.ts index bfd7269a..6a252c8c 100644 --- a/src/dtos/types.ts +++ b/src/dtos/types.ts @@ -41,10 +41,10 @@ export interface IDependencyMatcherData { interface ISplitMatcherBase { matcherType: string - negate: boolean - keySelector: null | { + negate?: boolean + keySelector?: null | { trafficType: string, - attribute: string | null + attribute?: string | null } userDefinedSegmentMatcherData?: null | IInSegmentMatcherData userDefinedLargeSegmentMatcherData?: null | IInLargeSegmentMatcherData @@ -207,7 +207,7 @@ export interface IExcludedSegment { export interface IRBSegment { name: string, changeNumber: number, - status: 'ACTIVE' | 'ARCHIVED', + status?: 'ACTIVE' | 'ARCHIVED', conditions?: ISplitCondition[] | null, excluded?: { keys?: string[] | null, @@ -218,7 +218,7 @@ export interface IRBSegment { export interface ISplit { name: string, changeNumber: number, - status: 'ACTIVE' | 'ARCHIVED', + status?: 'ACTIVE' | 'ARCHIVED', conditions: ISplitCondition[], prerequisites?: null | { n: string, diff --git a/src/evaluator/types.ts b/src/evaluator/types.ts index 92806ddf..42900f06 100644 --- a/src/evaluator/types.ts +++ b/src/evaluator/types.ts @@ -13,8 +13,8 @@ export interface IMatcherDto { name: string value?: string | number | boolean | string[] | IDependencyMatcherData | IBetweenMatcherData | IBetweenStringMatcherData | null - attribute: string | null - negate: boolean + attribute?: string | null + negate?: boolean dataType: string } diff --git a/src/evaluator/value/index.ts b/src/evaluator/value/index.ts index 06184aa5..b6ac9fbb 100644 --- a/src/evaluator/value/index.ts +++ b/src/evaluator/value/index.ts @@ -4,7 +4,7 @@ import { ILogger } from '../../logger/types'; import { sanitize } from './sanitize'; import { ENGINE_VALUE, ENGINE_VALUE_NO_ATTRIBUTES, ENGINE_VALUE_INVALID } from '../../logger/constants'; -function parseValue(log: ILogger, key: SplitIO.SplitKey, attributeName: string | null, attributes?: SplitIO.Attributes) { +function parseValue(log: ILogger, key: SplitIO.SplitKey, attributeName?: string | null, attributes?: SplitIO.Attributes) { let value = undefined; if (attributeName) { if (attributes) { diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index b5786bbf..0510a485 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -90,7 +90,7 @@ function matchFilters(featureFlag: ISplit, filters: ISplitFiltersValidation) { export function computeMutation(rules: Array, segments: Set, filters?: ISplitFiltersValidation): ISplitMutations { return rules.reduce((accum, ruleEntity) => { - if (ruleEntity.status === 'ACTIVE' && (!filters || matchFilters(ruleEntity as ISplit, filters))) { + if (ruleEntity.status !== 'ARCHIVED' && (!filters || matchFilters(ruleEntity as ISplit, filters))) { accum.added.push(ruleEntity); parseSegments(ruleEntity).forEach((segmentName: string) => { From 2afb814b9a32bbfb0ecbd5bb582a5eb789f3730c Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 13 Mar 2026 01:23:27 -0300 Subject: [PATCH 07/14] Refactor FallbackTreatmentsCalculator from class to factory function --- .../__tests__/fallback-calculator.spec.ts | 12 +-- .../fallbackTreatmentsCalculator/index.ts | 64 +++++------- .../__tests__/clientInputValidation.spec.ts | 2 +- .../__tests__/sdkClientMethod.spec.ts | 4 +- src/sdkClient/client.ts | 2 +- src/sdkClient/clientInputValidation.ts | 99 +++++++------------ src/sdkFactory/__tests__/index.spec.ts | 2 +- src/sdkFactory/index.ts | 2 +- 8 files changed, 73 insertions(+), 114 deletions(-) diff --git a/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts b/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts index 2461bff6..6800d085 100644 --- a/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts +++ b/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts @@ -9,8 +9,8 @@ describe('FallbackTreatmentsCalculator' , () => { 'featureA': { treatment: 'TREATMENT_A', config: '{ value: 1 }' }, }, }; - const calculator = new FallbackTreatmentsCalculator(config); - const result = calculator.resolve('featureA', 'label by flag'); + const calculator = FallbackTreatmentsCalculator(config); + const result = calculator('featureA', 'label by flag'); expect(result).toEqual({ treatment: 'TREATMENT_A', @@ -24,8 +24,8 @@ describe('FallbackTreatmentsCalculator' , () => { byFlag: {}, global: { treatment: 'GLOBAL_TREATMENT', config: '{ global: true }' }, }; - const calculator = new FallbackTreatmentsCalculator(config); - const result = calculator.resolve('missingFlag', 'label by global'); + const calculator = FallbackTreatmentsCalculator(config); + const result = calculator('missingFlag', 'label by global'); expect(result).toEqual({ treatment: 'GLOBAL_TREATMENT', @@ -38,8 +38,8 @@ describe('FallbackTreatmentsCalculator' , () => { const config: FallbackTreatmentConfiguration = { byFlag: {}, }; - const calculator = new FallbackTreatmentsCalculator(config); - const result = calculator.resolve('missingFlag', 'label by noFallback'); + const calculator = FallbackTreatmentsCalculator(config); + const result = calculator('missingFlag', 'label by noFallback'); expect(result).toEqual({ treatment: CONTROL, diff --git a/src/evaluator/fallbackTreatmentsCalculator/index.ts b/src/evaluator/fallbackTreatmentsCalculator/index.ts index e582aa97..5c2b4663 100644 --- a/src/evaluator/fallbackTreatmentsCalculator/index.ts +++ b/src/evaluator/fallbackTreatmentsCalculator/index.ts @@ -1,50 +1,32 @@ -import { FallbackTreatmentConfiguration, Treatment, TreatmentWithConfig } from '../../../types/splitio'; +import { FallbackTreatmentConfiguration, TreatmentWithConfig } from '../../../types/splitio'; import { CONTROL } from '../../utils/constants'; import { isString } from '../../utils/lang'; -export type IFallbackTreatmentsCalculator = { - resolve(flagName: string, label: string): TreatmentWithConfig & { label: string }; -} +export type IFallbackTreatmentsCalculator = (flagName: string, label?: string) => TreatmentWithConfig & { label: string }; export const FALLBACK_PREFIX = 'fallback - '; -export class FallbackTreatmentsCalculator implements IFallbackTreatmentsCalculator { - private readonly fallbacks: FallbackTreatmentConfiguration; - - constructor(fallbacks: FallbackTreatmentConfiguration = {}) { - this.fallbacks = fallbacks; - } - - resolve(flagName: string, label: string): TreatmentWithConfig & { label: string } { - const treatment = this.fallbacks.byFlag?.[flagName]; - if (treatment) { - return this.copyWithLabel(treatment, label); - } - - if (this.fallbacks.global) { - return this.copyWithLabel(this.fallbacks.global, label); - } - - return { - treatment: CONTROL, - config: null, - label, - }; - } - - private copyWithLabel(fallback: Treatment | TreatmentWithConfig, label: string): TreatmentWithConfig & { label: string } { - if (isString(fallback)) { - return { - treatment: fallback, +export function FallbackTreatmentsCalculator(fallbacks: FallbackTreatmentConfiguration = {}): IFallbackTreatmentsCalculator { + + return (flagName: string, label = '') => { + const fallback = fallbacks.byFlag?.[flagName] || fallbacks.global; + + return fallback ? + isString(fallback) ? + { + treatment: fallback, + config: null, + label: `${FALLBACK_PREFIX}${label}`, + } : + { + treatment: fallback.treatment, + config: fallback.config, + label: `${FALLBACK_PREFIX}${label}`, + } : + { + treatment: CONTROL, config: null, - label: `${FALLBACK_PREFIX}${label}`, + label, }; - } - - return { - treatment: fallback.treatment, - config: fallback.config, - label: `${FALLBACK_PREFIX}${label}`, - }; - } + }; } diff --git a/src/sdkClient/__tests__/clientInputValidation.spec.ts b/src/sdkClient/__tests__/clientInputValidation.spec.ts index 3c554c6b..e4de8f28 100644 --- a/src/sdkClient/__tests__/clientInputValidation.spec.ts +++ b/src/sdkClient/__tests__/clientInputValidation.spec.ts @@ -14,7 +14,7 @@ const settings: any = { const EVALUATION_RESULT = 'on'; const client: any = createClientMock(EVALUATION_RESULT); -const fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator = new FallbackTreatmentsCalculator(settings); +const fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator = FallbackTreatmentsCalculator(); const readinessManager: any = { isReadyFromCache: () => true, diff --git a/src/sdkClient/__tests__/sdkClientMethod.spec.ts b/src/sdkClient/__tests__/sdkClientMethod.spec.ts index bc18bd85..e3cf4807 100644 --- a/src/sdkClient/__tests__/sdkClientMethod.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethod.spec.ts @@ -19,7 +19,7 @@ const paramMocks = [ telemetryTracker: telemetryTrackerFactory(), clients: {}, impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() }, - fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator({}) + fallbackTreatmentsCalculator: FallbackTreatmentsCalculator({}) }, // SyncManager (i.e., Sync SDK) and Signal listener { @@ -31,7 +31,7 @@ const paramMocks = [ telemetryTracker: telemetryTrackerFactory(), clients: {}, impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() }, - fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator({}) + fallbackTreatmentsCalculator: FallbackTreatmentsCalculator({}) } ]; diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index 8828a557..6721b12f 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -147,7 +147,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl let { treatment, label, config = null } = evaluation; if (treatment === CONTROL) { - const fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label); + const fallbackTreatment = fallbackTreatmentsCalculator(featureFlagName, label); treatment = fallbackTreatment.treatment; label = fallbackTreatment.label; config = fallbackTreatment.config; diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index d0083feb..9ed2a722 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -59,17 +59,20 @@ export function clientInputValidationDecorator res[split] = evaluateFallBackTreatment(split, withConfig) as SplitIO.Treatment); + return res; + } + + const { treatment, config } = fallbackTreatmentsCalculator(featureFlagName as string); - if (withConfig) { - return { + return withConfig ? + { treatment, config - }; - } - - return treatment; + } : treatment; } function wrapResult(value: T): MaybeThenable { @@ -79,89 +82,65 @@ export function clientInputValidationDecorator res[split] = evaluateFallBackTreatment(split, false) as SplitIO.Treatment); - - return wrapResult(res); - } + return params.valid ? + client.getTreatments(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options) : + wrapResult(evaluateFallBackTreatment(params.nameOrNames || [], false)); } function getTreatmentsWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) { const params = validateEvaluationParams(GET_TREATMENTS_WITH_CONFIG, maybeKey, maybeFeatureFlagNames, maybeAttributes, maybeOptions); - if (params.valid) { - return client.getTreatmentsWithConfig(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options); - } else { - const res: SplitIO.TreatmentsWithConfig = {}; - if (params.nameOrNames) (params.nameOrNames as string[]).forEach(split => res[split] = evaluateFallBackTreatment(split, true) as SplitIO.TreatmentWithConfig); - - return wrapResult(res); - } + return params.valid ? + client.getTreatmentsWithConfig(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options) : + wrapResult(evaluateFallBackTreatment(params.nameOrNames || [], true)); } function getTreatmentsByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) { const params = validateEvaluationParams(GET_TREATMENTS_BY_FLAG_SETS, maybeKey, maybeFlagSets, maybeAttributes, maybeOptions); - if (params.valid) { - return client.getTreatmentsByFlagSets(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options); - } else { - return wrapResult({}); - } + return params.valid ? + client.getTreatmentsByFlagSets(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options) : + wrapResult({}); } function getTreatmentsWithConfigByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) { const params = validateEvaluationParams(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, maybeKey, maybeFlagSets, maybeAttributes, maybeOptions); - if (params.valid) { - return client.getTreatmentsWithConfigByFlagSets(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options); - } else { - return wrapResult({}); - } + return params.valid ? + client.getTreatmentsWithConfigByFlagSets(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, params.options) : + wrapResult({}); } function getTreatmentsByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) { const params = validateEvaluationParams(GET_TREATMENTS_BY_FLAG_SET, maybeKey, [maybeFlagSet], maybeAttributes, maybeOptions); - if (params.valid) { - return client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, (params.nameOrNames as string[])[0], params.attributes as SplitIO.Attributes | undefined, params.options); - } else { - return wrapResult({}); - } + return params.valid ? + client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, (params.nameOrNames as string[])[0], params.attributes as SplitIO.Attributes | undefined, params.options) : + wrapResult({}); } function getTreatmentsWithConfigByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) { const params = validateEvaluationParams(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, maybeKey, [maybeFlagSet], maybeAttributes, maybeOptions); - if (params.valid) { - return client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, (params.nameOrNames as string[])[0], params.attributes as SplitIO.Attributes | undefined, params.options); - } else { - return wrapResult({}); - } + return params.valid ? + client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, (params.nameOrNames as string[])[0], params.attributes as SplitIO.Attributes | undefined, params.options) : + wrapResult({}); } function track(maybeKey: SplitIO.SplitKey, maybeTT: string, maybeEvent: string, maybeEventValue?: number, maybeProperties?: SplitIO.Properties) { @@ -172,11 +151,9 @@ export function clientInputValidationDecorator Date: Tue, 17 Mar 2026 22:57:49 -0300 Subject: [PATCH 08/14] Update dependencies for vulnerability fixes --- .gitignore | 8 + package-lock.json | 359 ++++++++++++++++++---------------- package.json | 8 +- src/integrations/pluggable.ts | 2 +- 4 files changed, 198 insertions(+), 179 deletions(-) diff --git a/.gitignore b/.gitignore index 34d8005c..f294b4f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,11 @@ ## coverage info /coverage + +## worktrees +/.worktrees + +## agents files +/AGENTS.md +/CLAUDE.md +/.claude diff --git a/package-lock.json b/package-lock.json index 73d9628f..c182b2e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,10 +16,10 @@ "@types/ioredis": "^4.28.0", "@types/jest": "^27.0.0", "@types/lodash": "^4.14.162", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.6.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "cross-env": "^7.0.2", - "eslint": "^8.48.0", + "eslint": "^8.56.0", "eslint-plugin-compat": "^6.0.1", "eslint-plugin-import": "^2.25.3", "eslint-plugin-tsdoc": "^0.3.0", @@ -32,7 +32,7 @@ "redis-server": "1.2.2", "rimraf": "^3.0.2", "ts-jest": "^27.0.5", - "typescript": "4.4.4" + "typescript": "4.7.4" }, "peerDependencies": { "ioredis": "^4.28.0 || ^5.0.0" @@ -93,6 +93,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -561,19 +562,21 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", - "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -596,13 +599,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -618,6 +623,7 @@ "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" }, @@ -630,6 +636,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -638,23 +645,25 @@ } }, "node_modules/@eslint/js": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", - "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -675,11 +684,12 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@ioredis/commands": { "version": "1.5.0", @@ -1553,17 +1563,12 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", "dev": true, + "peer": true, "dependencies": { "jest-diff": "^27.0.0", "pretty-format": "^27.0.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1588,12 +1593,6 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -1616,34 +1615,32 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1651,43 +1648,29 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1696,16 +1679,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1713,25 +1697,26 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1740,12 +1725,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1753,22 +1739,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1785,17 +1772,19 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -1805,10 +1794,11 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1817,59 +1807,53 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -1882,6 +1866,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1904,6 +1889,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1930,10 +1916,11 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2015,6 +2002,7 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2266,6 +2254,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -2642,6 +2631,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -2909,19 +2899,22 @@ } }, "node_modules/eslint": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", - "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.48.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -3476,6 +3469,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -3489,10 +3483,11 @@ } }, "node_modules/espree/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3609,16 +3604,17 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -3629,6 +3625,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -3776,10 +3773,11 @@ } }, "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/form-data": { "version": "3.0.4", @@ -3971,6 +3969,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -4137,19 +4136,21 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4166,6 +4167,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -4609,6 +4611,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -6405,7 +6408,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -6580,6 +6584,7 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -6663,6 +6668,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -6874,6 +6880,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -6949,6 +6956,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7635,12 +7643,13 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", - "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -7785,10 +7794,12 @@ } }, "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, + "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index eaea0b0f..fdac034d 100644 --- a/package.json +++ b/package.json @@ -60,10 +60,10 @@ "@types/ioredis": "^4.28.0", "@types/jest": "^27.0.0", "@types/lodash": "^4.14.162", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.6.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "cross-env": "^7.0.2", - "eslint": "^8.48.0", + "eslint": "^8.56.0", "eslint-plugin-compat": "^6.0.1", "eslint-plugin-import": "^2.25.3", "eslint-plugin-tsdoc": "^0.3.0", @@ -76,7 +76,7 @@ "redis-server": "1.2.2", "rimraf": "^3.0.2", "ts-jest": "^27.0.5", - "typescript": "4.4.4" + "typescript": "4.7.4" }, "sideEffects": false } diff --git a/src/integrations/pluggable.ts b/src/integrations/pluggable.ts index b1b7a12f..85e74837 100644 --- a/src/integrations/pluggable.ts +++ b/src/integrations/pluggable.ts @@ -21,7 +21,7 @@ export function pluggableIntegrationsManagerFactory( // No need to check if `settings.integrations` is an array of functions. It was already validated integrations.forEach(integrationFactory => { let integration = integrationFactory(params); - if (integration && integration.queue) listeners.push(integration); + if (integration) listeners.push(integration); }); // If `listeners` is empty, not return a integration manager From d052fa1a467bc2f728b74b5116f40fffd98a7cc0 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 18 Mar 2026 12:04:34 -0300 Subject: [PATCH 09/14] Refactors to simplify code and make whitelist rule more robust in case of null or undefined field --- src/dtos/types.ts | 2 +- .../__tests__/evaluate-feature.spec.ts | 47 ++++++++-------- .../__tests__/evaluate-features.spec.ts | 53 +++++++++---------- src/evaluator/condition/engineUtils.ts | 4 +- src/evaluator/matchers/whitelist.ts | 4 +- src/logger/constants.ts | 1 - src/logger/messages/info.ts | 3 +- src/sdkClient/client.ts | 6 +-- 8 files changed, 58 insertions(+), 62 deletions(-) diff --git a/src/dtos/types.ts b/src/dtos/types.ts index 6a252c8c..2b0ee4ef 100644 --- a/src/dtos/types.ts +++ b/src/dtos/types.ts @@ -23,7 +23,7 @@ export interface IBetweenStringMatcherData { } export interface IWhitelistMatcherData { - whitelist: string[] + whitelist?: string[] | null } export interface IInSegmentMatcherData { diff --git a/src/evaluator/__tests__/evaluate-feature.spec.ts b/src/evaluator/__tests__/evaluate-feature.spec.ts index ffda4687..85db31e7 100644 --- a/src/evaluator/__tests__/evaluate-feature.spec.ts +++ b/src/evaluator/__tests__/evaluate-feature.spec.ts @@ -1,29 +1,30 @@ -// @ts-nocheck import { evaluateFeature } from '../index'; import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, SPLIT_NOT_FOUND } from '../../utils/labels'; import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; - -const splitsMock = { - regular: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - config: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': { 'on': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - killed: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on2', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - archived: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on3', 'seed': 1684183541, 'configurations': {}, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - trafficAlocation1: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on4', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - killedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - archivedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - trafficAlocation1WithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on6', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] } +import { ISplit } from '../../dtos/types'; +import { IStorageSync } from '../../storages/types'; + +const splitsMock: Record = { + regular: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + config: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': { 'on': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + killed: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on2', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + archived: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on3', 'seed': 1684183541, 'configurations': {}, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + trafficAlocation1: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on4', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + killedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + archivedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + trafficAlocation1WithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on6', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] } }; const mockStorage = { splits: { - getSplit(name) { + getSplit(name: string) { if (name === 'throw_exception') throw new Error('Error'); if (splitsMock[name]) return splitsMock[name]; return null; } } -}; +} as IStorageSync; test('EVALUATOR / should return label exception, treatment control and config null on error', async () => { const expectedOutput = { @@ -35,7 +36,7 @@ test('EVALUATOR / should return label exception, treatment control and config nu loggerMock, 'fake-key', 'throw_exception', - null, + undefined, mockStorage, ); @@ -59,7 +60,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'config', - null, + undefined, mockStorage, ); expect(evaluationWithConfig).toEqual(expectedOutput); // If the split is retrieved successfully we should get the right evaluation result, label and config. @@ -68,7 +69,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'not_existent_split', - null, + undefined, mockStorage, ); expect(evaluationNotFound).toEqual(expectedOutputControl); // If the split is not retrieved successfully because it does not exist, we should get the right evaluation result, label and config. @@ -77,7 +78,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'regular', - null, + undefined, mockStorage, ); expect(evaluation).toEqual({ ...expectedOutput, config: null }); // If the split is retrieved successfully we should get the right evaluation result, label and config. If Split has no config it should have config equal null. @@ -86,7 +87,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'killed', - null, + undefined, mockStorage, ); expect(evaluationKilled).toEqual({ ...expectedOutput, treatment: 'off', config: null, label: SPLIT_KILLED }); @@ -96,7 +97,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'archived', - null, + undefined, mockStorage, ); expect(evaluationArchived).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, config: null }); @@ -106,7 +107,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'trafficAlocation1', - null, + undefined, mockStorage, ); expect(evaluationtrafficAlocation1).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, config: null, treatment: 'off' }); @@ -116,7 +117,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'killedWithConfig', - null, + undefined, mockStorage, ); expect(evaluationKilledWithConfig).toEqual({ ...expectedOutput, treatment: 'off', label: SPLIT_KILLED }); @@ -126,7 +127,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'archivedWithConfig', - null, + undefined, mockStorage, ); expect(evaluationArchivedWithConfig).toEqual({ ...expectedOutput, treatment: 'control', label: SPLIT_ARCHIVED, config: null }); @@ -136,7 +137,7 @@ test('EVALUATOR / should return right label, treatment and config if storage ret loggerMock, 'fake-key', 'trafficAlocation1WithConfig', - null, + undefined, mockStorage, ); expect(evaluationtrafficAlocation1WithConfig).toEqual({ ...expectedOutput, label: NOT_IN_SPLIT, treatment: 'off' }); diff --git a/src/evaluator/__tests__/evaluate-features.spec.ts b/src/evaluator/__tests__/evaluate-features.spec.ts index e42fc6d3..45832bd0 100644 --- a/src/evaluator/__tests__/evaluate-features.spec.ts +++ b/src/evaluator/__tests__/evaluate-features.spec.ts @@ -1,46 +1,45 @@ -// @ts-nocheck import { evaluateFeatures, evaluateFeaturesByFlagSets } from '../index'; import { EXCEPTION, NOT_IN_SPLIT, SPLIT_ARCHIVED, SPLIT_KILLED, SPLIT_NOT_FOUND } from '../../utils/labels'; import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; import { WARN_FLAGSET_WITHOUT_FLAGS } from '../../logger/constants'; - -const splitsMock = { - regular: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - config: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': { 'on': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - killed: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on2', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - archived: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on3', 'seed': 1684183541, 'configurations': {}, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - trafficAlocation1: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on4', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - killedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - archivedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, - trafficAlocation1WithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on6', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': '', 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': '', 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] } +import { ISplit } from '../../dtos/types'; +import { IStorageSync } from '../../storages/types'; + +const splitsMock: Record = { + regular: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + config: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on', 'seed': 1684183541, 'configurations': { 'on': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + killed: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on2', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + archived: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on3', 'seed': 1684183541, 'configurations': {}, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + trafficAlocation1: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on4', 'seed': 1684183541, 'configurations': {}, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + killedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': true, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + archivedWithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': 1667452163, 'trafficAllocation': 100, 'trafficTypeName': 'user', 'name': 'always-on5', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ARCHIVED', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] }, + trafficAlocation1WithConfig: { 'changeNumber': 1487277320548, 'trafficAllocationSeed': -1667452163, 'trafficAllocation': 1, 'trafficTypeName': 'user', 'name': 'always-on6', 'seed': 1684183541, 'configurations': { 'off': "{color:'black'}" }, 'status': 'ACTIVE', 'killed': false, 'defaultTreatment': 'off', 'conditions': [{ 'conditionType': 'ROLLOUT', 'matcherGroup': { 'combiner': 'AND', 'matchers': [{ 'keySelector': { 'trafficType': 'user', 'attribute': '' }, 'matcherType': 'ALL_KEYS', 'negate': false, 'userDefinedSegmentMatcherData': { 'segmentName': '' }, 'unaryNumericMatcherData': { 'dataType': null, 'value': 0 }, 'whitelistMatcherData': { 'whitelist': null }, 'betweenMatcherData': { 'dataType': null, 'start': 0, 'end': 0 } }] }, 'partitions': [{ 'treatment': 'on', 'size': 100 }, { 'treatment': 'off', 'size': 0 }], 'label': 'in segment all' }] } }; -const flagSetsMock = { +const flagSetsMock: Record> = { reg_and_config: new Set(['regular', 'config']), arch_and_killed: new Set(['killed', 'archived']), }; const mockStorage = { splits: { - getSplit(name) { + getSplit(name: string) { if (name === 'throw_exception') throw new Error('Error'); if (splitsMock[name]) return splitsMock[name]; return null; }, - getSplits(names) { - const splits = {}; - names.forEach(name => { - splits[name] = this.getSplit(name); - }); - - return splits; + getSplits(names: string[]) { + return names.reduce((acc, name) => { + acc[name] = this.getSplit(name); + return acc; + }, {} as Record); }, - getNamesByFlagSets(flagSets) { + getNamesByFlagSets(flagSets: string[]) { return flagSets.map(flagset => flagSetsMock[flagset] || new Set()); } } -}; +} as IStorageSync; test('EVALUATOR - Multiple evaluations at once / should return label exception, treatment control and config null on error', async () => { const expectedOutput = { @@ -56,7 +55,7 @@ test('EVALUATOR - Multiple evaluations at once / should return label exception, loggerMock, 'fake-key', ['throw_exception'], - null, + undefined, mockStorage, ); @@ -80,7 +79,7 @@ test('EVALUATOR - Multiple evaluations at once / should return right labels, tre loggerMock, 'fake-key', ['config', 'not_existent_split', 'regular', 'killed', 'archived', 'trafficAlocation1', 'killedWithConfig', 'archivedWithConfig', 'trafficAlocation1WithConfig'], - null, + undefined, mockStorage, ); // assert evaluationWithConfig @@ -132,7 +131,7 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => { loggerMock, 'fake-key', flagSets, - null, + undefined, storage, 'method-name' ); @@ -191,9 +190,9 @@ describe('EVALUATOR - Multiple evaluations at once by flag sets', () => { // Should support async storage too expect(await getResultsByFlagsets(['inexistent_set1', 'inexistent_set2'], { splits: { - getNamesByFlagSets(flagSets) { return Promise.resolve(flagSets.map(flagset => flagSetsMock[flagset] || new Set())); } + getNamesByFlagSets(flagSets: string[]) { return Promise.resolve(flagSets.map(flagset => flagSetsMock[flagset] || new Set())); } } - })).toEqual({}); + } as unknown as IStorageSync)).toEqual({}); expect(loggerMock.warn.mock.calls).toEqual([ [WARN_FLAGSET_WITHOUT_FLAGS, ['method-name', 'inexistent_set1']], [WARN_FLAGSET_WITHOUT_FLAGS, ['method-name', 'inexistent_set2']], diff --git a/src/evaluator/condition/engineUtils.ts b/src/evaluator/condition/engineUtils.ts index 398ea6cc..fc0bf8b9 100644 --- a/src/evaluator/condition/engineUtils.ts +++ b/src/evaluator/condition/engineUtils.ts @@ -18,10 +18,10 @@ export function getTreatment(log: ILogger, key: string, seed: number | undefined /** * Evaluates the traffic allocation to see if we should apply rollout conditions or not. */ -export function shouldApplyRollout(trafficAllocation: number, key: string, trafficAllocationSeed: number) { +export function shouldApplyRollout(trafficAllocation: number, bucketingKey: string, trafficAllocationSeed: number) { // For rollout, if traffic allocation for splits is 100%, we don't need to filter it because everything should evaluate the rollout. if (trafficAllocation < 100) { - const _bucket = bucket(key, trafficAllocationSeed); + const _bucket = bucket(bucketingKey, trafficAllocationSeed); if (_bucket > trafficAllocation) { return false; diff --git a/src/evaluator/matchers/whitelist.ts b/src/evaluator/matchers/whitelist.ts index 309b1540..2bcb72bf 100644 --- a/src/evaluator/matchers/whitelist.ts +++ b/src/evaluator/matchers/whitelist.ts @@ -1,5 +1,5 @@ -export function whitelistMatcherContext(ruleAttr: string[]) { - const whitelistSet = new Set(ruleAttr); +export function whitelistMatcherContext(ruleAttr?: string[] | null) { + const whitelistSet = new Set(ruleAttr || []); return function whitelistMatcher(runtimeAttr: string): boolean { const isInWhitelist = whitelistSet.has(runtimeAttr); diff --git a/src/logger/constants.ts b/src/logger/constants.ts index ca331f82..0a541f95 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -32,7 +32,6 @@ export const ENGINE_DEFAULT = 41; export const CLIENT_READY_FROM_CACHE = 100; export const CLIENT_READY = 101; -export const IMPRESSION = 102; export const IMPRESSION_QUEUEING = 103; export const NEW_SHARED_CLIENT = 104; export const NEW_FACTORY = 105; diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index 1e9b5f0d..f8e230ac 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -8,8 +8,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.CLIENT_READY_FROM_CACHE, READY_MSG + ' from cache'], [c.CLIENT_READY, READY_MSG], // SDK - [c.IMPRESSION, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Feature flag: %s. Key: %s. Evaluation: %s. Label: %s'], - [c.IMPRESSION_QUEUEING, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Queueing corresponding impression.'], + [c.IMPRESSION_QUEUEING, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Queueing impression. Feature flag: %s. Key: %s. Evaluation: %s. Label: %s'], [c.NEW_SHARED_CLIENT, 'New shared client instance created.'], [c.NEW_FACTORY, 'New Split SDK instance created. %s'], [c.EVENTS_TRACKER_SUCCESS, c.LOG_PREFIX_EVENTS_TRACKER + 'Successfully queued %s'], diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index 6721b12f..6eded6c3 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -7,7 +7,7 @@ import { SDK_NOT_READY } from '../utils/labels'; import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, TRACK_FN_LABEL } from '../utils/constants'; import { IEvaluationResult } from '../evaluator/types'; import SplitIO from '../../types/splitio'; -import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants'; +import { IMPRESSION_QUEUEING } from '../logger/constants'; import { ISdkFactoryContext } from '../sdkFactory/types'; import { isConsumerMode } from '../utils/settingsValidation/mode'; import { Method } from '../sync/submitters/types'; @@ -153,10 +153,8 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl config = fallbackTreatment.config; } - log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]); - if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) { - log.info(IMPRESSION_QUEUEING); + log.info(IMPRESSION_QUEUEING, [featureFlagName, matchingKey, treatment, label]); queue.push({ imp: { feature: featureFlagName, From cc3f145709c2f9dc5aea586ff5cd0e291dbb675e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 18 Mar 2026 16:02:05 -0300 Subject: [PATCH 10/14] Update CHANGES.txt to reference related issue #471 --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 079f5528..47660612 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 2.12.0 (February 24, 2026) - - Added support for ioredis v5. + - Added support for ioredis v5 (Related to issue https://github.com/splitio/javascript-commons/issues/471). 2.11.0 (January 28, 2026) - Added functionality to provide metadata alongside SDK update and READY events. Read more in our docs. From f037f2a06235ccf6a3da5e8900a4694697528b70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:35:28 +0000 Subject: [PATCH 11/14] Bump picomatch from 2.3.1 to 2.3.2 Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index c182b2e6..1e048b49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -1563,7 +1562,6 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", "dev": true, - "peer": true, "dependencies": { "jest-diff": "^27.0.0", "pretty-format": "^27.0.0" @@ -1654,7 +1652,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -1866,7 +1863,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2254,7 +2250,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -2905,7 +2900,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4611,7 +4605,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -6668,7 +6661,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -6968,10 +6960,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -7799,7 +7792,6 @@ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From dc179e5e2ff3d169e24b6c7f584c98e78c383293 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:38:07 +0000 Subject: [PATCH 12/14] Bump lodash from 4.17.23 to 4.18.1 Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e048b49..4d1a6271 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6472,9 +6472,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, From eb99cbd54ed026a3e79fde5d2c599870e1463abf Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 28 Apr 2026 16:26:40 -0300 Subject: [PATCH 13/14] Handle null values in /splitChanges response for sets, ff.d, and rbs.d fields AI-Session-Id: 616e2b0b-7059-4a31-b636-080938576336 AI-Tool: claude-code AI-Model: unknown --- CHANGES.txt | 3 +++ package-lock.json | 26 +++++++++++++------ package.json | 2 +- src/dtos/types.ts | 8 +++--- .../inLocalStorage/SplitsCacheInLocal.ts | 2 +- src/storages/inMemory/SplitsCacheInMemory.ts | 2 +- src/storages/inRedis/SplitsCacheInRedis.ts | 2 +- .../pluggable/SplitsCachePluggable.ts | 2 +- src/storages/setRolloutPlan.ts | 4 +-- .../polling/updaters/splitChangesUpdater.ts | 4 +-- src/utils/lang/__tests__/sets.spec.ts | 2 ++ src/utils/lang/sets.ts | 6 +++-- 12 files changed, 40 insertions(+), 23 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 47660612..25b18eb8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.12.1 (April 28, 2026) + - Updated internal modules to handle `null` values from `/splitChanges` response. + 2.12.0 (February 24, 2026) - Added support for ioredis v5 (Related to issue https://github.com/splitio/javascript-commons/issues/471). diff --git a/package-lock.json b/package-lock.json index 4d1a6271..042598a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.12.0", + "version": "2.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.12.0", + "version": "2.12.1", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -93,6 +93,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -1562,6 +1563,7 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", "dev": true, + "peer": true, "dependencies": { "jest-diff": "^27.0.0", "pretty-format": "^27.0.0" @@ -1652,6 +1654,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -1765,9 +1768,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -1863,6 +1866,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2204,10 +2208,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2250,6 +2255,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -2900,6 +2906,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4605,6 +4612,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -6661,6 +6669,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -7792,6 +7801,7 @@ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index fdac034d..074c7298 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.12.0", + "version": "2.12.1", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/dtos/types.ts b/src/dtos/types.ts index 2b0ee4ef..0887c6ca 100644 --- a/src/dtos/types.ts +++ b/src/dtos/types.ts @@ -232,8 +232,8 @@ export interface ISplit { trafficAllocationSeed?: number configurations?: { [treatmentName: string]: string - }, - sets?: string[], + } | null, + sets?: string[] | null, impressionsDisabled?: boolean } @@ -245,12 +245,12 @@ export interface ISplitChangesResponse { ff?: { t: number, s?: number, - d: ISplit[] + d?: ISplit[] | null, }, rbs?: { t: number, s?: number, - d: IRBSegment[] + d?: IRBSegment[] | null, } } diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 30945684..19154070 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -205,7 +205,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { }); } - private removeFromFlagSets(featureFlagName: string, flagSets?: string[]) { + private removeFromFlagSets(featureFlagName: string, flagSets?: string[] | null) { if (!flagSets) return; flagSets.forEach(flagSet => { diff --git a/src/storages/inMemory/SplitsCacheInMemory.ts b/src/storages/inMemory/SplitsCacheInMemory.ts index 461d15e6..71254b93 100644 --- a/src/storages/inMemory/SplitsCacheInMemory.ts +++ b/src/storages/inMemory/SplitsCacheInMemory.ts @@ -114,7 +114,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync { }); } - private removeFromFlagSets(featureFlagName: string, flagSets: string[] | undefined) { + private removeFromFlagSets(featureFlagName: string, flagSets?: string[] | null) { if (!flagSets) return; flagSets.forEach(flagSet => { this.removeNames(flagSet, featureFlagName); diff --git a/src/storages/inRedis/SplitsCacheInRedis.ts b/src/storages/inRedis/SplitsCacheInRedis.ts index 7258a770..85b07886 100644 --- a/src/storages/inRedis/SplitsCacheInRedis.ts +++ b/src/storages/inRedis/SplitsCacheInRedis.ts @@ -59,7 +59,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync { return this.redis.incr(ttKey); } - private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) { + private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[] | null, flagSetsOfAddedFlag?: string[] | null) { const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag); let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag); diff --git a/src/storages/pluggable/SplitsCachePluggable.ts b/src/storages/pluggable/SplitsCachePluggable.ts index 9b53f3a9..556d61cd 100644 --- a/src/storages/pluggable/SplitsCachePluggable.ts +++ b/src/storages/pluggable/SplitsCachePluggable.ts @@ -43,7 +43,7 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync { return this.wrapper.incr(ttKey); } - private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) { + private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[] | null, flagSetsOfAddedFlag?: string[] | null) { const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag); let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag); diff --git a/src/storages/setRolloutPlan.ts b/src/storages/setRolloutPlan.ts index a8529231..b92a1d8a 100644 --- a/src/storages/setRolloutPlan.ts +++ b/src/storages/setRolloutPlan.ts @@ -35,12 +35,12 @@ export function setRolloutPlan(log: ILogger, rolloutPlan: RolloutPlan, storage: if (splits && ff) { splits.clear(); - splits.update(ff.d, [], ff.t); + splits.update(ff.d || [], [], ff.t); } if (rbSegments && rbs) { rbSegments.clear(); - rbSegments.update(rbs.d, [], rbs.t); + rbSegments.update(rbs.d || [], [], rbs.t); } const segmentChanges = rolloutPlan.segmentChanges; diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index 0510a485..c84675dc 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -173,7 +173,7 @@ export function splitChangesUpdaterFactory( let updatedFlags: string[] = []; let ffUpdate: MaybeThenable = false; if (splitChanges.ff) { - const { added, removed, names } = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation); + const { added, removed, names } = computeMutation(splitChanges.ff.d || [], usedSegments, splitFiltersValidation); updatedFlags = names; log.debug(SYNC_SPLITS_UPDATE, [added.length, removed.length]); ffUpdate = splits.update(added, removed, splitChanges.ff.t); @@ -181,7 +181,7 @@ export function splitChangesUpdaterFactory( let rbsUpdate: MaybeThenable = false; if (splitChanges.rbs) { - const { added, removed } = computeMutation(splitChanges.rbs.d, usedSegments); + const { added, removed } = computeMutation(splitChanges.rbs.d || [], usedSegments); log.debug(SYNC_RBS_UPDATE, [added.length, removed.length]); rbsUpdate = rbSegments.update(added, removed, splitChanges.rbs.t); } diff --git a/src/utils/lang/__tests__/sets.spec.ts b/src/utils/lang/__tests__/sets.spec.ts index 8be359eb..f1141b10 100644 --- a/src/utils/lang/__tests__/sets.spec.ts +++ b/src/utils/lang/__tests__/sets.spec.ts @@ -23,4 +23,6 @@ test('returnDifference', () => { expect(returnDifference([], [])).toEqual([]); expect(returnDifference(list, [])).toEqual(list); expect(returnDifference([], list2)).toEqual([]); + + expect(returnDifference(undefined, null)).toEqual([]); }); diff --git a/src/utils/lang/sets.ts b/src/utils/lang/sets.ts index 5015a0fc..13bcfe43 100644 --- a/src/utils/lang/sets.ts +++ b/src/utils/lang/sets.ts @@ -12,6 +12,8 @@ export function returnSetsUnion(set: Set, set2: Set): Set { return new Set(setToArray(set).concat(setToArray(set2))); } -export function returnDifference(list: T[] = [], list2: T[] = []): T[] { - return list.filter(item => list2.indexOf(item) === -1); +export function returnDifference(list1?: T[] | null, list2?: T[] | null): T[] { + list1 = list1 || []; + list2 = list2 || []; + return list1.filter(item => list2!.indexOf(item) === -1); } From 8b4f68de1b4e0fa61fb88f9686d8293c6bf5fdf3 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 28 Apr 2026 16:35:42 -0300 Subject: [PATCH 14/14] Update CHANGELOG entry --- CHANGES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 25b18eb8..0f2bb0fd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ -2.12.1 (April 28, 2026) +2.12.1 (April 30, 2026) - Updated internal modules to handle `null` values from `/splitChanges` response. + - Updated some dependencies for vulnerability fixes. 2.12.0 (February 24, 2026) - Added support for ioredis v5 (Related to issue https://github.com/splitio/javascript-commons/issues/471).