Skip to content

Commit acfbc5b

Browse files
Merge pull request #353 from splitio/data_loader_for_ssr
[FME-9871] Data loader for server-side rendering
2 parents 9ddac79 + cf45f70 commit acfbc5b

File tree

10 files changed

+41
-16
lines changed

10 files changed

+41
-16
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
2.5.0 (September 10, 2025)
2+
- Added `factory.getRolloutPlan()` method for standalone server-side SDKs, which returns the rollout plan snapshot from the storage.
3+
- Added `initialRolloutPlan` configuration option for standalone client-side SDKs, which allows preloading the SDK storage with a snapshot of the rollout plan.
4+
15
2.4.1 (June 3, 2025)
26
- Bugfix - Improved the Proxy fallback to flag spec version 1.2 to handle cases where the Proxy does not return an end-of-stream marker in 400 status code responses.
37

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-commons",
3-
"version": "2.4.1",
3+
"version": "2.5.0",
44
"description": "Split JavaScript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/sdkClient/sdkClientMethodCS.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING, L
99
import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
1010
import { ISdkFactoryContext } from '../sdkFactory/types';
1111
import { buildInstanceId } from './identity';
12+
import { setRolloutPlan } from '../storages/setRolloutPlan';
13+
import { ISegmentsCacheSync } from '../storages/types';
1214

1315
/**
1416
* Factory of client method for the client-side API variant where TT is ignored.
1517
* Therefore, clients don't have a bound TT for the track method.
1618
*/
1719
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.IBrowserClient {
18-
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log } } = params;
20+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log, initialRolloutPlan } } = params;
1921

2022
const mainClientInstance = clientCSDecorator(
2123
log,
@@ -56,6 +58,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
5658
sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
5759
});
5860

61+
if (sharedStorage && initialRolloutPlan) {
62+
setRolloutPlan(log, initialRolloutPlan, { segments: sharedStorage.segments as ISegmentsCacheSync, largeSegments: sharedStorage.largeSegments as ISegmentsCacheSync }, matchingKey);
63+
}
64+
5965
// 3 possibilities:
6066
// - Standalone mode: both syncManager and sharedSyncManager are defined
6167
// - Consumer mode: both syncManager and sharedSyncManager are undefined

src/sdkFactory/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized
1414
import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
1515
import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
1616
import { DEBUG, OPTIMIZED } from '../utils/constants';
17+
import { setRolloutPlan } from '../storages/setRolloutPlan';
18+
import { IStorageSync } from '../storages/types';
19+
import { getMatching } from '../utils/key';
1720

1821
/**
1922
* Modular SDK factory
@@ -24,7 +27,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
2427
syncManagerFactory, SignalListener, impressionsObserverFactory,
2528
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
2629
filterAdapterFactory, lazyInit } = params;
27-
const { log, sync: { impressionsMode } } = settings;
30+
const { log, sync: { impressionsMode }, initialRolloutPlan, core: { key } } = settings;
2831

2932
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
3033
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
@@ -43,7 +46,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
4346

4447
const storage = storageFactory({
4548
settings,
46-
onReadyCb: (error) => {
49+
onReadyCb(error) {
4750
if (error) {
4851
// If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
4952
readiness.timeout();
@@ -52,11 +55,16 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
5255
readiness.splits.emit(SDK_SPLITS_ARRIVED);
5356
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
5457
},
55-
onReadyFromCacheCb: () => {
58+
onReadyFromCacheCb() {
5659
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
5760
}
5861
});
59-
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
62+
63+
if (initialRolloutPlan) {
64+
setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
65+
if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
66+
}
67+
6068
const clients: Record<string, SplitIO.IBasicClient> = {};
6169
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
6270
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });

src/storages/getRolloutPlan.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import { IMembershipsResponse, IMySegmentsResponse } from '../dtos/types';
88

99
/**
1010
* Gets the rollout plan snapshot from the given synchronous storage.
11-
* If `keys` are provided, the memberships for those keys is returned, to protect segments data.
12-
* Otherwise, the segments data is returned.
1311
*/
1412
export function getRolloutPlan(log: ILogger, storage: IStorageSync, options: SplitIO.RolloutPlanOptions = {}): RolloutPlan {
1513

1614
const { keys, exposeSegments } = options;
1715
const { splits, segments, rbSegments } = storage;
1816

19-
log.debug(`storage: get feature flags${keys ? `, and memberships for keys ${keys}` : ''}${exposeSegments ? ', and segments' : ''}`);
17+
log.debug(`storage: get feature flags${keys ? `, and memberships for keys: ${keys}` : ''}${exposeSegments ? ', and segments' : ''}`);
2018

2119
return {
2220
splitChanges: {

src/storages/inLocalStorage/validateCache.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const MILLIS_IN_A_DAY = 86400000;
1717
* @returns `true` if cache should be cleared, `false` otherwise
1818
*/
1919
function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
20-
const { log } = settings;
20+
const { log, initialRolloutPlan } = settings;
2121

2222
// Check expiration
2323
const lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()) as string, 10);
@@ -41,7 +41,7 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: IS
4141
} catch (e) {
4242
log.error(LOG_PREFIX + e);
4343
}
44-
if (isThereCache) {
44+
if (isThereCache && !initialRolloutPlan) {
4545
log.info(LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache');
4646
return true;
4747
}

src/storages/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,6 @@ export interface IStorageAsync extends IStorageBase<
497497

498498
/** StorageFactory */
499499

500-
export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
501-
502500
export interface IStorageFactoryParams {
503501
settings: ISettings,
504502
/**

src/utils/settingsValidation/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ISettingsValidationParams } from './types';
77
import { ISettings } from '../../types';
88
import { validateKey } from '../inputValidation/key';
99
import { ERROR_MIN_CONFIG_PARAM, LOG_PREFIX_CLIENT_INSTANTIATION } from '../../logger/constants';
10+
import { validateRolloutPlan } from '../../storages/setRolloutPlan';
1011

1112
// Exported for telemetry
1213
export const base = {
@@ -152,6 +153,9 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
152153
// @ts-ignore, modify readonly prop
153154
if (storage) withDefaults.storage = storage(withDefaults);
154155

156+
// @ts-ignore, modify readonly prop
157+
if (withDefaults.initialRolloutPlan) withDefaults.initialRolloutPlan = validateRolloutPlan(log, withDefaults);
158+
155159
// Validate key and TT (for client-side)
156160
const maybeKey = withDefaults.core.key;
157161
if (validationParams.acceptKey) {

types/splitio.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,14 @@ declare namespace SplitIO {
15941594
/**
15951595
* Returns the current snapshot of the SDK rollout plan in cache.
15961596
*
1597-
* @param keys - Optional list of keys to generate the rollout plan snapshot with the memberships of the given keys, rather than the complete segments data.
1597+
* Wait for the SDK client to be ready before calling this method.
1598+
*
1599+
* ```js
1600+
* await factory.client().ready();
1601+
* const rolloutPlan = factory.getRolloutPlan();
1602+
* ```
1603+
*
1604+
* @param options - An object of type RolloutPlanOptions for advanced options.
15981605
* @returns The current snapshot of the SDK rollout plan.
15991606
*/
16001607
getRolloutPlan(options?: RolloutPlanOptions): RolloutPlan;

0 commit comments

Comments
 (0)