From 1eeff81e5153f976290980c5e7bbcc7fe6146298 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 1 Oct 2025 11:39:56 -0300 Subject: [PATCH 01/10] Add method to retrieve client readiness status synchronously --- .../__tests__/sdkReadinessManager.spec.ts | 4 +- src/readiness/sdkReadinessManager.ts | 2 +- src/readiness/types.ts | 3 +- src/types.ts | 15 ------ types/splitio.d.ts | 52 +++++++++++++++++++ 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 9044fc72..35ee9d7a 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -51,8 +51,8 @@ describe('SDK Readiness Manager - Event emitter', () => { }); expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function. - expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function. - expect(sdkStatus.__getStatus()).toEqual({ + expect(typeof sdkStatus.getStatus).toBe('function'); // The sdkStatus exposes a .getStatus() function. + expect(sdkStatus.getStatus()).toEqual({ isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0 }); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index ee558d47..3f3de706 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -104,7 +104,7 @@ export function sdkReadinessManagerFactory( return readyPromise; }, - __getStatus() { + getStatus() { return { isReady: readinessManager.isReady(), isReadyFromCache: readinessManager.isReadyFromCache(), diff --git a/src/readiness/types.ts b/src/readiness/types.ts index df3c2603..2de99b43 100644 --- a/src/readiness/types.ts +++ b/src/readiness/types.ts @@ -1,4 +1,3 @@ -import { IStatusInterface } from '../types'; import SplitIO from '../../types/splitio'; /** Splits data emitter */ @@ -72,7 +71,7 @@ export interface IReadinessManager { export interface ISdkReadinessManager { readinessManager: IReadinessManager - sdkStatus: IStatusInterface + sdkStatus: SplitIO.IStatusInterface /** * Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally diff --git a/src/types.ts b/src/types.ts index ad3fa04c..5f6c7e39 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,21 +14,6 @@ export interface ISettings extends SplitIO.ISettings { readonly initialRolloutPlan?: RolloutPlan; } -/** - * SplitIO.IStatusInterface interface extended with private properties for internal use - */ -export interface IStatusInterface extends SplitIO.IStatusInterface { - // Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually. - __getStatus(): { - isReady: boolean; - isReadyFromCache: boolean; - isTimedout: boolean; - hasTimedout: boolean; - isDestroyed: boolean; - isOperational: boolean; - lastUpdate: number; - }; -} /** * SplitIO.IBasicClient interface extended with private properties for internal use */ diff --git a/types/splitio.d.ts b/types/splitio.d.ts index eaa490f3..364c5208 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -668,6 +668,52 @@ declare namespace SplitIO { [status in ConsentStatus]: ConsentStatus; }; } + /** + * Readiness Status interface. It represents the readiness state of an SDK client. + */ + interface ReadinessStatus { + + /** + * `isReady` indicates if the client has triggered an `SDK_READY` event and + * thus is ready to evaluate with cached data synchronized with the backend. + */ + isReady: boolean; + + /** + * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and + * thus is ready to evaluate with cached data, although the data in cache might be stale. + */ + isReadyFromCache: boolean; + + /** + * `isTimedout` indicates if the client has triggered an `SDK_READY_TIMED_OUT` event and is not ready to evaluate. + * In other words, `isTimedout` is equivalent to `hasTimedout && !isReady`. + */ + isTimedout: boolean; + + /** + * `hasTimedout` indicates if the client has ever triggered an `SDK_READY_TIMED_OUT` event. + * It's meant to keep a reference that the SDK emitted a timeout at some point, not the current state. + */ + hasTimedout: boolean; + + /** + * `isDestroyed` indicates if the client has been destroyed, i.e., `destroy` method has been called. + */ + isDestroyed: boolean; + + /** + * `isOperational` indicates if the client can evaluate feature flags. + * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. + * It's equivalent to `(isReady || isReadyFromCache) && !isDestroyed`. + */ + isOperational: boolean; + + /** + * `lastUpdate` indicates the timestamp of the most recent status event. + */ + lastUpdate: number; + } /** * Common API for entities that expose status handlers. */ @@ -676,6 +722,12 @@ declare namespace SplitIO { * Constant object containing the SDK events for you to use. */ Event: EventConsts; + /** + * Gets the readiness status. + * + * @returns The current readiness status. + */ + getStatus(): ReadinessStatus; /** * Returns a promise that resolves once the SDK has finished loading (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. From 6d6882273103fb306539775d620a91d839e3e0aa Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 8 Oct 2025 11:12:47 -0300 Subject: [PATCH 02/10] Use log level when logger is set --- CHANGES.txt | 5 ++++- package-lock.json | 4 ++-- package.json | 2 +- src/logger/index.ts | 2 -- .../logger/__tests__/index.spec.ts | 14 ++++---------- types/splitio.d.ts | 5 +---- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 711d3ca3..2b93b7fc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,8 @@ +2.7.1 (October 8, 2025) + - Bugfix - Update `debug` option to support log levels when `logger` option is used. + 2.7.0 (October 7, 2025) - - Added support for custom loggers: added `logger` configuration option and `LoggerAPI.setLogger` method to allow the SDK to use a custom logger. + - Added support for custom loggers: added `logger` configuration option and `factory.Logger.setLogger` method to allow the SDK to use a custom logger. 2.6.0 (September 18, 2025) - Added `storage.wrapper` configuration option to allow the SDK to use a custom storage wrapper for the storage type `LOCALSTORAGE`. Default value is `window.localStorage`. diff --git a/package-lock.json b/package-lock.json index 7665ca86..356a4e93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.7.0", + "version": "2.7.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.7.0", + "version": "2.7.1", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index c14387e2..30ea1784 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.7.0", + "version": "2.7.1", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/logger/index.ts b/src/logger/index.ts index 09c3c7a9..903fb2a6 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -72,8 +72,6 @@ export class Logger implements ILogger { if (logger) { if (isLogger(logger)) { this.logger = logger; - // If custom logger is set, all logs are either enabled or disabled - if (this.logLevel !== LogLevelIndexes.NONE) this.setLogLevel(LogLevels.DEBUG); return; } else { this.error('Invalid `logger` instance. It must be an object with `debug`, `info`, `warn` and `error` methods. Defaulting to `console.log`'); diff --git a/src/utils/settingsValidation/logger/__tests__/index.spec.ts b/src/utils/settingsValidation/logger/__tests__/index.spec.ts index 9b7e765a..4d667375 100644 --- a/src/utils/settingsValidation/logger/__tests__/index.spec.ts +++ b/src/utils/settingsValidation/logger/__tests__/index.spec.ts @@ -49,19 +49,13 @@ describe('logger validators', () => { }); test.each(testTargets)('returns a logger with the provided log level if `debug` property is true or a string log level', (validateLogger) => { - expect(getLoggerLogLevel(validateLogger({ debug: true }))).toBe('DEBUG'); + expect(getLoggerLogLevel(validateLogger({ debug: true, logger: loggerMock }))).toBe('DEBUG'); expect(getLoggerLogLevel(validateLogger({ debug: 'DEBUG' }))).toBe('DEBUG'); - expect(getLoggerLogLevel(validateLogger({ debug: 'INFO' }))).toBe('INFO'); + expect(getLoggerLogLevel(validateLogger({ debug: 'INFO', logger: loggerMock }))).toBe('INFO'); expect(getLoggerLogLevel(validateLogger({ debug: 'WARN' }))).toBe('WARN'); - expect(getLoggerLogLevel(validateLogger({ debug: 'ERROR' }))).toBe('ERROR'); + expect(getLoggerLogLevel(validateLogger({ debug: 'ERROR', logger: loggerMock }))).toBe('ERROR'); expect(getLoggerLogLevel(validateLogger({ debug: 'NONE' }))).toBe('NONE'); - - // When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true) - expect(getLoggerLogLevel(validateLogger({ debug: 'DEBUG', logger: loggerMock }))).toBe('DEBUG'); - expect(getLoggerLogLevel(validateLogger({ debug: 'INFO', logger: loggerMock }))).toBe('DEBUG'); - expect(getLoggerLogLevel(validateLogger({ debug: 'WARN', logger: loggerMock }))).toBe('DEBUG'); - expect(getLoggerLogLevel(validateLogger({ debug: 'ERROR', logger: loggerMock }))).toBe('DEBUG'); - expect(getLoggerLogLevel(validateLogger({ debug: 'NONE', logger: loggerMock }))).toBe('NONE'); + expect(getLoggerLogLevel(validateLogger({ debug: false }))).toBe('NONE'); expect(consoleLogSpy).not.toBeCalled(); }); diff --git a/types/splitio.d.ts b/types/splitio.d.ts index ebeba4df..4062e012 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -93,6 +93,7 @@ interface ISharedSettings { urls?: SplitIO.UrlSettings; /** * Custom logger object. If not provided, the SDK will use the default `console.log` method for all log levels. + * Set together with `debug` option to `true` or a log level string to enable logging. */ logger?: SplitIO.Logger; } @@ -145,8 +146,6 @@ interface IPluggableSharedSettings { * config.debug = ErrorLogger() * ``` * - * When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true), delegating log level control to the custom logger. - * * @defaultValue `false` */ debug?: boolean | SplitIO.LogLevel | SplitIO.ILogger; @@ -170,8 +169,6 @@ interface INonPluggableSharedSettings { * config.debug = 'WARN' * ``` * - * When combined with the `logger` option, any log level other than `NONE` (false) will be set to `DEBUG` (true), delegating log level control to the custom logger. - * * @defaultValue `false` */ debug?: boolean | SplitIO.LogLevel; From 9163c486e0356a1b76a0efdd0dcf1d4702dfd292 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 14 Oct 2025 15:42:58 -0300 Subject: [PATCH 03/10] Fix typos --- src/sync/streaming/SSEHandler/index.ts | 2 +- src/sync/submitters/telemetrySubmitter.ts | 6 +++--- src/trackers/telemetryTracker.ts | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sync/streaming/SSEHandler/index.ts b/src/sync/streaming/SSEHandler/index.ts index f7a39c8b..4de5ed9d 100644 --- a/src/sync/streaming/SSEHandler/index.ts +++ b/src/sync/streaming/SSEHandler/index.ts @@ -25,7 +25,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter, const code = error.parsedData.code; telemetryTracker.streamingEvent(ABLY_ERROR, code); - // 401 errors due to invalid or expired token (e.g., if refresh token coudn't be executed) + // 401 errors due to invalid or expired token (e.g., if refresh token couldn't be executed) if (40140 <= code && code <= 40149) return true; // Others 4XX errors (e.g., bad request from the SDK) if (40000 <= code && code <= 49999) return false; diff --git a/src/sync/submitters/telemetrySubmitter.ts b/src/sync/submitters/telemetrySubmitter.ts index 82fba1c5..74a6c82e 100644 --- a/src/sync/submitters/telemetrySubmitter.ts +++ b/src/sync/submitters/telemetrySubmitter.ts @@ -119,7 +119,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) { if (!telemetry || !now) return; // No submitter created if telemetry cache is not defined const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, readiness, sdkReadinessManager } = params; - const startTime = timer(now); + const stopTimer = timer(now); const submitter = firstPushWindowDecorator( submitterFactory( @@ -131,12 +131,12 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) { ); readiness.gate.once(SDK_READY_FROM_CACHE, () => { - telemetry.recordTimeUntilReadyFromCache(startTime()); + telemetry.recordTimeUntilReadyFromCache(stopTimer()); }); sdkReadinessManager.incInternalReadyCbCount(); readiness.gate.once(SDK_READY, () => { - telemetry.recordTimeUntilReady(startTime()); + telemetry.recordTimeUntilReady(stopTimer()); // Post config data when the SDK is ready and if the telemetry submitter was started if (submitter.isRunning()) { diff --git a/src/trackers/telemetryTracker.ts b/src/trackers/telemetryTracker.ts index 0312cc94..1a0ebc6e 100644 --- a/src/trackers/telemetryTracker.ts +++ b/src/trackers/telemetryTracker.ts @@ -11,11 +11,11 @@ export function telemetryTrackerFactory( ): ITelemetryTracker { if (telemetryCache && now) { - const startTime = timer(now); + const sessionTimer = timer(now); return { trackEval(method) { - const evalTime = timer(now); + const evalTimer = timer(now); return (label) => { switch (label) { @@ -25,20 +25,20 @@ export function telemetryTrackerFactory( case SDK_NOT_READY: // @ts-ignore ITelemetryCacheAsync doesn't implement the method if (telemetryCache.recordNonReadyUsage) telemetryCache.recordNonReadyUsage(); } - telemetryCache.recordLatency(method, evalTime()); + telemetryCache.recordLatency(method, evalTimer()); }; }, trackHttp(operation) { - const httpTime = timer(now); + const httpTimer = timer(now); return (error) => { - (telemetryCache as ITelemetryCacheSync).recordHttpLatency(operation, httpTime()); + (telemetryCache as ITelemetryCacheSync).recordHttpLatency(operation, httpTimer()); if (error && error.statusCode) (telemetryCache as ITelemetryCacheSync).recordHttpError(operation, error.statusCode); else (telemetryCache as ITelemetryCacheSync).recordSuccessfulSync(operation, Date.now()); }; }, sessionLength() { // @ts-ignore ITelemetryCacheAsync doesn't implement the method - if (telemetryCache.recordSessionLength) telemetryCache.recordSessionLength(startTime()); + if (telemetryCache.recordSessionLength) telemetryCache.recordSessionLength(sessionTimer()); }, streamingEvent(e, d) { if (e === AUTH_REJECTION) { From 1724ce89e5282c2d05fa89043ac7d83a6e8a5af2 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 14 Oct 2025 15:47:48 -0300 Subject: [PATCH 04/10] Updated SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event --- CHANGES.txt | 3 +++ src/readiness/readinessManager.ts | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2b93b7fc..fdab3d6d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.8.0 (October XX, 2025) + - Updated the SDK_READY_FROM_CACHE event to be emitted alongside the SDK_READY event if it hasn’t already been emitted. + 2.7.1 (October 8, 2025) - Bugfix - Update `debug` option to support log levels when `logger` option is used. diff --git a/src/readiness/readinessManager.ts b/src/readiness/readinessManager.ts index c69eedce..8a93d03c 100644 --- a/src/readiness/readinessManager.ts +++ b/src/readiness/readinessManager.ts @@ -3,7 +3,6 @@ import { ISettings } from '../types'; import SplitIO from '../../types/splitio'; import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants'; import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types'; -import { STORAGE_LOCALSTORAGE } from '../utils/constants'; function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISplitsEventEmitter { const splitsEventEmitter = objectAssign(new EventEmitter(), { @@ -115,7 +114,7 @@ export function readinessManagerFactory( isReady = true; try { syncLastUpdate(); - if (!isReadyFromCache && settings.storage?.type === STORAGE_LOCALSTORAGE) { + if (!isReadyFromCache) { isReadyFromCache = true; gate.emit(SDK_READY_FROM_CACHE); } From 53cc6dbb7ff6249a12e7b5384e9a24785178d20b Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 21 Oct 2025 11:52:00 -0300 Subject: [PATCH 05/10] feat: add whenReady and whenReadyFromCache methods to replace deprecated ready method --- src/readiness/sdkReadinessManager.ts | 30 ++++++++++++++++++++++++++- types/splitio.d.ts | 31 +++++++++++++++++++++------- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 3f3de706..2bda4ed3 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -9,6 +9,7 @@ import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY, CLIENT_NO const NEW_LISTENER_EVENT = 'newListener'; const REMOVE_LISTENER_EVENT = 'removeListener'; +const TIMEOUT_ERROR = new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.'); /** * SdkReadinessManager factory, which provides the public status API of SDK clients and manager: ready promise, readiness event emitter and constants (SDK_READY, etc). @@ -93,10 +94,11 @@ export function sdkReadinessManagerFactory( SDK_READY_TIMED_OUT, }, + // @TODO: remove in next major ready() { if (readinessManager.hasTimedout()) { if (!readinessManager.isReady()) { - return promiseWrapper(Promise.reject(new Error('Split SDK has emitted SDK_READY_TIMED_OUT event.')), defaultOnRejected); + return promiseWrapper(Promise.reject(TIMEOUT_ERROR), defaultOnRejected); } else { return Promise.resolve(); } @@ -104,6 +106,32 @@ export function sdkReadinessManagerFactory( return readyPromise; }, + whenReady() { + return new Promise((resolve, reject) => { + if (readinessManager.isReady()) { + resolve(); + } else if (readinessManager.hasTimedout()) { + reject(TIMEOUT_ERROR); + } else { + readinessManager.gate.once(SDK_READY, resolve); + readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR)); + } + }); + }, + + whenReadyFromCache() { + return new Promise((resolve, reject) => { + if (readinessManager.isReadyFromCache()) { + resolve(); + } else if (readinessManager.hasTimedout()) { + reject(TIMEOUT_ERROR); + } else { + readinessManager.gate.once(SDK_READY_FROM_CACHE, resolve); + readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR)); + } + }); + }, + getStatus() { return { isReady: readinessManager.isReady(), diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 4a473fd8..58aca849 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -525,19 +525,19 @@ declare namespace SplitIO { */ type EventConsts = { /** - * The ready event. + * The ready event emitted once the SDK is ready to evaluate feature flags with cache synchronized with the backend. */ SDK_READY: 'init::ready'; /** - * The ready event when fired with cached data. + * The ready event emitted once the SDK is ready to evaluate feature flags with cache that could be stale. Use SDK_READY if you want to be sure the cache is in sync with the backend. */ SDK_READY_FROM_CACHE: 'init::cache-ready'; /** - * The timeout event. + * The timeout event emitted after `startup.readyTimeout` seconds if the SDK_READY event was not emitted. */ SDK_READY_TIMED_OUT: 'init::timeout'; /** - * The update event. + * The update event emitted when the SDK cache is updated with new data from the backend. */ SDK_UPDATE: 'state::update'; }; @@ -704,7 +704,7 @@ declare namespace SplitIO { /** * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and - * thus is ready to evaluate with cached data, although the data in cache might be stale. + * thus is ready to evaluate with cached data, although the data in cache might be stale, not synchronized with the backend. */ isReadyFromCache: boolean; @@ -728,7 +728,7 @@ declare namespace SplitIO { /** * `isOperational` indicates if the client can evaluate feature flags. * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. - * It's equivalent to `(isReady || isReadyFromCache) && !isDestroyed`. + * It's equivalent to `isReadyFromCache && !isDestroyed`. */ isOperational: boolean; @@ -752,7 +752,7 @@ declare namespace SplitIO { */ getStatus(): ReadinessStatus; /** - * Returns a promise that resolves once the SDK has finished loading (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * Returns a promise that resolves once the SDK has finished synchronizing with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. * * Caveats: the method was designed to avoid an unhandled Promise rejection if the rejection case is not handled, so that `onRejected` handler is optional when using promises. @@ -767,8 +767,23 @@ declare namespace SplitIO { * ``` * * @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout. + * @deprecated Use `whenReady` instead. */ ready(): Promise; + /** + * Returns a promise that resolves once the SDK is ready for evaluations using cached data synchronized with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. + * + * @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout. + */ + whenReady(): Promise; + /** + * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). + * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. + * + * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. + */ + whenReadyFromCache(): Promise; } /** * Common definitions between clients for different environments interface. @@ -1702,7 +1717,7 @@ declare namespace SplitIO { * Wait for the SDK client to be ready before calling this method. * * ```js - * await factory.client().ready(); + * await factory.client().whenReady(); * const rolloutPlan = factory.getRolloutPlan(); * ``` * From 8b6a8d552e8381e560ed4b5e374c0a005f4891c4 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 12:21:23 -0300 Subject: [PATCH 06/10] refactor: simplify SDK readiness checks --- src/logger/constants.ts | 2 +- src/logger/messages/warn.ts | 2 +- .../__tests__/clientInputValidation.spec.ts | 2 +- src/sdkClient/client.ts | 6 ++-- src/sdkClient/clientInputValidation.ts | 4 +-- .../__tests__/index.asyncCache.spec.ts | 5 ++-- .../__tests__/index.syncCache.spec.ts | 3 +- src/sdkManager/index.ts | 8 ++--- .../__tests__/isOperational.spec.ts | 30 ++++++------------- src/utils/inputValidation/index.ts | 2 +- src/utils/inputValidation/isOperational.ts | 13 +++++--- src/utils/inputValidation/splitExistence.ts | 4 +-- 12 files changed, 38 insertions(+), 43 deletions(-) diff --git a/src/logger/constants.ts b/src/logger/constants.ts index de1ebe58..ca331f82 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -60,7 +60,7 @@ export const SUBMITTERS_PUSH_PAGE_HIDDEN = 125; export const ENGINE_VALUE_INVALID = 200; export const ENGINE_VALUE_NO_ATTRIBUTES = 201; export const CLIENT_NO_LISTENER = 202; -export const CLIENT_NOT_READY = 203; +export const CLIENT_NOT_READY_FROM_CACHE = 203; export const SYNC_MYSEGMENTS_FETCH_RETRY = 204; export const SYNC_SPLITS_FETCH_FAILS = 205; export const STREAMING_PARSING_ERROR_FAILS = 206; diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 81cfda1a..8f87babd 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -14,7 +14,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Dropping %s after retry. Reason: %s.'], [c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'], // client status - [c.CLIENT_NOT_READY, '%s: the SDK is not ready, results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'], + [c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect%s. Make sure to wait for SDK readiness before using this method.'], [c.CLIENT_NO_LISTENER, 'No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'], // input validation [c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'], diff --git a/src/sdkClient/__tests__/clientInputValidation.spec.ts b/src/sdkClient/__tests__/clientInputValidation.spec.ts index f70845f7..452949e8 100644 --- a/src/sdkClient/__tests__/clientInputValidation.spec.ts +++ b/src/sdkClient/__tests__/clientInputValidation.spec.ts @@ -14,7 +14,7 @@ const EVALUATION_RESULT = 'on'; const client: any = createClientMock(EVALUATION_RESULT); const readinessManager: any = { - isReady: () => true, + isReadyFromCache: () => true, isDestroyed: () => false }; diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index 0e526f72..2e431dc8 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -51,7 +51,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return treatment; }; - const evaluation = readinessManager.isReady() || readinessManager.isReadyFromCache() ? + const evaluation = readinessManager.isReadyFromCache() ? evaluateFeature(log, key, featureFlagName, attributes, storage) : isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected Promise.resolve(treatmentNotReady) : @@ -80,7 +80,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return treatments; }; - const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ? + const evaluations = readinessManager.isReadyFromCache() ? evaluateFeatures(log, key, featureFlagNames, attributes, storage) : isAsync ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected Promise.resolve(treatmentsNotReady(featureFlagNames)) : @@ -109,7 +109,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return treatments; }; - const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ? + const evaluations = readinessManager.isReadyFromCache() ? evaluateFeaturesByFlagSets(log, key, flagSetNames, attributes, storage, methodName) : isAsync ? Promise.resolve({}) : diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index 40765d41..b67025d7 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -9,7 +9,7 @@ import { validateSplits, validateTrafficType, validateIfNotDestroyed, - validateIfOperational, + validateIfReadyFromCache, validateEvaluationOptions } from '../utils/inputValidation'; import { startsWith } from '../utils/lang'; @@ -46,7 +46,7 @@ export function clientInputValidationDecorator true), + isReadyFromCache: jest.fn(() => true), isDestroyed: jest.fn(() => false) }, sdkStatus: jest.fn() @@ -77,7 +78,7 @@ describe('Manager with async cache', () => { const cache = new SplitsCachePluggable(loggerMock, keys, wrapperAdapter(loggerMock, {})); const manager = sdkManagerFactory({ mode: 'consumer_partial', log: loggerMock }, cache, sdkReadinessManagerMock); - expect(await manager.split('some_spplit')).toEqual(null); + expect(await manager.split('some_split')).toEqual(null); expect(await manager.splits()).toEqual([]); expect(await manager.names()).toEqual([]); @@ -98,7 +99,7 @@ describe('Manager with async cache', () => { const manager = sdkManagerFactory({ mode: 'consumer_partial', log: loggerMock }, {}, sdkReadinessManagerMock) as SplitIO.IAsyncManager; function validateManager() { - expect(manager.split('some_spplit')).resolves.toBe(null); + expect(manager.split('some_split')).resolves.toBe(null); expect(manager.splits()).resolves.toEqual([]); expect(manager.names()).resolves.toEqual([]); } diff --git a/src/sdkManager/__tests__/index.syncCache.spec.ts b/src/sdkManager/__tests__/index.syncCache.spec.ts index 391a053c..3437f008 100644 --- a/src/sdkManager/__tests__/index.syncCache.spec.ts +++ b/src/sdkManager/__tests__/index.syncCache.spec.ts @@ -9,6 +9,7 @@ import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; const sdkReadinessManagerMock = { readinessManager: { isReady: jest.fn(() => true), + isReadyFromCache: jest.fn(() => true), isDestroyed: jest.fn(() => false) }, sdkStatus: jest.fn() @@ -62,7 +63,7 @@ describe('Manager with sync cache (In Memory)', () => { sdkReadinessManagerMock.readinessManager.isDestroyed = () => true; function validateManager() { - expect(manager.split('some_spplit')).toBe(null); + expect(manager.split('some_split')).toBe(null); expect(manager.splits()).toEqual([]); expect(manager.names()).toEqual([]); } diff --git a/src/sdkManager/index.ts b/src/sdkManager/index.ts index d241b82e..5260170c 100644 --- a/src/sdkManager/index.ts +++ b/src/sdkManager/index.ts @@ -1,7 +1,7 @@ import { objectAssign } from '../utils/lang/objectAssign'; import { thenable } from '../utils/promise/thenable'; import { find } from '../utils/lang'; -import { validateSplit, validateSplitExistence, validateIfNotDestroyed, validateIfOperational } from '../utils/inputValidation'; +import { validateSplit, validateSplitExistence, validateIfOperational } from '../utils/inputValidation'; import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types'; import { ISdkReadinessManager } from '../readiness/types'; import { ISplit } from '../dtos/types'; @@ -66,7 +66,7 @@ export function sdkManagerFactory { @@ -28,37 +28,25 @@ describe('validateIfNotDestroyed', () => { }); }); -describe('validateIfOperational', () => { - - test('Should return true and log nothing if the SDK was ready.', () => { - const readinessManagerMock = { isReady: jest.fn(() => true) }; - - // @ts-ignore - expect(validateIfOperational(loggerMock, readinessManagerMock, 'test_method')).toBe(true); // It should return true if SDK was ready. - expect(readinessManagerMock.isReady).toBeCalledTimes(1); // It checks for readiness status using the context. - expect(loggerMock.warn).not.toBeCalled(); // But it should not log any warnings. - expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors. - }); +describe('validateIfReadyFromCache', () => { test('Should return true and log nothing if the SDK was ready from cache.', () => { - const readinessManagerMock = { isReady: jest.fn(() => false), isReadyFromCache: jest.fn(() => true) }; + const readinessManagerMock = { isReadyFromCache: jest.fn(() => true) }; // @ts-ignore - expect(validateIfOperational(loggerMock, readinessManagerMock, 'test_method')).toBe(true); // It should return true if SDK was ready. - expect(readinessManagerMock.isReady).toBeCalledTimes(1); // It checks for SDK_READY status. + expect(validateIfReadyFromCache(loggerMock, readinessManagerMock, 'test_method')).toBe(true); // It should return true if SDK was ready. expect(readinessManagerMock.isReadyFromCache).toBeCalledTimes(1); // It checks for SDK_READY_FROM_CACHE status. expect(loggerMock.warn).not.toBeCalled(); // But it should not log any warnings. expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors. }); - test('Should return false and log a warning if the SDK was not ready.', () => { - const readinessManagerMock = { isReady: jest.fn(() => false), isReadyFromCache: jest.fn(() => false) }; + test('Should return false and log a warning if the SDK was not ready from cache.', () => { + const readinessManagerMock = { isReadyFromCache: jest.fn(() => false) }; // @ts-ignore - expect(validateIfOperational(loggerMock, readinessManagerMock, 'test_method')).toBe(false); // It should return true if SDK was ready. - expect(readinessManagerMock.isReady).toBeCalledTimes(1); // It checks for SDK_READY status. + expect(validateIfReadyFromCache(loggerMock, readinessManagerMock, 'test_method')).toBe(false); // It should return true if SDK was ready. expect(readinessManagerMock.isReadyFromCache).toBeCalledTimes(1); // It checks for SDK_READY_FROM_CACHE status. - expect(loggerMock.warn).toBeCalledWith(CLIENT_NOT_READY, ['test_method', '']); // It should log the expected warning. + expect(loggerMock.warn).toBeCalledWith(CLIENT_NOT_READY_FROM_CACHE, ['test_method', '']); // It should log the expected warning. expect(loggerMock.error).not.toBeCalled(); // But it should not log any errors. }); }); diff --git a/src/utils/inputValidation/index.ts b/src/utils/inputValidation/index.ts index eac9777d..f6e06c5e 100644 --- a/src/utils/inputValidation/index.ts +++ b/src/utils/inputValidation/index.ts @@ -7,7 +7,7 @@ export { validateKey } from './key'; export { validateSplit } from './split'; export { validateSplits } from './splits'; export { validateTrafficType } from './trafficType'; -export { validateIfNotDestroyed, validateIfOperational } from './isOperational'; +export { validateIfNotDestroyed, validateIfReadyFromCache, validateIfOperational } from './isOperational'; export { validateSplitExistence } from './splitExistence'; export { validateTrafficTypeExistence } from './trafficTypeExistence'; export { validateEvaluationOptions } from './eventProperties'; diff --git a/src/utils/inputValidation/isOperational.ts b/src/utils/inputValidation/isOperational.ts index 3d990433..5f122926 100644 --- a/src/utils/inputValidation/isOperational.ts +++ b/src/utils/inputValidation/isOperational.ts @@ -1,4 +1,4 @@ -import { ERROR_CLIENT_DESTROYED, CLIENT_NOT_READY } from '../../logger/constants'; +import { ERROR_CLIENT_DESTROYED, CLIENT_NOT_READY_FROM_CACHE } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { IReadinessManager } from '../../readiness/types'; @@ -9,9 +9,14 @@ export function validateIfNotDestroyed(log: ILogger, readinessManager: IReadines return false; } -export function validateIfOperational(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) { - if (readinessManager.isReady() || readinessManager.isReadyFromCache()) return true; +export function validateIfReadyFromCache(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) { + if (readinessManager.isReadyFromCache()) return true; - log.warn(CLIENT_NOT_READY, [method, featureFlagNameOrNames ? ` for feature flag ${featureFlagNameOrNames.toString()}` : '']); + log.warn(CLIENT_NOT_READY_FROM_CACHE, [method, featureFlagNameOrNames ? ` for feature flag ${featureFlagNameOrNames.toString()}` : '']); return false; } + +// Operational means that the SDK is ready to evaluate (not destroyed and ready from cache) +export function validateIfOperational(log: ILogger, readinessManager: IReadinessManager, method: string, featureFlagNameOrNames?: string | string[] | false) { + return validateIfNotDestroyed(log, readinessManager, method) && validateIfReadyFromCache(log, readinessManager, method, featureFlagNameOrNames); +} diff --git a/src/utils/inputValidation/splitExistence.ts b/src/utils/inputValidation/splitExistence.ts index 2f3105f9..60ac3743 100644 --- a/src/utils/inputValidation/splitExistence.ts +++ b/src/utils/inputValidation/splitExistence.ts @@ -5,10 +5,10 @@ import { WARN_NOT_EXISTENT_SPLIT } from '../../logger/constants'; /** * This is defined here and in this format mostly because of the logger and the fact that it's considered a validation at product level. - * But it's not going to run on the input validation layer. In any case, the most compeling reason to use it as we do is to avoid going to Redis and get a split twice. + * But it's not going to run on the input validation layer. In any case, the most compelling reason to use it as we do is to avoid going to Redis and get a split twice. */ export function validateSplitExistence(log: ILogger, readinessManager: IReadinessManager, splitName: string, labelOrSplitObj: any, method: string): boolean { - if (readinessManager.isReady()) { // Only if it's ready we validate this, otherwise it may just be that the SDK is not ready yet. + if (readinessManager.isReady()) { // Only if it's ready (synced with BE) we validate this, otherwise it may just be that the SDK is still syncing if (labelOrSplitObj === SPLIT_NOT_FOUND || labelOrSplitObj == null) { log.warn(WARN_NOT_EXISTENT_SPLIT, [method, splitName]); return false; From 13eaec3e8026397d116e65a25b919aa606ef33be Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 12:40:13 -0300 Subject: [PATCH 07/10] feat: update whenReadyFromCache to return boolean indicating SDK ready state --- src/readiness/sdkReadinessManager.ts | 6 +++--- types/splitio.d.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 2bda4ed3..03afd873 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -120,13 +120,13 @@ export function sdkReadinessManagerFactory( }, whenReadyFromCache() { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (readinessManager.isReadyFromCache()) { - resolve(); + resolve(readinessManager.isReady()); } else if (readinessManager.hasTimedout()) { reject(TIMEOUT_ERROR); } else { - readinessManager.gate.once(SDK_READY_FROM_CACHE, resolve); + readinessManager.gate.once(SDK_READY_FROM_CACHE, () => resolve(readinessManager.isReady())); readinessManager.gate.once(SDK_READY_TIMED_OUT, () => reject(TIMEOUT_ERROR)); } }); diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 58aca849..f3981076 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -781,9 +781,9 @@ declare namespace SplitIO { * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. * - * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. + * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. The promise resolves with a boolean value that indicates whether the SDK is ready (synchronized with the backend) or not. */ - whenReadyFromCache(): Promise; + whenReadyFromCache(): Promise; } /** * Common definitions between clients for different environments interface. From e2179d7f2da0407ea20bc70140da21d55c414ebb Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 15:32:39 -0300 Subject: [PATCH 08/10] Polishing --- src/readiness/readinessManager.ts | 4 ++-- types/splitio.d.ts | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/readiness/readinessManager.ts b/src/readiness/readinessManager.ts index 8a93d03c..319e843d 100644 --- a/src/readiness/readinessManager.ts +++ b/src/readiness/readinessManager.ts @@ -90,7 +90,7 @@ export function readinessManagerFactory( if (!isReady && !isDestroyed) { try { syncLastUpdate(); - gate.emit(SDK_READY_FROM_CACHE); + gate.emit(SDK_READY_FROM_CACHE, isReady); } catch (e) { // throws user callback exceptions in next tick setTimeout(() => { throw e; }, 0); @@ -116,7 +116,7 @@ export function readinessManagerFactory( syncLastUpdate(); if (!isReadyFromCache) { isReadyFromCache = true; - gate.emit(SDK_READY_FROM_CACHE); + gate.emit(SDK_READY_FROM_CACHE, isReady); } gate.emit(SDK_READY); } catch (e) { diff --git a/types/splitio.d.ts b/types/splitio.d.ts index f3981076..b3884694 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -772,16 +772,17 @@ declare namespace SplitIO { ready(): Promise; /** * Returns a promise that resolves once the SDK is ready for evaluations using cached data synchronized with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). - * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. + * As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready after a timeout event, the `whenReady` method will return a resolved promise once the SDK is ready. * - * @returns A promise that resolves once the SDK is ready or rejects if the SDK has timedout. + * @returns A promise that resolves once the SDK_READY event is emitted or rejects if the SDK has timedout. */ whenReady(): Promise; /** * Returns a promise that resolves once the SDK is ready for evaluations using cached data which might not yet be synchronized with the backend (`SDK_READY_FROM_CACHE` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). - * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. + * As it's meant to provide similar flexibility than event listeners, given that the SDK might be ready from cache after a timeout event, the `whenReadyFromCache` method will return a resolved promise once the SDK is ready from cache. * - * @returns A promise that resolves once the SDK is ready from cache or rejects if the SDK has timedout. The promise resolves with a boolean value that indicates whether the SDK is ready (synchronized with the backend) or not. + * @returns A promise that resolves once the SDK_READY_FROM_CACHE event is emitted or rejects if the SDK has timedout. The promise resolves with a boolean value that + * indicates whether the SDK_READY_FROM_CACHE event was emitted together with the SDK_READY event (i.e., the SDK is ready and synchronized with the backend) or not. */ whenReadyFromCache(): Promise; } From e49de68dda535708d7c9775514f998b3560ce2f7 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 16:44:46 -0300 Subject: [PATCH 09/10] Revert "Add method to retrieve client readiness status synchronously" This reverts commit 1eeff81e5153f976290980c5e7bbcc7fe6146298. --- .../__tests__/sdkReadinessManager.spec.ts | 4 +- src/readiness/sdkReadinessManager.ts | 2 +- src/readiness/types.ts | 3 +- src/types.ts | 15 ++++++ types/splitio.d.ts | 52 ------------------- 5 files changed, 20 insertions(+), 56 deletions(-) diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 35ee9d7a..9044fc72 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -51,8 +51,8 @@ describe('SDK Readiness Manager - Event emitter', () => { }); expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function. - expect(typeof sdkStatus.getStatus).toBe('function'); // The sdkStatus exposes a .getStatus() function. - expect(sdkStatus.getStatus()).toEqual({ + expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function. + expect(sdkStatus.__getStatus()).toEqual({ isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0 }); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 03afd873..62f51571 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -132,7 +132,7 @@ export function sdkReadinessManagerFactory( }); }, - getStatus() { + __getStatus() { return { isReady: readinessManager.isReady(), isReadyFromCache: readinessManager.isReadyFromCache(), diff --git a/src/readiness/types.ts b/src/readiness/types.ts index 2de99b43..df3c2603 100644 --- a/src/readiness/types.ts +++ b/src/readiness/types.ts @@ -1,3 +1,4 @@ +import { IStatusInterface } from '../types'; import SplitIO from '../../types/splitio'; /** Splits data emitter */ @@ -71,7 +72,7 @@ export interface IReadinessManager { export interface ISdkReadinessManager { readinessManager: IReadinessManager - sdkStatus: SplitIO.IStatusInterface + sdkStatus: IStatusInterface /** * Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally diff --git a/src/types.ts b/src/types.ts index 5f6c7e39..ad3fa04c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,21 @@ export interface ISettings extends SplitIO.ISettings { readonly initialRolloutPlan?: RolloutPlan; } +/** + * SplitIO.IStatusInterface interface extended with private properties for internal use + */ +export interface IStatusInterface extends SplitIO.IStatusInterface { + // Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually. + __getStatus(): { + isReady: boolean; + isReadyFromCache: boolean; + isTimedout: boolean; + hasTimedout: boolean; + isDestroyed: boolean; + isOperational: boolean; + lastUpdate: number; + }; +} /** * SplitIO.IBasicClient interface extended with private properties for internal use */ diff --git a/types/splitio.d.ts b/types/splitio.d.ts index b3884694..0a8dfda2 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -691,52 +691,6 @@ declare namespace SplitIO { [status in ConsentStatus]: ConsentStatus; }; } - /** - * Readiness Status interface. It represents the readiness state of an SDK client. - */ - interface ReadinessStatus { - - /** - * `isReady` indicates if the client has triggered an `SDK_READY` event and - * thus is ready to evaluate with cached data synchronized with the backend. - */ - isReady: boolean; - - /** - * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and - * thus is ready to evaluate with cached data, although the data in cache might be stale, not synchronized with the backend. - */ - isReadyFromCache: boolean; - - /** - * `isTimedout` indicates if the client has triggered an `SDK_READY_TIMED_OUT` event and is not ready to evaluate. - * In other words, `isTimedout` is equivalent to `hasTimedout && !isReady`. - */ - isTimedout: boolean; - - /** - * `hasTimedout` indicates if the client has ever triggered an `SDK_READY_TIMED_OUT` event. - * It's meant to keep a reference that the SDK emitted a timeout at some point, not the current state. - */ - hasTimedout: boolean; - - /** - * `isDestroyed` indicates if the client has been destroyed, i.e., `destroy` method has been called. - */ - isDestroyed: boolean; - - /** - * `isOperational` indicates if the client can evaluate feature flags. - * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. - * It's equivalent to `isReadyFromCache && !isDestroyed`. - */ - isOperational: boolean; - - /** - * `lastUpdate` indicates the timestamp of the most recent status event. - */ - lastUpdate: number; - } /** * Common API for entities that expose status handlers. */ @@ -745,12 +699,6 @@ declare namespace SplitIO { * Constant object containing the SDK events for you to use. */ Event: EventConsts; - /** - * Gets the readiness status. - * - * @returns The current readiness status. - */ - getStatus(): ReadinessStatus; /** * Returns a promise that resolves once the SDK has finished synchronizing with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready. From 818235bcfca9817a4f73e35ca6b20d8486741c07 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 22 Oct 2025 16:48:09 -0300 Subject: [PATCH 10/10] Revert "Revert "Add method to retrieve client readiness status synchronously"" This reverts commit e49de68dda535708d7c9775514f998b3560ce2f7. --- .../__tests__/sdkReadinessManager.spec.ts | 4 +- src/readiness/sdkReadinessManager.ts | 2 +- src/readiness/types.ts | 3 +- src/types.ts | 15 ------ types/splitio.d.ts | 52 +++++++++++++++++++ 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/readiness/__tests__/sdkReadinessManager.spec.ts b/src/readiness/__tests__/sdkReadinessManager.spec.ts index 9044fc72..35ee9d7a 100644 --- a/src/readiness/__tests__/sdkReadinessManager.spec.ts +++ b/src/readiness/__tests__/sdkReadinessManager.spec.ts @@ -51,8 +51,8 @@ describe('SDK Readiness Manager - Event emitter', () => { }); expect(typeof sdkStatus.ready).toBe('function'); // The sdkStatus exposes a .ready() function. - expect(typeof sdkStatus.__getStatus).toBe('function'); // The sdkStatus exposes a .__getStatus() function. - expect(sdkStatus.__getStatus()).toEqual({ + expect(typeof sdkStatus.getStatus).toBe('function'); // The sdkStatus exposes a .getStatus() function. + expect(sdkStatus.getStatus()).toEqual({ isReady: false, isReadyFromCache: false, isTimedout: false, hasTimedout: false, isDestroyed: false, isOperational: false, lastUpdate: 0 }); diff --git a/src/readiness/sdkReadinessManager.ts b/src/readiness/sdkReadinessManager.ts index 62f51571..03afd873 100644 --- a/src/readiness/sdkReadinessManager.ts +++ b/src/readiness/sdkReadinessManager.ts @@ -132,7 +132,7 @@ export function sdkReadinessManagerFactory( }); }, - __getStatus() { + getStatus() { return { isReady: readinessManager.isReady(), isReadyFromCache: readinessManager.isReadyFromCache(), diff --git a/src/readiness/types.ts b/src/readiness/types.ts index df3c2603..2de99b43 100644 --- a/src/readiness/types.ts +++ b/src/readiness/types.ts @@ -1,4 +1,3 @@ -import { IStatusInterface } from '../types'; import SplitIO from '../../types/splitio'; /** Splits data emitter */ @@ -72,7 +71,7 @@ export interface IReadinessManager { export interface ISdkReadinessManager { readinessManager: IReadinessManager - sdkStatus: IStatusInterface + sdkStatus: SplitIO.IStatusInterface /** * Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally diff --git a/src/types.ts b/src/types.ts index ad3fa04c..5f6c7e39 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,21 +14,6 @@ export interface ISettings extends SplitIO.ISettings { readonly initialRolloutPlan?: RolloutPlan; } -/** - * SplitIO.IStatusInterface interface extended with private properties for internal use - */ -export interface IStatusInterface extends SplitIO.IStatusInterface { - // Expose status for internal purposes only. Not considered part of the public API, and might be updated eventually. - __getStatus(): { - isReady: boolean; - isReadyFromCache: boolean; - isTimedout: boolean; - hasTimedout: boolean; - isDestroyed: boolean; - isOperational: boolean; - lastUpdate: number; - }; -} /** * SplitIO.IBasicClient interface extended with private properties for internal use */ diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 0a8dfda2..b3884694 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -691,6 +691,52 @@ declare namespace SplitIO { [status in ConsentStatus]: ConsentStatus; }; } + /** + * Readiness Status interface. It represents the readiness state of an SDK client. + */ + interface ReadinessStatus { + + /** + * `isReady` indicates if the client has triggered an `SDK_READY` event and + * thus is ready to evaluate with cached data synchronized with the backend. + */ + isReady: boolean; + + /** + * `isReadyFromCache` indicates if the client has triggered an `SDK_READY_FROM_CACHE` event and + * thus is ready to evaluate with cached data, although the data in cache might be stale, not synchronized with the backend. + */ + isReadyFromCache: boolean; + + /** + * `isTimedout` indicates if the client has triggered an `SDK_READY_TIMED_OUT` event and is not ready to evaluate. + * In other words, `isTimedout` is equivalent to `hasTimedout && !isReady`. + */ + isTimedout: boolean; + + /** + * `hasTimedout` indicates if the client has ever triggered an `SDK_READY_TIMED_OUT` event. + * It's meant to keep a reference that the SDK emitted a timeout at some point, not the current state. + */ + hasTimedout: boolean; + + /** + * `isDestroyed` indicates if the client has been destroyed, i.e., `destroy` method has been called. + */ + isDestroyed: boolean; + + /** + * `isOperational` indicates if the client can evaluate feature flags. + * In this state, `getTreatment` calls will not return `CONTROL` due to the SDK being unready or destroyed. + * It's equivalent to `isReadyFromCache && !isDestroyed`. + */ + isOperational: boolean; + + /** + * `lastUpdate` indicates the timestamp of the most recent status event. + */ + lastUpdate: number; + } /** * Common API for entities that expose status handlers. */ @@ -699,6 +745,12 @@ declare namespace SplitIO { * Constant object containing the SDK events for you to use. */ Event: EventConsts; + /** + * Gets the readiness status. + * + * @returns The current readiness status. + */ + getStatus(): ReadinessStatus; /** * Returns a promise that resolves once the SDK has finished synchronizing with the backend (`SDK_READY` event emitted) or rejected if the SDK has timedout (`SDK_READY_TIMED_OUT` event emitted). * As it's meant to provide similar flexibility to the event approach, given that the SDK might be eventually ready after a timeout event, the `ready` method will return a resolved promise once the SDK is ready.