From 89111320ffdeddd45e1bd7a738c9e294bda60bdb Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Wed, 22 Oct 2025 16:43:38 -0300 Subject: [PATCH 1/2] [FME-10567] Add fallbackTreatmentCalculator to client --- .../__tests__/fallback-calculator.spec.ts | 106 ++++++++++++++++-- .../__tests__/fallback-sanitizer.spec.ts | 23 ++-- .../fallbackSanitizer/index.ts | 44 ++++---- .../fallbackTreatmentsCalculator/index.ts | 36 ++++-- .../__tests__/clientInputValidation.spec.ts | 5 +- .../__tests__/sdkClientMethod.spec.ts | 7 +- src/sdkClient/client.ts | 12 +- src/sdkClient/clientInputValidation.ts | 29 +++-- src/sdkClient/sdkClient.ts | 3 +- src/sdkFactory/__tests__/index.spec.ts | 2 + src/sdkFactory/index.ts | 5 +- src/sdkFactory/types.ts | 2 + types/splitio.d.ts | 17 +-- 13 files changed, 206 insertions(+), 85 deletions(-) diff --git a/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts b/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts index 786ecbe8..4a1e9d93 100644 --- a/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts +++ b/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-calculator.spec.ts @@ -1,7 +1,91 @@ import { FallbackTreatmentsCalculator } from '../'; -import type { FallbackTreatmentConfiguration } from '../../../../types/splitio'; // adjust path if needed +import type { FallbackTreatmentConfiguration } from '../../../../types/splitio'; +import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; +import { CONTROL } from '../../../utils/constants'; -describe('FallbackTreatmentsCalculator', () => { +describe('FallbackTreatmentsCalculator' , () => { + const longName = 'a'.repeat(101); + + test('logs an error if flag name is invalid - by Flag', () => { + let config: FallbackTreatmentConfiguration = { + byFlag: { + 'feature A': { treatment: 'TREATMENT_A', config: '{ value: 1 }' }, + }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[0][0]).toBe( + 'Fallback treatments - Discarded flag \'feature A\': Invalid flag name (max 100 chars, no spaces)' + ); + config = { + byFlag: { + [longName]: { treatment: 'TREATMENT_A', config: '{ value: 1 }' }, + }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[1][0]).toBe( + `Fallback treatments - Discarded flag '${longName}': Invalid flag name (max 100 chars, no spaces)` + ); + + config = { + byFlag: { + 'featureB': { treatment: longName, config: '{ value: 1 }' }, + }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[2][0]).toBe( + 'Fallback treatments - Discarded treatment for flag \'featureB\': Invalid treatment (max 100 chars and must match pattern)' + ); + + config = { + byFlag: { + // @ts-ignore + 'featureC': { config: '{ global: true }' }, + }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[3][0]).toBe( + 'Fallback treatments - Discarded treatment for flag \'featureC\': Invalid treatment (max 100 chars and must match pattern)' + ); + + config = { + byFlag: { + // @ts-ignore + 'featureC': { treatment: 'invalid treatment!', config: '{ global: true }' }, + }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[4][0]).toBe( + 'Fallback treatments - Discarded treatment for flag \'featureC\': Invalid treatment (max 100 chars and must match pattern)' + ); + }); + + test('logs an error if flag name is invalid - global', () => { + let config: FallbackTreatmentConfiguration = { + global: { treatment: longName, config: '{ value: 1 }' }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[2][0]).toBe( + 'Fallback treatments - Discarded treatment for flag \'featureB\': Invalid treatment (max 100 chars and must match pattern)' + ); + + config = { + // @ts-ignore + global: { config: '{ global: true }' }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[3][0]).toBe( + 'Fallback treatments - Discarded treatment for flag \'featureC\': Invalid treatment (max 100 chars and must match pattern)' + ); + + config = { + // @ts-ignore + global: { treatment: 'invalid treatment!', config: '{ global: true }' }, + }; + new FallbackTreatmentsCalculator(loggerMock, config); + expect(loggerMock.error.mock.calls[4][0]).toBe( + 'Fallback treatments - Discarded treatment for flag \'featureC\': Invalid treatment (max 100 chars and must match pattern)' + ); + }); test('returns specific fallback if flag exists', () => { const config: FallbackTreatmentConfiguration = { @@ -9,7 +93,7 @@ describe('FallbackTreatmentsCalculator', () => { 'featureA': { treatment: 'TREATMENT_A', config: '{ value: 1 }' }, }, }; - const calculator = new FallbackTreatmentsCalculator(config); + const calculator = new FallbackTreatmentsCalculator(loggerMock, config); const result = calculator.resolve('featureA', 'label by flag'); expect(result).toEqual({ @@ -24,7 +108,7 @@ describe('FallbackTreatmentsCalculator', () => { byFlag: {}, global: { treatment: 'GLOBAL_TREATMENT', config: '{ global: true }' }, }; - const calculator = new FallbackTreatmentsCalculator(config); + const calculator = new FallbackTreatmentsCalculator(loggerMock, config); const result = calculator.resolve('missingFlag', 'label by global'); expect(result).toEqual({ @@ -38,29 +122,29 @@ describe('FallbackTreatmentsCalculator', () => { const config: FallbackTreatmentConfiguration = { byFlag: {}, }; - const calculator = new FallbackTreatmentsCalculator(config); + const calculator = new FallbackTreatmentsCalculator(loggerMock, config); const result = calculator.resolve('missingFlag', 'label by noFallback'); expect(result).toEqual({ - treatment: 'CONTROL', + treatment: CONTROL, config: null, - label: 'fallback - label by noFallback', + label: 'label by noFallback', }); }); test('returns undefined label if no label provided', () => { const config: FallbackTreatmentConfiguration = { byFlag: { - 'featureB': { treatment: 'TREATMENT_B' }, + 'featureB': { treatment: 'TREATMENT_B', config: '{ value: 1 }' }, }, }; - const calculator = new FallbackTreatmentsCalculator(config); + const calculator = new FallbackTreatmentsCalculator(loggerMock, config); const result = calculator.resolve('featureB'); expect(result).toEqual({ treatment: 'TREATMENT_B', - config: undefined, - label: undefined, + config: '{ value: 1 }', + label: '', }); }); }); diff --git a/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-sanitizer.spec.ts b/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-sanitizer.spec.ts index 0f314e2a..aaaf106c 100644 --- a/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-sanitizer.spec.ts +++ b/src/evaluator/fallbackTreatmentsCalculator/__tests__/fallback-sanitizer.spec.ts @@ -1,5 +1,6 @@ import { FallbacksSanitizer } from '../fallbackSanitizer'; import { TreatmentWithConfig } from '../../../../types/splitio'; +import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; describe('FallbacksSanitizer', () => { const validTreatment: TreatmentWithConfig = { treatment: 'on', config: '{"color":"blue"}' }; @@ -10,7 +11,7 @@ describe('FallbacksSanitizer', () => { }); afterEach(() => { - (console.error as jest.Mock).mockRestore(); + (loggerMock.error as jest.Mock).mockRestore(); }); describe('isValidFlagName', () => { @@ -52,14 +53,14 @@ describe('FallbacksSanitizer', () => { describe('sanitizeGlobal', () => { test('returns the treatment if valid', () => { - expect(FallbacksSanitizer.sanitizeGlobal(validTreatment)).toEqual(validTreatment); - expect(console.error).not.toHaveBeenCalled(); + expect(FallbacksSanitizer.sanitizeGlobal(loggerMock, validTreatment)).toEqual(validTreatment); + expect(loggerMock.error).not.toHaveBeenCalled(); }); test('returns undefined and logs error if invalid', () => { - const result = FallbacksSanitizer.sanitizeGlobal(invalidTreatment); + const result = FallbacksSanitizer.sanitizeGlobal(loggerMock, invalidTreatment); expect(result).toBeUndefined(); - expect(console.error).toHaveBeenCalledWith( + expect(loggerMock.error).toHaveBeenCalledWith( expect.stringContaining('Fallback treatments - Discarded fallback') ); }); @@ -73,10 +74,10 @@ describe('FallbacksSanitizer', () => { bad_treatment: invalidTreatment, }; - const result = FallbacksSanitizer.sanitizeByFlag(input); + const result = FallbacksSanitizer.sanitizeByFlag(loggerMock, input); expect(result).toEqual({ valid_flag: validTreatment }); - expect(console.error).toHaveBeenCalledTimes(2); // invalid flag + bad_treatment + expect(loggerMock.error).toHaveBeenCalledTimes(2); // invalid flag + bad_treatment }); test('returns empty object if all invalid', () => { @@ -84,9 +85,9 @@ describe('FallbacksSanitizer', () => { 'invalid flag': invalidTreatment, }; - const result = FallbacksSanitizer.sanitizeByFlag(input); + const result = FallbacksSanitizer.sanitizeByFlag(loggerMock, input); expect(result).toEqual({}); - expect(console.error).toHaveBeenCalled(); + expect(loggerMock.error).toHaveBeenCalled(); }); test('returns same object if all valid', () => { @@ -95,9 +96,9 @@ describe('FallbacksSanitizer', () => { flag_two: { treatment: 'valid_2', config: null }, }; - const result = FallbacksSanitizer.sanitizeByFlag(input); + const result = FallbacksSanitizer.sanitizeByFlag(loggerMock, input); expect(result).toEqual(input); - expect(console.error).not.toHaveBeenCalled(); + expect(loggerMock.error).not.toHaveBeenCalled(); }); }); }); diff --git a/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts b/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts index 06c7fe2e..d7996bbb 100644 --- a/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts +++ b/src/evaluator/fallbackTreatmentsCalculator/fallbackSanitizer/index.ts @@ -1,4 +1,6 @@ -import { FallbackTreatment } from '../../../../types/splitio'; +import { FallbackTreatment, Treatment, TreatmentWithConfig } from '../../../../types/splitio'; +import { ILogger } from '../../../logger/types'; +import { isObject, isString } from '../../../utils/lang'; import { FallbackDiscardReason } from '../constants'; @@ -10,51 +12,43 @@ export class FallbacksSanitizer { return name.length <= 100 && !name.includes(' '); } - private static isValidTreatment(t?: FallbackTreatment): boolean { - if (!t) { - return false; - } - - if (typeof t === 'string') { - if (t.length > 100) { - return false; - } - return FallbacksSanitizer.pattern.test(t); - } + private static isValidTreatment(t?: Treatment | FallbackTreatment): boolean { + const treatment = isObject(t) ? (t as TreatmentWithConfig).treatment : t; - const { treatment } = t; - if (!treatment || treatment.length > 100) { + if (!isString(treatment) || treatment.length > 100) { return false; } return FallbacksSanitizer.pattern.test(treatment); } - static sanitizeGlobal(treatment?: FallbackTreatment): FallbackTreatment | undefined { + static sanitizeGlobal(logger: ILogger, treatment?: string | FallbackTreatment): string | FallbackTreatment | undefined { if (!this.isValidTreatment(treatment)) { - console.error( + logger.error( `Fallback treatments - Discarded fallback: ${FallbackDiscardReason.Treatment}` ); return undefined; } - return treatment!; + return treatment; } static sanitizeByFlag( - byFlagFallbacks: Record - ): Record { - const sanitizedByFlag: Record = {}; - - const entries = Object.entries(byFlagFallbacks); - entries.forEach(([flag, t]) => { + logger: ILogger, + byFlagFallbacks: Record + ): Record { + const sanitizedByFlag: Record = {}; + + const entries = Object.keys(byFlagFallbacks); + entries.forEach((flag) => { + const t = byFlagFallbacks[flag]; if (!this.isValidFlagName(flag)) { - console.error( + logger.error( `Fallback treatments - Discarded flag '${flag}': ${FallbackDiscardReason.FlagName}` ); return; } if (!this.isValidTreatment(t)) { - console.error( + logger.error( `Fallback treatments - Discarded treatment for flag '${flag}': ${FallbackDiscardReason.Treatment}` ); return; diff --git a/src/evaluator/fallbackTreatmentsCalculator/index.ts b/src/evaluator/fallbackTreatmentsCalculator/index.ts index 30ef69e2..521b9419 100644 --- a/src/evaluator/fallbackTreatmentsCalculator/index.ts +++ b/src/evaluator/fallbackTreatmentsCalculator/index.ts @@ -1,16 +1,28 @@ -import { FallbackTreatmentConfiguration, FallbackTreatment, IFallbackTreatmentsCalculator} from '../../../types/splitio'; +import { FallbackTreatmentConfiguration, FallbackTreatment } from '../../../types/splitio'; +import { FallbacksSanitizer } from './fallbackSanitizer'; +import { CONTROL } from '../../utils/constants'; +import { isString } from '../../utils/lang'; +import { ILogger } from '../../logger/types'; + +export type IFallbackTreatmentsCalculator = { + resolve(flagName: string, label?: string): FallbackTreatment & { label?: string }; +} export class FallbackTreatmentsCalculator implements IFallbackTreatmentsCalculator { private readonly labelPrefix = 'fallback - '; - private readonly control = 'CONTROL'; private readonly fallbacks: FallbackTreatmentConfiguration; - constructor(fallbacks: FallbackTreatmentConfiguration) { - this.fallbacks = fallbacks; + constructor(logger: ILogger, fallbacks?: FallbackTreatmentConfiguration) { + const sanitizedGlobal = fallbacks?.global ? FallbacksSanitizer.sanitizeGlobal(logger, fallbacks.global) : undefined; + const sanitizedByFlag = fallbacks?.byFlag ? FallbacksSanitizer.sanitizeByFlag(logger, fallbacks.byFlag) : {}; + this.fallbacks = { + global: sanitizedGlobal, + byFlag: sanitizedByFlag + }; } - resolve(flagName: string, label?: string | undefined): FallbackTreatment { - const treatment = this.fallbacks.byFlag[flagName]; + resolve(flagName: string, label?: string): FallbackTreatment & { label?: string } { + const treatment = this.fallbacks.byFlag?.[flagName]; if (treatment) { return this.copyWithLabel(treatment, label); } @@ -20,14 +32,14 @@ export class FallbackTreatmentsCalculator implements IFallbackTreatmentsCalculat } return { - treatment: this.control, + treatment: CONTROL, config: null, - label: this.resolveLabel(label), + label, }; } - private copyWithLabel(fallback: FallbackTreatment, label: string | undefined): FallbackTreatment { - if (typeof fallback === 'string') { + private copyWithLabel(fallback: string | FallbackTreatment, label?: string): FallbackTreatment & { label: string } { + if (isString(fallback)) { return { treatment: fallback, config: null, @@ -42,8 +54,8 @@ export class FallbackTreatmentsCalculator implements IFallbackTreatmentsCalculat }; } - private resolveLabel(label?: string | undefined): string | undefined { - return label ? `${this.labelPrefix}${label}` : undefined; + private resolveLabel(label?: string): string { + return label ? `${this.labelPrefix}${label}` : ''; } } diff --git a/src/sdkClient/__tests__/clientInputValidation.spec.ts b/src/sdkClient/__tests__/clientInputValidation.spec.ts index f70845f7..4a80541d 100644 --- a/src/sdkClient/__tests__/clientInputValidation.spec.ts +++ b/src/sdkClient/__tests__/clientInputValidation.spec.ts @@ -4,6 +4,7 @@ import { clientInputValidationDecorator } from '../clientInputValidation'; // Mocks import { DebugLogger } from '../../logger/browser/DebugLogger'; import { createClientMock } from './testUtils'; +import { FallbackTreatmentsCalculator, IFallbackTreatmentsCalculator } from '../../evaluator/fallbackTreatmentsCalculator'; const settings: any = { log: DebugLogger(), @@ -13,13 +14,15 @@ const settings: any = { const EVALUATION_RESULT = 'on'; const client: any = createClientMock(EVALUATION_RESULT); +const fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator = new FallbackTreatmentsCalculator(settings); + const readinessManager: any = { isReady: () => true, isDestroyed: () => false }; describe('clientInputValidationDecorator', () => { - const clientWithValidation = clientInputValidationDecorator(settings, client, readinessManager); + const clientWithValidation = clientInputValidationDecorator(settings, client, readinessManager, fallbackTreatmentsCalculator); const logSpy = jest.spyOn(console, 'log'); beforeEach(() => { diff --git a/src/sdkClient/__tests__/sdkClientMethod.spec.ts b/src/sdkClient/__tests__/sdkClientMethod.spec.ts index e2f53f83..31b62356 100644 --- a/src/sdkClient/__tests__/sdkClientMethod.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethod.spec.ts @@ -4,6 +4,7 @@ import { sdkClientMethodFactory } from '../sdkClientMethod'; import { assertClientApi } from './testUtils'; import { telemetryTrackerFactory } from '../../trackers/telemetryTracker'; import { IBasicClient } from '../../types'; +import { FallbackTreatmentsCalculator } from '../../evaluator/fallbackTreatmentsCalculator'; const errorMessage = 'Shared Client not supported by the storage mechanism. Create isolated instances instead.'; @@ -17,7 +18,8 @@ const paramMocks = [ settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} }, telemetryTracker: telemetryTrackerFactory(), clients: {}, - uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() } + uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }, + fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator(loggerMock, {}) }, // SyncManager (i.e., Sync SDK) and Signal listener { @@ -28,7 +30,8 @@ const paramMocks = [ settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} }, telemetryTracker: telemetryTrackerFactory(), clients: {}, - uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() } + uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }, + fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator(loggerMock, {}) } ]; diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index 0e526f72..2fbb7dd7 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -35,7 +35,7 @@ function stringify(options?: SplitIO.EvaluationOptions) { * Creator of base client with getTreatments and track methods. */ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | SplitIO.IAsyncClient { - const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker, telemetryTracker } = params; + const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker, telemetryTracker, fallbackTreatmentsCalculator } = params; const { log, mode } = settings; const isAsync = isConsumerMode(mode); @@ -143,9 +143,17 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const matchingKey = getMatching(key); const bucketingKey = getBucketing(key); - const { treatment, label, changeNumber, config = null, impressionsDisabled } = evaluation; + const { changeNumber, impressionsDisabled } = evaluation; + let { treatment, label, config = null } = evaluation; log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]); + if (treatment === CONTROL) { + const fallbackTreatment = fallbackTreatmentsCalculator.resolve(featureFlagName, label); + treatment = fallbackTreatment.treatment; + label = fallbackTreatment.label ? fallbackTreatment.label : label; + config = fallbackTreatment.config; + } + if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) { log.info(IMPRESSION_QUEUEING); queue.push({ diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index 40765d41..bd371a7c 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -1,4 +1,3 @@ -import { objectAssign } from '../utils/lang/objectAssign'; import { validateAttributes, validateEvent, @@ -13,19 +12,20 @@ import { validateEvaluationOptions } from '../utils/inputValidation'; import { startsWith } from '../utils/lang'; -import { CONTROL, CONTROL_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENT_WITH_CONFIG, TRACK_FN_LABEL } from '../utils/constants'; +import { GET_TREATMENT, GET_TREATMENTS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENT_WITH_CONFIG, TRACK_FN_LABEL } from '../utils/constants'; import { IReadinessManager } from '../readiness/types'; import { MaybeThenable } from '../dtos/types'; import { ISettings } from '../types'; import SplitIO from '../../types/splitio'; import { isConsumerMode } from '../utils/settingsValidation/mode'; import { validateFlagSets } from '../utils/settingsValidation/splitFilters'; +import { IFallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator'; /** * Decorator that validates the input before actually executing the client methods. * We should "guard" the client here, while not polluting the "real" implementation of those methods. */ -export function clientInputValidationDecorator(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient { +export function clientInputValidationDecorator(settings: ISettings, client: TClient, readinessManager: IReadinessManager, fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator): TClient { const { log, mode } = settings; const isAsync = isConsumerMode(mode); @@ -59,6 +59,19 @@ export function clientInputValidationDecorator(value: T): MaybeThenable { return isAsync ? Promise.resolve(value) : value; } @@ -69,7 +82,8 @@ export function clientInputValidationDecorator res[split] = CONTROL); + if (params.nameOrNames) (params.nameOrNames as string[]).forEach((split: string) => res[split] = evaluateFallBackTreatment(split, false) as SplitIO.Treatment); return wrapResult(res); } @@ -103,7 +118,7 @@ export function clientInputValidationDecorator res[split] = objectAssign({}, CONTROL_WITH_CONFIG)); + if (params.nameOrNames) (params.nameOrNames as string[]).forEach(split => res[split] = evaluateFallBackTreatment(split, true) as SplitIO.TreatmentWithConfig); return wrapResult(res); } diff --git a/src/sdkClient/sdkClient.ts b/src/sdkClient/sdkClient.ts index cc44c9f7..01fee12b 100644 --- a/src/sdkClient/sdkClient.ts +++ b/src/sdkClient/sdkClient.ts @@ -43,7 +43,8 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo clientInputValidationDecorator( settings, clientFactory(params), - sdkReadinessManager.readinessManager + sdkReadinessManager.readinessManager, + params.fallbackTreatmentsCalculator ), // Sdk destroy diff --git a/src/sdkFactory/__tests__/index.spec.ts b/src/sdkFactory/__tests__/index.spec.ts index e46296be..2165bcd4 100644 --- a/src/sdkFactory/__tests__/index.spec.ts +++ b/src/sdkFactory/__tests__/index.spec.ts @@ -3,6 +3,7 @@ import { sdkFactory } from '../index'; import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; import SplitIO from '../../../types/splitio'; import { EventEmitter } from '../../utils/MinEvents'; +import { FallbackTreatmentsCalculator } from '../../evaluator/fallbackTreatmentsCalculator'; /** Mocks */ @@ -36,6 +37,7 @@ const paramsForAsyncSDK = { platform: { EventEmitter }, + fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator(fullSettings.log) }; const SignalListenerInstanceMock = { start: jest.fn() }; diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index eba01028..5e38d5a7 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -17,6 +17,7 @@ import { DEBUG, OPTIMIZED } from '../utils/constants'; import { setRolloutPlan } from '../storages/setRolloutPlan'; import { IStorageSync } from '../storages/types'; import { getMatching } from '../utils/key'; +import { FallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator'; /** * Modular SDK factory @@ -60,6 +61,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA } }); + const fallbackTreatmentsCalculator = new FallbackTreatmentsCalculator(settings.log, settings.fallbackTreatments); + if (initialRolloutPlan) { setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key)); if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); @@ -85,7 +88,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA // 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 }; + const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, 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 25882c38..00890c8e 100644 --- a/src/sdkFactory/types.ts +++ b/src/sdkFactory/types.ts @@ -3,6 +3,7 @@ import { ISignalListener } from '../listeners/types'; import { IReadinessManager, ISdkReadinessManager } from '../readiness/types'; import type { sdkManagerFactory } from '../sdkManager'; import type { splitApiFactory } from '../services/splitApi'; +import type { IFallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator'; import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types'; import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/types'; import { ISyncManager } from '../sync/types'; @@ -51,6 +52,7 @@ export interface ISdkFactoryContext { splitApi?: ISplitApi syncManager?: ISyncManager, clients: Record, + fallbackTreatmentsCalculator: IFallbackTreatmentsCalculator } export interface ISdkFactoryContextSync extends ISdkFactoryContext { diff --git a/types/splitio.d.ts b/types/splitio.d.ts index f1ac014f..cfe069ad 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -1233,25 +1233,18 @@ declare namespace SplitIO { */ type ConsentStatus = 'GRANTED' | 'DECLINED' | 'UNKNOWN'; /** - * Fallback treatment can be either a string (treatment) or an object with treatment, config and label. + * Fallback treatment can be either a string (Treatment) or an object with treatment and config (TreatmentWithConfig). */ - type FallbackTreatment = string | { - treatment: string; - config?: string | null; - label?: string | null; - }; + type FallbackTreatment = TreatmentWithConfig; /** * Fallback treatments to be used when the SDK is not ready or the flag is not found. */ type FallbackTreatmentConfiguration = { - global?: FallbackTreatment, - byFlag: { - [key: string]: FallbackTreatment + global?: string | FallbackTreatment, + byFlag?: { + [key: string]: string | FallbackTreatment } } - type IFallbackTreatmentsCalculator = { - resolve(flagName: string, label?: string | undefined): FallbackTreatment; - } /** * Logger. Its interface details are not part of the public API. It shouldn't be used directly. */ From d9c4cff5923626d923bec79604f0cb2985d66642 Mon Sep 17 00:00:00 2001 From: ZamoraEmmanuel <87494075+ZamoraEmmanuel@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:09:57 -0300 Subject: [PATCH 2/2] Update src/sdkClient/clientInputValidation.ts Co-authored-by: Emiliano Sanchez --- src/sdkClient/clientInputValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index bd371a7c..443c31f4 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -60,7 +60,7 @@ export function clientInputValidationDecorator