Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
82487b7
Bump minimatch from 3.1.2 to 3.1.5
dependabot[bot] Feb 28, 2026
9cb3ccf
Handle null conditions and excluded fields in IRBSegment parsing
EmilianoSanchez Mar 5, 2026
bfab496
Remove whenInit callback mechanism from trackers. It was originally a…
EmilianoSanchez Mar 5, 2026
6b495bb
Move SDK initialization logic together with destroy, since both use t…
EmilianoSanchez Mar 5, 2026
16dea9f
Refactor impressionsTracker to handle its own initialization and clea…
EmilianoSanchez Mar 10, 2026
dbf6978
Refactor /splitChanges DTO to make the SDK more robust in case nullab…
EmilianoSanchez Mar 11, 2026
880727f
Merge pull request #477 from splitio/polishing
EmilianoSanchez Mar 11, 2026
82acca2
Merge branch 'development' into refactor-init
EmilianoSanchez Mar 11, 2026
970a5a3
Merge branch 'refactor-init' into refactor-impressions-tracker
EmilianoSanchez Mar 11, 2026
2afb814
Refactor FallbackTreatmentsCalculator from class to factory function
EmilianoSanchez Mar 13, 2026
df5ec56
Merge branch 'development' into dependabot/npm_and_yarn/minimatch-3.1.5
EmilianoSanchez Mar 17, 2026
2e11bce
Merge pull request #475 from splitio/dependabot/npm_and_yarn/minimatc…
EmilianoSanchez Mar 17, 2026
5ab7f81
Merge branch 'development' into refactor-init
EmilianoSanchez Mar 18, 2026
c5b1483
Update dependencies for vulnerability fixes
EmilianoSanchez Mar 18, 2026
b102bbf
Merge pull request #478 from splitio/refactor-init
EmilianoSanchez Mar 18, 2026
0a226b7
Merge branch 'development' into refactor-impressions-tracker
EmilianoSanchez Mar 18, 2026
5121dd8
Merge pull request #479 from splitio/refactor-impressions-tracker
EmilianoSanchez Mar 18, 2026
6c85f3d
Merge branch 'development' into refactor-fallback-calculator
EmilianoSanchez Mar 18, 2026
d052fa1
Refactors to simplify code and make whitelist rule more robust in cas…
EmilianoSanchez Mar 18, 2026
cc3f145
Update CHANGES.txt to reference related issue #471
EmilianoSanchez Mar 18, 2026
b992a20
Merge pull request #480 from splitio/refactor-fallback-calculator
EmilianoSanchez Mar 19, 2026
f037f2a
Bump picomatch from 2.3.1 to 2.3.2
dependabot[bot] Mar 26, 2026
b0b1660
Merge pull request #485 from splitio/dependabot/npm_and_yarn/picomatc…
EmilianoSanchez Apr 8, 2026
dc179e5
Bump lodash from 4.17.23 to 4.18.1
dependabot[bot] Apr 8, 2026
ed063c8
Merge pull request #488 from splitio/dependabot/npm_and_yarn/lodash-4…
EmilianoSanchez Apr 13, 2026
eb99cbd
Handle null values in /splitChanges response for sets, ff.d, and rbs.…
EmilianoSanchez Apr 28, 2026
8b4f68d
Update CHANGELOG entry
EmilianoSanchez Apr 28, 2026
a516806
Merge pull request #499 from splitio/handle-null-splitchanges-response
EmilianoSanchez Apr 28, 2026
60f4a1c
Handle null values in /splitChanges response
EmilianoSanchez Apr 29, 2026
4d5c409
Polishing
EmilianoSanchez Jun 25, 2026
ab43673
Re-trigger CI
EmilianoSanchez Jun 25, 2026
4e7d8ad
Re-trigger CI
EmilianoSanchez Jun 25, 2026
c6fe211
Merge pull request #502 from splitio/handle-null-splitchanges-response
EmilianoSanchez Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@

## coverage info
/coverage

## worktrees
/.worktrees

## agents files
/AGENTS.md
/CLAUDE.md
/.claude
6 changes: 5 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
2.12.1 (June 25, 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.
- 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.
Expand Down
825 changes: 401 additions & 424 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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
}
30 changes: 15 additions & 15 deletions src/dtos/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface IBetweenStringMatcherData {
}

export interface IWhitelistMatcherData {
whitelist: string[]
whitelist?: string[] | null
}

export interface IInSegmentMatcherData {
Expand All @@ -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
Expand Down Expand Up @@ -207,18 +207,18 @@ export interface IExcludedSegment {
export interface IRBSegment {
name: string,
changeNumber: number,
status: 'ACTIVE' | 'ARCHIVED',
conditions?: ISplitCondition[],
status?: 'ACTIVE' | 'ARCHIVED',
conditions?: ISplitCondition[] | null,
excluded?: {
keys?: string[] | null,
segments?: IExcludedSegment[] | null
}
} | null
}

export interface ISplit {
name: string,
changeNumber: number,
status: 'ACTIVE' | 'ARCHIVED',
status?: 'ACTIVE' | 'ARCHIVED',
conditions: ISplitCondition[],
prerequisites?: null | {
n: string,
Expand All @@ -232,9 +232,9 @@ export interface ISplit {
trafficAllocationSeed?: number
configurations?: {
[treatmentName: string]: string
},
sets?: string[],
impressionsDisabled?: boolean
} | null,
sets?: string[] | null,
impressionsDisabled?: boolean | null
}

// Split definition used in offline mode
Expand All @@ -245,13 +245,13 @@ export interface ISplitChangesResponse {
ff?: {
t: number,
s?: number,
d: ISplit[]
},
d?: ISplit[] | null,
} | null,
rbs?: {
t: number,
s?: number,
d: IRBSegment[]
}
d?: IRBSegment[] | null,
} | null,
}

/** Interface of the parsed JSON response of `/segmentChanges/{segmentName}` */
Expand Down
47 changes: 24 additions & 23 deletions src/evaluator/__tests__/evaluate-feature.spec.ts

Large diffs are not rendered by default.

53 changes: 26 additions & 27 deletions src/evaluator/__tests__/evaluate-features.spec.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/evaluator/condition/engineUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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,
Expand Down
64 changes: 23 additions & 41 deletions src/evaluator/fallbackTreatmentsCalculator/index.ts
Original file line number Diff line number Diff line change
@@ -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}`,
};
}
};
}
4 changes: 2 additions & 2 deletions src/evaluator/matchers/whitelist.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/evaluator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion src/evaluator/value/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/integrations/pluggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/logger/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions src/logger/messages/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
6 changes: 3 additions & 3 deletions src/logger/messages/warn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export const codesWarn: [number, string][] = codesError.concat([
[c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'],
[c.WARN_CONVERTING, '%s: %s "%s" is not of type string, converting.'],
[c.WARN_TRIMMING, '%s: %s "%s" has extra whitespace, trimming.'],
[c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in the Split user interface.'],
[c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in Harness FME UI.'],
[c.WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'],
[c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding feature flag in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split user interface.'],
[c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding feature flag in this environment, make sure you\'re tracking your events to a valid traffic type defined in Harness FME UI.'],
[c.WARN_FLAGSET_NOT_CONFIGURED, '%s: you passed %s which is not part of the configured FlagSetsFilter, ignoring Flag Set.'],
// initialization / settings validation
[c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS + ': %s integration item(s) at settings is invalid. %s'],
Expand All @@ -36,5 +36,5 @@ export const codesWarn: [number, string][] = codesError.concat([
[c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing %s notification: %s'],
[c.WARN_INVALID_FLAGSET, '%s: you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
[c.WARN_LOWERCASE_FLAGSET, '%s: flag set %s should be all lowercase - converting string to lowercase.'],
[c.WARN_FLAGSET_WITHOUT_FLAGS, '%s: you passed %s flag set that does not contain cached feature flag names. Please double check what flag sets are in use in the Split user interface.'],
[c.WARN_FLAGSET_WITHOUT_FLAGS, '%s: you passed %s flag set that does not contain cached feature flag names. Please double check what flag sets are in use in Harness FME UI.'],
]);
2 changes: 1 addition & 1 deletion src/sdkClient/__tests__/clientInputValidation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions src/sdkClient/__tests__/sdkClientMethod.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const paramMocks = [
settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} },
telemetryTracker: telemetryTrackerFactory(),
clients: {},
uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() },
fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator({})
impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() },
fallbackTreatmentsCalculator: FallbackTreatmentsCalculator({})
},
// SyncManager (i.e., Sync SDK) and Signal listener
{
Expand All @@ -30,8 +30,8 @@ const paramMocks = [
settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} },
telemetryTracker: telemetryTrackerFactory(),
clients: {},
uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() },
fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator({})
impressionsTracker: { start: jest.fn(), stop: jest.fn(), track: jest.fn() },
fallbackTreatmentsCalculator: FallbackTreatmentsCalculator({})
}
];

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/sdkClient/__tests__/sdkClientMethodCS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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);
});

});
Expand Down
Loading