Skip to content

Commit 131ee7a

Browse files
revert data_loader_for_ssr
1 parent bdf69e8 commit 131ee7a

File tree

11 files changed

+50
-129
lines changed

11 files changed

+50
-129
lines changed

CHANGES.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
2.0.0 (October XX, 2024)
22
- Added support for targeting rules based on large segments.
33
- Added `factory.destroy()` method, which invokes the `destroy` method on all SDK clients created by the factory.
4-
- Added `factory.getState()` method for standalone server-side SDKs, which returns the rollout plan snapshot from the storage.
5-
- Added `preloadedData` configuration option for standalone client-side SDKs, which allows preloading the SDK storage with a snapshot of the rollout plan.
64
- Updated internal storage factory to emit the SDK_READY_FROM_CACHE event when it corresponds, to clean up the initialization flow.
75
- Updated the handling of timers and async operations by moving them into an `init` factory method to enable lazy initialization of the SDK. This update is intended for the React SDK.
86
- Bugfixing - Fixed an issue with the server-side polling manager that caused dangling timers when the SDK was destroyed before it was ready.

src/sdkClient/__tests__/sdkClientMethodCS.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ const params = {
4747
settings: settingsWithKey,
4848
telemetryTracker: telemetryTrackerFactory(),
4949
clients: {},
50-
whenInit: (cb: () => void) => cb()
5150
};
5251

5352
const invalidAttributes = [

src/sdkFactory/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
5656
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
5757
}
5858
});
59-
59+
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
6060
const clients: Record<string, IBasicClient> = {};
6161
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
6262
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
@@ -82,7 +82,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
8282
// splitApi is used by SyncManager and Browser signal listener
8383
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
8484

85-
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform, whenInit };
85+
const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
8686

8787
const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
8888
ctx.syncManager = syncManager;

src/sdkFactory/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export interface ISdkFactoryContext {
5050
splitApi?: ISplitApi
5151
syncManager?: ISyncManager,
5252
clients: Record<string, IBasicClient>,
53-
whenInit(cb: () => void): void
5453
}
5554

5655
export interface ISdkFactoryContextSync extends ISdkFactoryContext {

src/storages/__tests__/dataLoader.spec.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/storages/dataLoader.ts

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,55 @@
11
import { SplitIO } from '../types';
2-
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
3-
import { setToArray, ISet } from '../utils/lang/sets';
4-
import { getMatching } from '../utils/key';
2+
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
3+
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
54

65
/**
7-
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
8-
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
6+
* Factory of client-side storage loader
97
*
10-
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
11-
* @param storage object containing `splits` and `segments` cache (client-side variant)
12-
* @param userKey user key (matching key) of the provided MySegmentsCache
13-
*
14-
* @TODO extend to load largeSegments
15-
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
16-
* @TODO add logs, and input validation in this module, in favor of size reduction.
17-
* @TODO unit tests
8+
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
9+
* and extended with a `mySegmentsData` property.
10+
* @returns function to preload the storage
1811
*/
19-
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
20-
// Do not load data if current preloadedData is empty
21-
if (Object.keys(preloadedData).length === 0) return;
22-
23-
const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
12+
export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {
13+
14+
/**
15+
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
16+
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
17+
*
18+
* @param storage object containing `splits` and `segments` cache (client-side variant)
19+
* @param userId user key string of the provided MySegmentsCache
20+
*
21+
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
22+
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
23+
*/
24+
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
25+
// Do not load data if current preloadedData is empty
26+
if (Object.keys(preloadedData).length === 0) return;
27+
28+
const { lastUpdated = -1, segmentsData = {}, since = -1, splitsData = {} } = preloadedData;
2429

25-
if (storage.splits) {
2630
const storedSince = storage.splits.getChangeNumber();
31+
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
2732

28-
// Do not load data if current data is more recent
29-
if (storedSince > since) return;
33+
// Do not load data if current localStorage data is more recent,
34+
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
35+
if (storedSince > since || lastUpdated < expirationTimestamp) return;
3036

3137
// cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
3238
storage.splits.clear();
3339
storage.splits.setChangeNumber(since);
3440

3541
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
36-
storage.splits.addSplits(splitsData.map(split => ([split.name, split])));
37-
}
42+
storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])));
3843

39-
if (matchingKey) { // add mySegments data (client-side)
40-
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[matchingKey];
44+
// add mySegments data
45+
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
4146
if (!mySegmentsData) {
4247
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
4348
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
44-
const matchingKeys = segmentsData[segmentName];
45-
return matchingKeys.indexOf(matchingKey) > -1;
49+
const userIds = JSON.parse(segmentsData[segmentName]).added;
50+
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
4651
});
4752
}
4853
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
49-
} else { // add segments data (server-side)
50-
Object.keys(segmentsData).filter(segmentName => {
51-
const matchingKeys = segmentsData[segmentName];
52-
storage.segments.addToSegment(segmentName, matchingKeys);
53-
});
54-
}
55-
}
56-
57-
export function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData {
58-
return {
59-
// lastUpdated: Date.now(),
60-
since: storage.splits.getChangeNumber(),
61-
splitsData: storage.splits.getAll(),
62-
segmentsData: userKeys ?
63-
undefined : // @ts-ignore accessing private prop
64-
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
65-
prev[cur] = setToArray(storage.segments.segmentCache[cur] as ISet<string>);
66-
return prev;
67-
}, {}),
68-
mySegmentsData: userKeys ?
69-
userKeys.reduce<Record<string, string[]>>((prev, userKey) => {
70-
prev[getMatching(userKey)] = storage.shared ?
71-
// Client-side segments
72-
// @ts-ignore accessing private prop
73-
Object.keys(storage.shared(userKey).segments.segmentCache) :
74-
// Server-side segments
75-
// @ts-ignore accessing private prop
76-
Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop
77-
return storage.segments.segmentCache[segmentName].has(userKey) ?
78-
prev.concat(segmentName) :
79-
prev;
80-
}, []);
81-
return prev;
82-
}, {}) :
83-
undefined
8454
};
8555
}

src/storages/inMemory/InMemoryStorageCS.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
77
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
88
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
99
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10-
import { getMatching } from '../../utils/key';
11-
import { loadData } from '../dataLoader';
1210

1311
/**
1412
* InMemory storage factory for standalone client-side SplitFactory
1513
*
1614
* @param params parameters required by EventsCacheSync
1715
*/
1816
export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
19-
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation }, preloadedData }, onReadyFromCacheCb } = params;
17+
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
2018

2119
const splits = new SplitsCacheInMemory(__splitFiltersValidation);
2220
const segments = new MySegmentsCacheInMemory();
@@ -44,18 +42,11 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
4442
},
4543

4644
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
47-
shared(matchingKey: string) {
48-
const segments = new MySegmentsCacheInMemory();
49-
const largeSegments = new MySegmentsCacheInMemory();
50-
51-
if (preloadedData) {
52-
loadData(preloadedData, { segments, largeSegments }, matchingKey);
53-
}
54-
45+
shared() {
5546
return {
5647
splits: this.splits,
57-
segments,
58-
largeSegments,
48+
segments: new MySegmentsCacheInMemory(),
49+
largeSegments: new MySegmentsCacheInMemory(),
5950
impressions: this.impressions,
6051
impressionCounts: this.impressionCounts,
6152
events: this.events,
@@ -81,12 +72,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
8172
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
8273
}
8374

84-
85-
if (preloadedData) {
86-
loadData(preloadedData, storage, getMatching(params.settings.core.key));
87-
if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
88-
}
89-
9075
return storage;
9176
}
9277

src/storages/types.ts

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

493493
/** StorageFactory */
494494

495+
export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
496+
495497
export interface IStorageFactoryParams {
496498
settings: ISettings,
497499
/**

src/trackers/eventTracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export function eventTrackerFactory(
3232
if (tracked) {
3333
log.info(EVENTS_TRACKER_SUCCESS, [msg]);
3434
if (integrationsManager) {
35-
// Wrap in a timeout because we don't want it to be blocking.
3635
whenInit(() => {
36+
// Wrap in a timeout because we don't want it to be blocking.
3737
setTimeout(() => {
3838
// copy of event, to avoid unexpected behaviour if modified by integrations
3939
const eventDataCopy = objectAssign({}, eventData);

src/trackers/impressionsTracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export function impressionsTrackerFactory(
6767
sdkLanguageVersion: version
6868
};
6969

70-
// Wrap in a timeout because we don't want it to be blocking.
7170
whenInit(() => {
71+
// Wrap in a timeout because we don't want it to be blocking.
7272
setTimeout(() => {
7373
// integrationsManager.handleImpression does not throw errors
7474
if (integrationsManager) integrationsManager.handleImpression(impressionData);

src/types.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ISplit, ISplitFiltersValidation } from './dtos/types';
1+
import { ISplitFiltersValidation } from './dtos/types';
22
import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
33
import { ILogger } from './logger/types';
44
import { ISdkFactoryContext } from './sdkFactory/types';
@@ -98,7 +98,6 @@ export interface ISettings {
9898
eventsFirstPushWindow: number
9999
},
100100
readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
101-
readonly preloadedData?: SplitIO.PreloadedData,
102101
readonly integrations: Array<{
103102
readonly type: string,
104103
(params: IIntegrationFactoryParams): IIntegration | void
@@ -772,31 +771,31 @@ export namespace SplitIO {
772771
* If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
773772
* @TODO configurable expiration time policy?
774773
*/
775-
// lastUpdated: number,
774+
lastUpdated: number,
776775
/**
777776
* Change number of the preloaded data.
778777
* If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
779778
*/
780779
since: number,
781780
/**
782-
* List of feature flag definitions.
783-
* @TODO rename to flags
781+
* Map of feature flags to their stringified definitions.
784782
*/
785-
splitsData: ISplit[],
783+
splitsData: {
784+
[splitName: string]: string
785+
},
786786
/**
787787
* Optional map of user keys to their list of segments.
788-
* @TODO rename to memberships
788+
* @TODO remove when releasing first version
789789
*/
790790
mySegmentsData?: {
791791
[key: string]: string[]
792792
},
793793
/**
794794
* Optional map of segments to their stringified definitions.
795795
* This property is ignored if `mySegmentsData` was provided.
796-
* @TODO rename to segments
797796
*/
798797
segmentsData?: {
799-
[segmentName: string]: string[]
798+
[segmentName: string]: string
800799
},
801800
}
802801
/**

0 commit comments

Comments
 (0)