Skip to content

Commit

Permalink
revert data_loader_for_ssr
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilianoSanchez committed Oct 9, 2024
1 parent bdf69e8 commit 131ee7a
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 129 deletions.
2 changes: 0 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
2.0.0 (October XX, 2024)
- Added support for targeting rules based on large segments.
- Added `factory.destroy()` method, which invokes the `destroy` method on all SDK clients created by the factory.
- Added `factory.getState()` method for standalone server-side SDKs, which returns the rollout plan snapshot from the storage.
- Added `preloadedData` configuration option for standalone client-side SDKs, which allows preloading the SDK storage with a snapshot of the rollout plan.
- Updated internal storage factory to emit the SDK_READY_FROM_CACHE event when it corresponds, to clean up the initialization flow.
- 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.
- Bugfixing - Fixed an issue with the server-side polling manager that caused dangling timers when the SDK was destroyed before it was ready.
Expand Down
1 change: 0 additions & 1 deletion src/sdkClient/__tests__/sdkClientMethodCS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const params = {
settings: settingsWithKey,
telemetryTracker: telemetryTrackerFactory(),
clients: {},
whenInit: (cb: () => void) => cb()
};

const invalidAttributes = [
Expand Down
4 changes: 2 additions & 2 deletions src/sdkFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
}
});

// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
const clients: Record<string, IBasicClient> = {};
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
Expand All @@ -82,7 +82,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
// splitApi is used by SyncManager and Browser signal listener
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);

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

const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
ctx.syncManager = syncManager;
Expand Down
1 change: 0 additions & 1 deletion src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export interface ISdkFactoryContext {
splitApi?: ISplitApi
syncManager?: ISyncManager,
clients: Record<string, IBasicClient>,
whenInit(cb: () => void): void
}

export interface ISdkFactoryContextSync extends ISdkFactoryContext {
Expand Down
31 changes: 0 additions & 31 deletions src/storages/__tests__/dataLoader.spec.ts

This file was deleted.

94 changes: 32 additions & 62 deletions src/storages/dataLoader.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,55 @@
import { SplitIO } from '../types';
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
import { setToArray, ISet } from '../utils/lang/sets';
import { getMatching } from '../utils/key';
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';

/**
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
* Factory of client-side storage loader
*
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
* @param storage object containing `splits` and `segments` cache (client-side variant)
* @param userKey user key (matching key) of the provided MySegmentsCache
*
* @TODO extend to load largeSegments
* @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.
* @TODO add logs, and input validation in this module, in favor of size reduction.
* @TODO unit tests
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
* and extended with a `mySegmentsData` property.
* @returns function to preload the storage
*/
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
// Do not load data if current preloadedData is empty
if (Object.keys(preloadedData).length === 0) return;

const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {

/**
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
*
* @param storage object containing `splits` and `segments` cache (client-side variant)
* @param userId user key string of the provided MySegmentsCache
*
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
* @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.
*/
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
// Do not load data if current preloadedData is empty
if (Object.keys(preloadedData).length === 0) return;

const { lastUpdated = -1, segmentsData = {}, since = -1, splitsData = {} } = preloadedData;

if (storage.splits) {
const storedSince = storage.splits.getChangeNumber();
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;

// Do not load data if current data is more recent
if (storedSince > since) return;
// Do not load data if current localStorage data is more recent,
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
if (storedSince > since || lastUpdated < expirationTimestamp) return;

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

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

if (matchingKey) { // add mySegments data (client-side)
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[matchingKey];
// add mySegments data
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
if (!mySegmentsData) {
// 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
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
const matchingKeys = segmentsData[segmentName];
return matchingKeys.indexOf(matchingKey) > -1;
const userIds = JSON.parse(segmentsData[segmentName]).added;
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
});
}
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
} else { // add segments data (server-side)
Object.keys(segmentsData).filter(segmentName => {
const matchingKeys = segmentsData[segmentName];
storage.segments.addToSegment(segmentName, matchingKeys);
});
}
}

export function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData {
return {
// lastUpdated: Date.now(),
since: storage.splits.getChangeNumber(),
splitsData: storage.splits.getAll(),
segmentsData: userKeys ?
undefined : // @ts-ignore accessing private prop
Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
prev[cur] = setToArray(storage.segments.segmentCache[cur] as ISet<string>);
return prev;
}, {}),
mySegmentsData: userKeys ?
userKeys.reduce<Record<string, string[]>>((prev, userKey) => {
prev[getMatching(userKey)] = storage.shared ?
// Client-side segments
// @ts-ignore accessing private prop
Object.keys(storage.shared(userKey).segments.segmentCache) :
// Server-side segments
// @ts-ignore accessing private prop
Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop
return storage.segments.segmentCache[segmentName].has(userKey) ?
prev.concat(segmentName) :
prev;
}, []);
return prev;
}, {}) :
undefined
};
}
23 changes: 4 additions & 19 deletions src/storages/inMemory/InMemoryStorageCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
import { getMatching } from '../../utils/key';
import { loadData } from '../dataLoader';

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

const splits = new SplitsCacheInMemory(__splitFiltersValidation);
const segments = new MySegmentsCacheInMemory();
Expand Down Expand Up @@ -44,18 +42,11 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
},

// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
shared(matchingKey: string) {
const segments = new MySegmentsCacheInMemory();
const largeSegments = new MySegmentsCacheInMemory();

if (preloadedData) {
loadData(preloadedData, { segments, largeSegments }, matchingKey);
}

shared() {
return {
splits: this.splits,
segments,
largeSegments,
segments: new MySegmentsCacheInMemory(),
largeSegments: new MySegmentsCacheInMemory(),
impressions: this.impressions,
impressionCounts: this.impressionCounts,
events: this.events,
Expand All @@ -81,12 +72,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
}


if (preloadedData) {
loadData(preloadedData, storage, getMatching(params.settings.core.key));
if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
}

return storage;
}

Expand Down
2 changes: 2 additions & 0 deletions src/storages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@ export interface IStorageAsync extends IStorageBase<

/** StorageFactory */

export type DataLoader = (storage: IStorageSync, matchingKey: string) => void

export interface IStorageFactoryParams {
settings: ISettings,
/**
Expand Down
2 changes: 1 addition & 1 deletion src/trackers/eventTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export function eventTrackerFactory(
if (tracked) {
log.info(EVENTS_TRACKER_SUCCESS, [msg]);
if (integrationsManager) {
// Wrap in a timeout because we don't want it to be blocking.
whenInit(() => {
// Wrap in a timeout because we don't want it to be blocking.
setTimeout(() => {
// copy of event, to avoid unexpected behaviour if modified by integrations
const eventDataCopy = objectAssign({}, eventData);
Expand Down
2 changes: 1 addition & 1 deletion src/trackers/impressionsTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export function impressionsTrackerFactory(
sdkLanguageVersion: version
};

// Wrap in a timeout because we don't want it to be blocking.
whenInit(() => {
// Wrap in a timeout because we don't want it to be blocking.
setTimeout(() => {
// integrationsManager.handleImpression does not throw errors
if (integrationsManager) integrationsManager.handleImpression(impressionData);
Expand Down
17 changes: 8 additions & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ISplit, ISplitFiltersValidation } from './dtos/types';
import { ISplitFiltersValidation } from './dtos/types';
import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
import { ILogger } from './logger/types';
import { ISdkFactoryContext } from './sdkFactory/types';
Expand Down Expand Up @@ -98,7 +98,6 @@ export interface ISettings {
eventsFirstPushWindow: number
},
readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
readonly preloadedData?: SplitIO.PreloadedData,
readonly integrations: Array<{
readonly type: string,
(params: IIntegrationFactoryParams): IIntegration | void
Expand Down Expand Up @@ -772,31 +771,31 @@ export namespace SplitIO {
* If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
* @TODO configurable expiration time policy?
*/
// lastUpdated: number,
lastUpdated: number,
/**
* Change number of the preloaded data.
* If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
*/
since: number,
/**
* List of feature flag definitions.
* @TODO rename to flags
* Map of feature flags to their stringified definitions.
*/
splitsData: ISplit[],
splitsData: {
[splitName: string]: string
},
/**
* Optional map of user keys to their list of segments.
* @TODO rename to memberships
* @TODO remove when releasing first version
*/
mySegmentsData?: {
[key: string]: string[]
},
/**
* Optional map of segments to their stringified definitions.
* This property is ignored if `mySegmentsData` was provided.
* @TODO rename to segments
*/
segmentsData?: {
[segmentName: string]: string[]
[segmentName: string]: string
},
}
/**
Expand Down

0 comments on commit 131ee7a

Please sign in to comment.