Skip to content

Commit c65b3d0

Browse files
refactor: call setRolloutPlan outside storage, to generalize to any storage
1 parent c20e74f commit c65b3d0

File tree

5 files changed

+61
-76
lines changed

5 files changed

+61
-76
lines changed

src/sdkClient/sdkClientMethodCS.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ 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 { isConsumerMode } from '../utils/settingsValidation/mode';
13+
import { setRolloutPlan } from '../storages/dataLoader';
14+
import { ISegmentsCacheSync } from '../storages/types';
1215

1316
/**
1417
* Factory of client method for the client-side API variant where TT is ignored.
1518
* Therefore, clients don't have a bound TT for the track method.
1619
*/
1720
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.IBrowserClient {
18-
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log } } = params;
21+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log, initialRolloutPlan, mode } } = params;
1922

2023
const mainClientInstance = clientCSDecorator(
2124
log,
@@ -56,6 +59,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
5659
sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
5760
});
5861

62+
if (sharedStorage && initialRolloutPlan && !isConsumerMode(mode)) {
63+
setRolloutPlan(log, initialRolloutPlan, { segments: sharedStorage.segments as ISegmentsCacheSync, largeSegments: sharedStorage.largeSegments as ISegmentsCacheSync }, matchingKey);
64+
}
65+
5966
// 3 possibilities:
6067
// - Standalone mode: both syncManager and sharedSyncManager are defined
6168
// - Consumer mode: both syncManager and sharedSyncManager are undefined

src/sdkFactory/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ 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/dataLoader';
18+
import { isConsumerMode } from '../utils/settingsValidation/mode';
19+
import { IStorageSync } from '../storages/types';
20+
import { getMatching } from '../utils/key';
1721

1822
/**
1923
* Modular SDK factory
@@ -24,7 +28,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
2428
syncManagerFactory, SignalListener, impressionsObserverFactory,
2529
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
2630
filterAdapterFactory, lazyInit } = params;
27-
const { log, sync: { impressionsMode } } = settings;
31+
const { log, sync: { impressionsMode }, initialRolloutPlan, mode, core: { key } } = settings;
2832

2933
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
3034
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
@@ -57,7 +61,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
5761
}
5862
});
5963

60-
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
64+
if (initialRolloutPlan && !isConsumerMode(mode)) {
65+
setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
66+
if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
67+
}
68+
6169
const clients: Record<string, SplitIO.IBasicClient> = {};
6270
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
6371
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });

src/storages/__tests__/dataLoader.spec.ts

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@ import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.
44
import { loggerMock } from '../../logger/__tests__/sdkLogger.mock';
55
import { IRBSegment, ISplit } from '../../dtos/types';
66

7-
import * as dataLoader from '../dataLoader';
7+
import { setRolloutPlan, getRolloutPlan } from '../dataLoader';
88

99
describe('getRolloutPlan & setRolloutPlan (client-side)', () => {
10-
jest.spyOn(dataLoader, 'setRolloutPlan');
11-
const onReadyFromCacheCb = jest.fn();
12-
const onReadyCb = jest.fn();
13-
1410
const otherKey = 'otherKey';
1511

1612
// @ts-expect-error Load server-side storage
@@ -41,56 +37,54 @@ describe('getRolloutPlan & setRolloutPlan (client-side)', () => {
4137
});
4238

4339
test('using preloaded data (no memberships, no segments)', () => {
44-
const rolloutPlan = dataLoader.getRolloutPlan(loggerMock, serverStorage);
40+
const rolloutPlan = getRolloutPlan(loggerMock, serverStorage);
4541

46-
// Load client-side storage with preloaded data
47-
const clientStorage = InMemoryStorageCSFactory({ settings: { ...fullSettings, initialRolloutPlan: rolloutPlan }, onReadyFromCacheCb, onReadyCb });
48-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(1);
49-
expect(onReadyFromCacheCb).toBeCalledTimes(1);
42+
// @ts-expect-error Load client-side storage with preloaded data
43+
const clientStorage = InMemoryStorageCSFactory({ settings: fullSettings });
44+
setRolloutPlan(loggerMock, rolloutPlan, clientStorage, fullSettings.core.key as string);
5045

5146
// Shared client storage
5247
const sharedClientStorage = clientStorage.shared!(otherKey);
53-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(2);
48+
setRolloutPlan(loggerMock, rolloutPlan, { segments: sharedClientStorage.segments, largeSegments: sharedClientStorage.largeSegments }, otherKey);
5449

5550
expect(clientStorage.segments.getRegisteredSegments()).toEqual([]);
5651
expect(sharedClientStorage.segments.getRegisteredSegments()).toEqual([]);
5752

5853
// Get preloaded data from client-side storage
59-
expect(dataLoader.getRolloutPlan(loggerMock, clientStorage)).toEqual(rolloutPlan);
54+
expect(getRolloutPlan(loggerMock, clientStorage)).toEqual(rolloutPlan);
6055
expect(rolloutPlan).toEqual({ ...expectedRolloutPlan, memberships: undefined, segmentChanges: undefined });
6156
});
6257

6358
test('using preloaded data with memberships', () => {
64-
const rolloutPlan = dataLoader.getRolloutPlan(loggerMock, serverStorage, { keys: [fullSettings.core.key as string, otherKey] });
59+
const rolloutPlan = getRolloutPlan(loggerMock, serverStorage, { keys: [fullSettings.core.key as string, otherKey] });
6560

66-
// Load client-side storage with preloaded data
67-
const clientStorage = InMemoryStorageCSFactory({ settings: { ...fullSettings, initialRolloutPlan: rolloutPlan }, onReadyFromCacheCb, onReadyCb });
68-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(1);
69-
expect(onReadyFromCacheCb).toBeCalledTimes(1);
61+
// @ts-expect-error Load client-side storage with preloaded data
62+
const clientStorage = InMemoryStorageCSFactory({ settings: fullSettings });
63+
setRolloutPlan(loggerMock, rolloutPlan, clientStorage, fullSettings.core.key as string);
7064

7165
// Shared client storage
7266
const sharedClientStorage = clientStorage.shared!(otherKey);
73-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(2);
67+
setRolloutPlan(loggerMock, rolloutPlan, { segments: sharedClientStorage.segments, largeSegments: sharedClientStorage.largeSegments }, otherKey);
7468

7569
expect(clientStorage.segments.getRegisteredSegments()).toEqual(['segment1']);
7670
expect(sharedClientStorage.segments.getRegisteredSegments()).toEqual(['segment1']);
7771

78-
// Get preloaded data from client-side storage
79-
expect(dataLoader.getRolloutPlan(loggerMock, clientStorage, { keys: [fullSettings.core.key as string, otherKey] })).toEqual(rolloutPlan);
80-
expect(rolloutPlan).toEqual({ ...expectedRolloutPlan, segmentChanges: undefined });
72+
// @TODO requires internal storage cache for `shared` storages
73+
// // Get preloaded data from client-side storage
74+
// expect(getRolloutPlan(loggerMock, clientStorage, { keys: [fullSettings.core.key as string, otherKey] })).toEqual(rolloutPlan);
75+
// expect(rolloutPlan).toEqual({ ...expectedRolloutPlan, segmentChanges: undefined });
8176
});
8277

8378
test('using preloaded data with segments', () => {
84-
const rolloutPlan = dataLoader.getRolloutPlan(loggerMock, serverStorage, { exposeSegments: true });
79+
const rolloutPlan = getRolloutPlan(loggerMock, serverStorage, { exposeSegments: true });
8580

86-
// Load client-side storage with preloaded data
87-
const clientStorage = InMemoryStorageCSFactory({ settings: { ...fullSettings, initialRolloutPlan: rolloutPlan }, onReadyFromCacheCb, onReadyCb });
88-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(1);
89-
expect(onReadyFromCacheCb).toBeCalledTimes(1);
81+
// @ts-expect-error Load client-side storage with preloaded data
82+
const clientStorage = InMemoryStorageCSFactory({ settings: fullSettings });
83+
setRolloutPlan(loggerMock, rolloutPlan, clientStorage, fullSettings.core.key as string);
9084

9185
// Shared client storage
9286
const sharedClientStorage = clientStorage.shared!(otherKey);
93-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(2);
87+
setRolloutPlan(loggerMock, rolloutPlan, { segments: sharedClientStorage.segments, largeSegments: sharedClientStorage.largeSegments }, otherKey);
9488

9589
expect(clientStorage.segments.getRegisteredSegments()).toEqual(['segment1']);
9690
expect(sharedClientStorage.segments.getRegisteredSegments()).toEqual(['segment1']);
@@ -99,16 +93,15 @@ describe('getRolloutPlan & setRolloutPlan (client-side)', () => {
9993
});
10094

10195
test('using preloaded data with memberships and segments', () => {
102-
const rolloutPlan = dataLoader.getRolloutPlan(loggerMock, serverStorage, { keys: [fullSettings.core.key as string], exposeSegments: true });
96+
const rolloutPlan = getRolloutPlan(loggerMock, serverStorage, { keys: [fullSettings.core.key as string], exposeSegments: true });
10397

104-
// Load client-side storage with preloaded data
105-
const clientStorage = InMemoryStorageCSFactory({ settings: { ...fullSettings, initialRolloutPlan: rolloutPlan }, onReadyFromCacheCb, onReadyCb });
106-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(1);
107-
expect(onReadyFromCacheCb).toBeCalledTimes(1);
98+
// @ts-expect-error Load client-side storage with preloaded data
99+
const clientStorage = InMemoryStorageCSFactory({ settings: fullSettings });
100+
setRolloutPlan(loggerMock, rolloutPlan, clientStorage, fullSettings.core.key as string);
108101

109102
// Shared client storage
110103
const sharedClientStorage = clientStorage.shared!(otherKey);
111-
expect(dataLoader.setRolloutPlan).toBeCalledTimes(2);
104+
setRolloutPlan(loggerMock, rolloutPlan, { segments: sharedClientStorage.segments, largeSegments: sharedClientStorage.largeSegments }, otherKey);
112105

113106
expect(clientStorage.segments.getRegisteredSegments()).toEqual(['segment1']); // main client membership is set via the rollout plan `memberships` field
114107
expect(sharedClientStorage.segments.getRegisteredSegments()).toEqual(['segment1']); // shared client membership is set via the rollout plan `segmentChanges` field

src/storages/inLocalStorage/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
2626

2727
function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
2828

29-
// Fallback to InMemoryStorage if LocalStorage API is not available or preloaded data is provided
30-
if (!isLocalStorageAvailable() || params.settings.initialRolloutPlan) {
31-
params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable or `initialRolloutPlan` is provided. Falling back to default MEMORY storage');
29+
// Fallback to InMemoryStorage if LocalStorage API is not available
30+
if (!isLocalStorageAvailable()) {
31+
params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
3232
return InMemoryStorageCSFactory(params);
3333
}
3434

src/storages/inMemory/InMemoryStorageCS.ts

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
77
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
88
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
99
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10-
import { getMatching } from '../../utils/key';
11-
import { setRolloutPlan } from '../dataLoader';
1210
import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
1311

1412
/**
@@ -17,9 +15,7 @@ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
1715
* @param params - parameters required by EventsCacheSync
1816
*/
1917
export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
20-
const { settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation }, initialRolloutPlan }, onReadyFromCacheCb } = params;
21-
22-
const storages: Record<string, IStorageSync> = {};
18+
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation } } } = params;
2319

2420
const splits = new SplitsCacheInMemory(__splitFiltersValidation);
2521
const rbSegments = new RBSegmentsCacheInMemory();
@@ -40,31 +36,20 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
4036
destroy() { },
4137

4238
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
43-
shared(matchingKey: string) {
44-
if (!storages[matchingKey]) {
45-
const segments = new MySegmentsCacheInMemory();
46-
const largeSegments = new MySegmentsCacheInMemory();
47-
48-
if (initialRolloutPlan) {
49-
setRolloutPlan(log, initialRolloutPlan, { segments, largeSegments }, matchingKey);
50-
}
51-
52-
storages[matchingKey] = {
53-
splits: this.splits,
54-
rbSegments: this.rbSegments,
55-
segments,
56-
largeSegments,
57-
impressions: this.impressions,
58-
impressionCounts: this.impressionCounts,
59-
events: this.events,
60-
telemetry: this.telemetry,
61-
uniqueKeys: this.uniqueKeys,
62-
63-
destroy() { }
64-
};
65-
}
39+
shared() {
40+
return {
41+
splits: this.splits,
42+
rbSegments: this.rbSegments,
43+
segments: new MySegmentsCacheInMemory(),
44+
largeSegments: new MySegmentsCacheInMemory(),
45+
impressions: this.impressions,
46+
impressionCounts: this.impressionCounts,
47+
events: this.events,
48+
telemetry: this.telemetry,
49+
uniqueKeys: this.uniqueKeys,
6650

67-
return storages[matchingKey];
51+
destroy() { }
52+
};
6853
},
6954
};
7055

@@ -78,14 +63,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
7863
storage.uniqueKeys.track = noopTrack;
7964
}
8065

81-
const matchingKey = getMatching(params.settings.core.key);
82-
storages[matchingKey] = storage;
83-
84-
if (initialRolloutPlan) {
85-
setRolloutPlan(log, initialRolloutPlan, storage, matchingKey);
86-
if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
87-
}
88-
8966
return storage;
9067
}
9168

0 commit comments

Comments
 (0)