-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Draft implementation of loadData and getSnapshot methods
- Loading branch information
1 parent
cf822b9
commit 5f865f3
Showing
6 changed files
with
124 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { InMemoryStorageFactory } from '../inMemory/InMemoryStorage'; | ||
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS'; | ||
import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks'; | ||
|
||
import * as dataLoader from '../dataLoader'; | ||
|
||
test('loadData & getSnapshot', () => { | ||
jest.spyOn(dataLoader, 'loadData'); | ||
const onReadyFromCacheCb = jest.fn(); | ||
// @ts-expect-error | ||
const serverStorage = InMemoryStorageFactory({ settings: fullSettings }); | ||
serverStorage.splits.setChangeNumber(123); // @ts-expect-error | ||
serverStorage.splits.addSplits([['split1', { name: 'split1' }]]); | ||
serverStorage.segments.addToSegment('segment1', [fullSettings.core.key as string]); | ||
|
||
const preloadedData = dataLoader.getSnapshot(serverStorage, [fullSettings.core.key as string]); | ||
|
||
// @ts-expect-error | ||
const clientStorage = InMemoryStorageCSFactory({ settings: { ...fullSettings, preloadedData }, onReadyFromCacheCb }); | ||
|
||
// Assert | ||
expect(dataLoader.loadData).toBeCalledTimes(1); | ||
expect(onReadyFromCacheCb).toBeCalledTimes(1); | ||
expect(dataLoader.getSnapshot(clientStorage, [fullSettings.core.key as string])).toEqual(preloadedData); | ||
expect(preloadedData).toEqual({ | ||
since: 123, | ||
splitsData: { | ||
split1: { name: 'split1' } | ||
}, | ||
mySegmentsData: { [fullSettings.core.key as string]: ['segment1'] }, | ||
segmentsData: undefined | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,86 @@ | ||
import { SplitIO } from '../types'; | ||
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser'; | ||
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types'; | ||
import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types'; | ||
import { setToArray, ISet } from '../utils/lang/sets'; | ||
|
||
/** | ||
* Factory of client-side storage loader | ||
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function | ||
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js) | ||
* | ||
* @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 | ||
* @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 | ||
*/ | ||
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; | ||
export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, userKey?: string) { | ||
// Do not load data if current preloadedData is empty | ||
if (Object.keys(preloadedData).length === 0) return; | ||
|
||
const { 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 localStorage data is more recent, | ||
// or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`, | ||
if (storedSince > since || lastUpdated < expirationTimestamp) return; | ||
// Do not load data if current data is more recent | ||
if (storedSince > since) 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(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName]))); | ||
storage.splits.addSplits(Object.keys(splitsData).map(splitName => ([splitName, splitsData[splitName]]))); | ||
} | ||
|
||
// add mySegments data | ||
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId]; | ||
if (userKey) { // add mySegments data (client-side) | ||
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey]; | ||
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 userIds = JSON.parse(segmentsData[segmentName]).added; | ||
return Array.isArray(userIds) && userIds.indexOf(userId) > -1; | ||
const userKeys = segmentsData[segmentName]; | ||
return userKeys.indexOf(userKey) > -1; | ||
}); | ||
} | ||
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) }); | ||
} else { // add segments data (server-side) | ||
Object.keys(segmentsData).filter(segmentName => { | ||
const userKeys = segmentsData[segmentName]; | ||
storage.segments.addToSegment(segmentName, userKeys); | ||
}); | ||
} | ||
} | ||
|
||
export function getSnapshot(storage: IStorageSync, userKeys?: string[]): SplitIO.PreloadedData { | ||
return { | ||
// lastUpdated: Date.now(), | ||
// @ts-ignore accessing private prop | ||
since: storage.splits.changeNumber, // @ts-ignore accessing private prop | ||
splitsData: storage.splits.splitsCache, | ||
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((prev, userKey) => { | ||
// @ts-ignore accessing private prop | ||
prev[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 | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters