From d3bfda0a53596ee62c0809a985614dddaee744cf Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 7 May 2025 11:57:13 -0300 Subject: [PATCH 1/4] Move Redis require to module scope --- src/storages/inRedis/index.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/storages/inRedis/index.ts b/src/storages/inRedis/index.ts index e548142d..dc62ee83 100644 --- a/src/storages/inRedis/index.ts +++ b/src/storages/inRedis/index.ts @@ -17,23 +17,29 @@ export interface InRedisStorageOptions { options?: Record } +let RD: typeof RedisAdapter | undefined; + +try { + // Using `require` to prevent error when bundling or importing the SDK in a .mjs file, since ioredis is a CommonJS module. + // Redis storage is not supported with .mjs files. + RD = require('./RedisAdapter').RedisAdapter; +} catch (error) { /* empty */ } + /** * InRedis storage factory for consumer server-side SplitFactory, that uses `Ioredis` Redis client for Node.js * @see {@link https://www.npmjs.com/package/ioredis} */ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsyncFactory { - // Lazy loading to prevent error when bundling or importing the SDK in a .mjs file, since ioredis is a CommonJS module. - // Redis storage is not supported with .mjs files. - const RD = require('./RedisAdapter').RedisAdapter; - const prefix = validatePrefix(options.prefix); function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync { + if (!RD) throw new Error('Redis storage is not available. Runtime environment must support CommonJS (`require`) to import the ioredis dependency.'); + const { onReadyCb, settings, settings: { log } } = params; const metadata = metadataBuilder(settings); const keys = new KeyBuilderSS(prefix, metadata); - const redisClient: RedisAdapter = new RD(log, options.options || {}); + const redisClient = new RD(log, options.options || {}); const telemetry = new TelemetryCacheInRedis(log, keys, redisClient); const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient); const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient); From 6b11bd687963fd210d671b4c6d742d0b8a1f8ebe Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 7 May 2025 12:15:20 -0300 Subject: [PATCH 2/4] rc --- CHANGES.txt | 3 +++ package-lock.json | 4 ++-- package.json | 2 +- src/storages/inRedis/index.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3100c540..94ebc2a5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.2.1 (May 7, 2025) + - Updated Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized. + 2.2.0 (March 28, 2025) - Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. - Added two new configuration options for the SDK storage in browsers when using storage type `LOCALSTORAGE`: diff --git a/package-lock.json b/package-lock.json index 5a5d556b..0ac9c204 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.2.0", + "version": "2.2.1-rc.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.2.0", + "version": "2.2.1-rc.2", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index e7912d5c..945cf44d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.2.0", + "version": "2.2.1-rc.2", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/storages/inRedis/index.ts b/src/storages/inRedis/index.ts index dc62ee83..7d351692 100644 --- a/src/storages/inRedis/index.ts +++ b/src/storages/inRedis/index.ts @@ -34,7 +34,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy const prefix = validatePrefix(options.prefix); function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync { - if (!RD) throw new Error('Redis storage is not available. Runtime environment must support CommonJS (`require`) to import the ioredis dependency.'); + if (!RD) throw new Error('The SDK Redis storage is not available. Your runtime environment must support CommonJS (`require`) to import the ioredis dependency.'); const { onReadyCb, settings, settings: { log } } = params; const metadata = metadataBuilder(settings); From c35dc8b09a56918c4df9a3ebbf3c1170b44c7e71 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 7 May 2025 16:42:46 -0300 Subject: [PATCH 3/4] Flag the SDK as ready from cache immediately when using Redis storage --- CHANGES.txt | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/sdkFactory/index.ts | 5 ++++- src/storages/inRedis/index.ts | 5 ++++- src/storages/types.ts | 1 + 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 94ebc2a5..7c3c2bda 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 2.2.1 (May 7, 2025) - - Updated Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized. + - Updated the Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized, and allow queueing feature flag evaluations before SDK_READY event is emitted (Reverted in v1.7.0). 2.2.0 (March 28, 2025) - Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. diff --git a/package-lock.json b/package-lock.json index 0ac9c204..50a01b82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.2.1-rc.2", + "version": "2.2.1-rc.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.2.1-rc.2", + "version": "2.2.1-rc.3", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index 945cf44d..f7fa5898 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.2.1-rc.2", + "version": "2.2.1-rc.3", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index b342a6e0..bf807425 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -7,7 +7,7 @@ import SplitIO from '../../types/splitio'; import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey'; import { createLoggerAPI } from '../logger/sdkLogger'; import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants'; -import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants'; +import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants'; import { objectAssign } from '../utils/lang/objectAssign'; import { strategyDebugFactory } from '../trackers/strategy/strategyDebug'; import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized'; @@ -52,6 +52,9 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA readiness.splits.emit(SDK_SPLITS_ARRIVED); readiness.segments.emit(SDK_SEGMENTS_ARRIVED); }, + onReadyFromCacheCb: () => { + readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); + } }); // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);` const clients: Record = {}; diff --git a/src/storages/inRedis/index.ts b/src/storages/inRedis/index.ts index 7d351692..72cadf9a 100644 --- a/src/storages/inRedis/index.ts +++ b/src/storages/inRedis/index.ts @@ -36,7 +36,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync { if (!RD) throw new Error('The SDK Redis storage is not available. Your runtime environment must support CommonJS (`require`) to import the ioredis dependency.'); - const { onReadyCb, settings, settings: { log } } = params; + const { onReadyFromCacheCb, onReadyCb, settings, settings: { log } } = params; const metadata = metadataBuilder(settings); const keys = new KeyBuilderSS(prefix, metadata); const redisClient = new RD(log, options.options || {}); @@ -44,6 +44,9 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient); const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient); + // RedisAdapter queues operations before connection + onReadyFromCacheCb(); + // subscription to Redis connect event in order to emit SDK_READY event on consumer mode redisClient.on('connect', () => { onReadyCb(); diff --git a/src/storages/types.ts b/src/storages/types.ts index a0ede2f1..115aebcf 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -472,6 +472,7 @@ export interface IStorageFactoryParams { * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer. */ onReadyCb: (error?: any) => void, + onReadyFromCacheCb: () => void, } From 6f3960be764e3eab9c86e074e340b240189aece8 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 May 2025 15:05:52 -0300 Subject: [PATCH 4/4] Update changelog entry --- CHANGES.txt | 6 ++++-- src/storages/inRedis/index.ts | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7c3c2bda..028c6c22 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,7 @@ -2.2.1 (May 7, 2025) - - Updated the Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized, and allow queueing feature flag evaluations before SDK_READY event is emitted (Reverted in v1.7.0). +2.2.1 (May XX, 2025) + - Updated the Redis storage to: + - Avoid lazy require of the `ioredis` dependency when the SDK is initialized, and + - Flag the SDK as ready from cache immediately to allow queueing feature flag evaluations before SDK_READY event is emitted (Reverted in v1.7.0). 2.2.0 (March 28, 2025) - Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. diff --git a/src/storages/inRedis/index.ts b/src/storages/inRedis/index.ts index 72cadf9a..054f91ce 100644 --- a/src/storages/inRedis/index.ts +++ b/src/storages/inRedis/index.ts @@ -34,7 +34,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy const prefix = validatePrefix(options.prefix); function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync { - if (!RD) throw new Error('The SDK Redis storage is not available. Your runtime environment must support CommonJS (`require`) to import the ioredis dependency.'); + if (!RD) throw new Error('The SDK Redis storage is unavailable. Make sure your runtime environment supports CommonJS (`require`) so the `ioredis` dependency can be imported.'); const { onReadyFromCacheCb, onReadyCb, settings, settings: { log } } = params; const metadata = metadataBuilder(settings); @@ -44,10 +44,10 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient); const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient); - // RedisAdapter queues operations before connection + // RedisAdapter lets queue operations before connected onReadyFromCacheCb(); - // subscription to Redis connect event in order to emit SDK_READY event on consumer mode + // Subscription to Redis connect event in order to emit SDK_READY event on consumer mode redisClient.on('connect', () => { onReadyCb(); impressionCountsCache.start();