@splitsoftware/splitio-commons 1.17.1-rc.1 → 1.17.1-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGES.txt +1 -0
  2. package/cjs/sdkFactory/index.js +3 -1
  3. package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
  4. package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
  5. package/cjs/storages/dataLoader.js +65 -32
  6. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  7. package/cjs/storages/inLocalStorage/index.js +4 -1
  8. package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
  9. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -7
  10. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
  11. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -12
  12. package/esm/sdkFactory/index.js +4 -2
  13. package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
  14. package/esm/storages/AbstractSplitsCacheSync.js +0 -7
  15. package/esm/storages/dataLoader.js +62 -30
  16. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  17. package/esm/storages/inLocalStorage/index.js +5 -2
  18. package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
  19. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -8
  20. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
  21. package/esm/utils/settingsValidation/storage/storageCS.js +0 -10
  22. package/package.json +1 -1
  23. package/src/sdkFactory/index.ts +5 -2
  24. package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
  25. package/src/storages/AbstractSplitsCacheSync.ts +0 -8
  26. package/src/storages/dataLoader.ts +63 -32
  27. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -10
  28. package/src/storages/inLocalStorage/index.ts +6 -2
  29. package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
  30. package/src/storages/types.ts +1 -6
  31. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +3 -7
  32. package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -11
  33. package/src/types.ts +9 -8
  34. package/src/utils/settingsValidation/storage/storageCS.ts +0 -13
  35. package/types/storages/AbstractSplitsCacheAsync.d.ts +0 -5
  36. package/types/storages/AbstractSplitsCacheSync.d.ts +0 -5
  37. package/types/storages/dataLoader.d.ts +17 -6
  38. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -6
  39. package/types/storages/types.d.ts +1 -4
  40. package/types/types.d.ts +8 -8
  41. package/types/utils/settingsValidation/storage/storageCS.d.ts +0 -5
@@ -1,6 +1,6 @@
1
1
  import { _Set, setToArray } from '../../../utils/lang/sets';
2
2
  import { timeout } from '../../../utils/promise/timeout';
3
- import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
3
+ import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
4
4
  import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
5
5
  import { startsWith } from '../../../utils/lang';
6
6
  import { IN_SEGMENT } from '../../../utils/constants';
@@ -121,7 +121,7 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
121
121
  function _splitChangesUpdater(since, retry) {
122
122
  if (retry === void 0) { retry = 0; }
123
123
  log.debug(SYNC_SPLITS_FETCH, [since]);
124
- var fetcherPromise = Promise.resolve(splitUpdateNotification ?
124
+ return Promise.resolve(splitUpdateNotification ?
125
125
  { splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
126
126
  splitChangesFetcher(since, noCache, till, _promiseDecorator))
127
127
  .then(function (splitChanges) {
@@ -165,15 +165,6 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
165
165
  }
166
166
  return false;
167
167
  });
168
- // After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
169
- // Wrapping in a promise since checkCache can be async.
170
- if (splitsEventEmitter && startingUp) {
171
- Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
172
- if (isCacheReady)
173
- splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
174
- });
175
- }
176
- return fetcherPromise;
177
168
  }
178
169
  var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
179
170
  return sincePromise.then(_splitChangesUpdater);
@@ -1,12 +1,6 @@
1
1
  import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS';
2
2
  import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
3
3
  import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
4
- export function __InLocalStorageMockFactory(params) {
5
- var result = InMemoryStorageCSFactory(params);
6
- result.splits.checkCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
7
- return result;
8
- }
9
- __InLocalStorageMockFactory.type = STORAGE_MEMORY;
10
4
  /**
11
5
  * This function validates `settings.storage` object
12
6
  *
@@ -23,10 +17,6 @@ export function validateStorageCS(settings) {
23
17
  storage = InMemoryStorageCSFactory;
24
18
  log.error(ERROR_STORAGE_INVALID);
25
19
  }
26
- // In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
27
- if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
28
- return __InLocalStorageMockFactory;
29
- }
30
20
  if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
31
21
  // Consumer modes require an async storage
32
22
  if (storage.type !== STORAGE_PLUGGABLE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.17.1-rc.1",
3
+ "version": "1.17.1-rc.2",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -7,7 +7,7 @@ import { IBasicClient, SplitIO } from '../types';
7
7
  import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
8
8
  import { createLoggerAPI } from '../logger/sdkLogger';
9
9
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
10
- import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
10
+ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
11
11
  import { objectAssign } from '../utils/lang/objectAssign';
12
12
  import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
13
13
  import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
@@ -46,8 +46,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
46
46
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
47
47
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
48
48
  },
49
+ onReadyFromCacheCb: () => {
50
+ readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
51
+ }
49
52
  });
50
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
53
+
51
54
  const clients: Record<string, IBasicClient> = {};
52
55
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
53
56
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
@@ -28,14 +28,6 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
28
28
  return Promise.resolve(true);
29
29
  }
30
30
 
31
- /**
32
- * Check if the splits information is already stored in cache.
33
- * Noop, just keeping the interface. This is used by client-side implementations only.
34
- */
35
- checkCache(): Promise<boolean> {
36
- return Promise.resolve(false);
37
- }
38
-
39
31
  /**
40
32
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
41
33
  * Used for SPLIT_KILL push notifications.
@@ -48,14 +48,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
48
48
 
49
49
  abstract clear(): void
50
50
 
51
- /**
52
- * Check if the splits information is already stored in cache. This data can be preloaded.
53
- * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
54
- */
55
- checkCache(): boolean {
56
- return false;
57
- }
58
-
59
51
  /**
60
52
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
61
53
  * Used for SPLIT_KILL push notifications.
@@ -1,55 +1,86 @@
1
1
  import { SplitIO } from '../types';
2
- import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
3
- import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
2
+ import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
3
+ import { setToArray, ISet } from '../utils/lang/sets';
4
4
 
5
5
  /**
6
- * Factory of client-side storage loader
6
+ * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
7
+ * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
7
8
  *
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
9
+ * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
10
+ * @param storage object containing `splits` and `segments` cache (client-side variant)
11
+ * @param userKey user key (matching key) of the provided MySegmentsCache
12
+ *
13
+ * @TODO extend to load largeSegments
14
+ * @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.
15
+ * @TODO add logs, and input validation in this module, in favor of size reduction.
16
+ * @TODO unit tests
11
17
  */
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;
18
+ export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, userKey?: string) {
19
+ // Do not load data if current preloadedData is empty
20
+ if (Object.keys(preloadedData).length === 0) return;
21
+
22
+ const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
29
23
 
24
+ if (storage.splits) {
30
25
  const storedSince = storage.splits.getChangeNumber();
31
- const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
32
26
 
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;
27
+ // Do not load data if current data is more recent
28
+ if (storedSince > since) return;
36
29
 
37
30
  // cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
38
31
  storage.splits.clear();
39
32
  storage.splits.setChangeNumber(since);
40
33
 
41
34
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
42
- storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])));
35
+ storage.splits.addSplits(splitsData.map(split => ([split.name, split])));
36
+ }
43
37
 
44
- // add mySegments data
45
- let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
38
+ if (userKey) { // add mySegments data (client-side)
39
+ let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
46
40
  if (!mySegmentsData) {
47
41
  // 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
48
42
  mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
49
- const userIds = JSON.parse(segmentsData[segmentName]).added;
50
- return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
43
+ const userKeys = segmentsData[segmentName];
44
+ return userKeys.indexOf(userKey) > -1;
51
45
  });
52
46
  }
53
47
  storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
48
+ } else { // add segments data (server-side)
49
+ Object.keys(segmentsData).filter(segmentName => {
50
+ const userKeys = segmentsData[segmentName];
51
+ storage.segments.addToSegment(segmentName, userKeys);
52
+ });
53
+ }
54
+ }
55
+
56
+ export function getSnapshot(storage: IStorageSync, userKeys?: string[]): SplitIO.PreloadedData {
57
+ return {
58
+ // lastUpdated: Date.now(),
59
+ // @ts-ignore accessing private prop
60
+ since: storage.splits.changeNumber,
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((prev, userKey) => {
70
+ // @ts-ignore accessing private prop
71
+ prev[userKey] = storage.shared ?
72
+ // Client-side segments
73
+ // @ts-ignore accessing private prop
74
+ Object.keys(storage.shared(userKey).segments.segmentCache) :
75
+ // Server-side segments
76
+ // @ts-ignore accessing private prop
77
+ Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop
78
+ return storage.segments.segmentCache[segmentName].has(userKey) ?
79
+ prev.concat(segmentName) :
80
+ prev;
81
+ }, []);
82
+ return prev;
83
+ }, {}) :
84
+ undefined
54
85
  };
55
86
  }
@@ -217,15 +217,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
217
217
  }
218
218
  }
219
219
 
220
- /**
221
- * Check if the splits information is already stored in browser LocalStorage.
222
- * In this function we could add more code to check if the data is valid.
223
- * @override
224
- */
225
- checkCache(): boolean {
226
- return this.getChangeNumber() > -1;
227
- }
228
-
229
220
  /**
230
221
  * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
231
222
  *
@@ -250,7 +241,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
250
241
  this.updateNewFilter = true;
251
242
 
252
243
  // if there is cache, clear it
253
- if (this.checkCache()) this.clear();
244
+ if (this.getChangeNumber() > -1) this.clear();
254
245
 
255
246
  } catch (e) {
256
247
  this.log.error(LOG_PREFIX + e);
@@ -12,7 +12,7 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
12
12
  import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
13
13
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
14
14
  import { LOG_PREFIX } from './constants';
15
- import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
15
+ import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
16
16
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
17
17
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
18
18
  import { getMatching } from '../../utils/key';
@@ -36,7 +36,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
36
36
  return InMemoryStorageCSFactory(params);
37
37
  }
38
38
 
39
- const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
39
+ const { onReadyFromCacheCb, settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
40
40
  const matchingKey = getMatching(settings.core.key);
41
41
  const keys = new KeyBuilderCS(prefix, matchingKey);
42
42
  const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
@@ -45,6 +45,10 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
45
45
  const segments = new MySegmentsCacheInLocal(log, keys);
46
46
  const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
47
47
 
48
+ if (settings.mode === LOCALHOST_MODE || splits.getChangeNumber() > -1) {
49
+ Promise.resolve().then(onReadyFromCacheCb);
50
+ }
51
+
48
52
  return {
49
53
  splits,
50
54
  segments,
@@ -7,6 +7,8 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
+ import { getMatching } from '../../utils/key';
11
+ import { loadData } from '../dataLoader';
10
12
 
11
13
  /**
12
14
  * InMemory storage factory for standalone client-side SplitFactory
@@ -14,7 +16,7 @@ import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
14
16
  * @param params parameters required by EventsCacheSync
15
17
  */
16
18
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
17
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
19
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation }, preloadedData }, onReadyFromCacheCb } = params;
18
20
 
19
21
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
20
22
  const segments = new MySegmentsCacheInMemory();
@@ -42,11 +44,18 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
42
44
  },
43
45
 
44
46
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
45
- shared() {
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
+
46
55
  return {
47
56
  splits: this.splits,
48
- segments: new MySegmentsCacheInMemory(),
49
- largeSegments: new MySegmentsCacheInMemory(),
57
+ segments,
58
+ largeSegments,
50
59
  impressions: this.impressions,
51
60
  impressionCounts: this.impressionCounts,
52
61
  events: this.events,
@@ -72,6 +81,12 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
72
81
  if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
73
82
  }
74
83
 
84
+
85
+ if (preloadedData) {
86
+ loadData(preloadedData, storage, getMatching(params.settings.core.key));
87
+ if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
88
+ }
89
+
75
90
  return storage;
76
91
  }
77
92
 
@@ -208,8 +208,6 @@ export interface ISplitsCacheBase {
208
208
  // only for Client-Side. Returns true if the storage is not synchronized yet (getChangeNumber() === -1) or contains a FF using segments or large segments
209
209
  usesSegments(): MaybeThenable<boolean>,
210
210
  clear(): MaybeThenable<boolean | void>,
211
- // should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
212
- checkCache(): MaybeThenable<boolean>,
213
211
  killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
214
212
  getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>
215
213
  }
@@ -226,7 +224,6 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
226
224
  trafficTypeExists(trafficType: string): boolean,
227
225
  usesSegments(): boolean,
228
226
  clear(): void,
229
- checkCache(): boolean,
230
227
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
231
228
  getNamesByFlagSets(flagSets: string[]): ISet<string>[]
232
229
  }
@@ -243,7 +240,6 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
243
240
  trafficTypeExists(trafficType: string): Promise<boolean>,
244
241
  usesSegments(): Promise<boolean>,
245
242
  clear(): Promise<boolean | void>,
246
- checkCache(): Promise<boolean>,
247
243
  killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
248
244
  getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
249
245
  }
@@ -495,8 +491,6 @@ export interface IStorageAsync extends IStorageBase<
495
491
 
496
492
  /** StorageFactory */
497
493
 
498
- export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
499
-
500
494
  export interface IStorageFactoryParams {
501
495
  settings: ISettings,
502
496
  /**
@@ -504,6 +498,7 @@ export interface IStorageFactoryParams {
504
498
  * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
505
499
  */
506
500
  onReadyCb: (error?: any) => void,
501
+ onReadyFromCacheCb: (error?: any) => void,
507
502
  }
508
503
 
509
504
  export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
@@ -7,7 +7,7 @@ import { syncTaskFactory } from '../../syncTask';
7
7
  import { ISyncTask } from '../../types';
8
8
  import { ISettings } from '../../../types';
9
9
  import { CONTROL } from '../../../utils/constants';
10
- import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
10
+ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
11
11
  import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
12
12
 
13
13
  /**
@@ -60,12 +60,8 @@ export function fromObjectUpdaterFactory(
60
60
 
61
61
  if (startingUp) {
62
62
  startingUp = false;
63
- Promise.resolve(splitsCache.checkCache()).then(cacheReady => {
64
- // Emits SDK_READY_FROM_CACHE
65
- if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
66
- // Emits SDK_READY
67
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
68
- });
63
+ // Emits SDK_READY
64
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
69
65
  }
70
66
  return true;
71
67
  });
@@ -4,7 +4,7 @@ import { ISplitChangesFetcher } from '../fetchers/types';
4
4
  import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
5
5
  import { ISplitsEventEmitter } from '../../../readiness/types';
6
6
  import { timeout } from '../../../utils/promise/timeout';
7
- import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
7
+ import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
8
8
  import { ILogger } from '../../../logger/types';
9
9
  import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
10
10
  import { startsWith } from '../../../utils/lang';
@@ -153,7 +153,8 @@ export function splitChangesUpdaterFactory(
153
153
  */
154
154
  function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
155
155
  log.debug(SYNC_SPLITS_FETCH, [since]);
156
- const fetcherPromise = Promise.resolve(splitUpdateNotification ?
156
+
157
+ return Promise.resolve(splitUpdateNotification ?
157
158
  { splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
158
159
  splitChangesFetcher(since, noCache, till, _promiseDecorator)
159
160
  )
@@ -200,15 +201,6 @@ export function splitChangesUpdaterFactory(
200
201
  }
201
202
  return false;
202
203
  });
203
-
204
- // After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
205
- // Wrapping in a promise since checkCache can be async.
206
- if (splitsEventEmitter && startingUp) {
207
- Promise.resolve(splits.checkCache()).then(isCacheReady => {
208
- if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
209
- });
210
- }
211
- return fetcherPromise;
212
204
  }
213
205
 
214
206
  let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ISplitFiltersValidation } from './dtos/types';
1
+ import { ISplit, ISplitFiltersValidation } from './dtos/types';
2
2
  import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
3
3
  import { ILogger } from './logger/types';
4
4
  import { ISdkFactoryContext } from './sdkFactory/types';
@@ -98,6 +98,7 @@ export interface ISettings {
98
98
  eventsFirstPushWindow: number
99
99
  },
100
100
  readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
101
+ readonly preloadedData?: SplitIO.PreloadedData,
101
102
  readonly integrations: Array<{
102
103
  readonly type: string,
103
104
  (params: IIntegrationFactoryParams): IIntegration | void
@@ -771,21 +772,20 @@ export namespace SplitIO {
771
772
  * If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
772
773
  * @TODO configurable expiration time policy?
773
774
  */
774
- lastUpdated: number,
775
+ // lastUpdated: number,
775
776
  /**
776
777
  * Change number of the preloaded data.
777
778
  * If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
778
779
  */
779
780
  since: number,
780
781
  /**
781
- * Map of feature flags to their stringified definitions.
782
+ * List of feature flag definitions.
783
+ * @TODO rename to flags
782
784
  */
783
- splitsData: {
784
- [splitName: string]: string
785
- },
785
+ splitsData: ISplit[],
786
786
  /**
787
787
  * Optional map of user keys to their list of segments.
788
- * @TODO remove when releasing first version
788
+ * @TODO rename to memberships
789
789
  */
790
790
  mySegmentsData?: {
791
791
  [key: string]: string[]
@@ -793,9 +793,10 @@ export namespace SplitIO {
793
793
  /**
794
794
  * Optional map of segments to their stringified definitions.
795
795
  * This property is ignored if `mySegmentsData` was provided.
796
+ * @TODO rename to segments
796
797
  */
797
798
  segmentsData?: {
798
- [segmentName: string]: string
799
+ [segmentName: string]: string[]
799
800
  },
800
801
  }
801
802
  /**
@@ -3,14 +3,6 @@ import { ISettings, SDKMode } from '../../../types';
3
3
  import { ILogger } from '../../../logger/types';
4
4
  import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
5
5
  import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
6
- import { IStorageFactoryParams, IStorageSync } from '../../../storages/types';
7
-
8
- export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSync {
9
- const result = InMemoryStorageCSFactory(params);
10
- result.splits.checkCache = () => true; // to emit SDK_READY_FROM_CACHE
11
- return result;
12
- }
13
- __InLocalStorageMockFactory.type = STORAGE_MEMORY;
14
6
 
15
7
  /**
16
8
  * This function validates `settings.storage` object
@@ -30,11 +22,6 @@ export function validateStorageCS(settings: { log: ILogger, storage?: any, mode:
30
22
  log.error(ERROR_STORAGE_INVALID);
31
23
  }
32
24
 
33
- // In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
34
- if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
35
- return __InLocalStorageMockFactory;
36
- }
37
-
38
25
  if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
39
26
  // Consumer modes require an async storage
40
27
  if (storage.type !== STORAGE_PLUGGABLE) throw new Error('A PluggableStorage instance is required on consumer mode');
@@ -19,11 +19,6 @@ export declare abstract class AbstractSplitsCacheAsync implements ISplitsCacheAs
19
19
  abstract trafficTypeExists(trafficType: string): Promise<boolean>;
20
20
  abstract clear(): Promise<boolean | void>;
21
21
  usesSegments(): Promise<boolean>;
22
- /**
23
- * Check if the splits information is already stored in cache.
24
- * Noop, just keeping the interface. This is used by client-side implementations only.
25
- */
26
- checkCache(): Promise<boolean>;
27
22
  /**
28
23
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
29
24
  * Used for SPLIT_KILL push notifications.
@@ -19,11 +19,6 @@ export declare abstract class AbstractSplitsCacheSync implements ISplitsCacheSyn
19
19
  abstract trafficTypeExists(trafficType: string): boolean;
20
20
  abstract usesSegments(): boolean;
21
21
  abstract clear(): void;
22
- /**
23
- * Check if the splits information is already stored in cache. This data can be preloaded.
24
- * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
25
- */
26
- checkCache(): boolean;
27
22
  /**
28
23
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
29
24
  * Used for SPLIT_KILL push notifications.
@@ -1,10 +1,21 @@
1
1
  import { SplitIO } from '../types';
2
- import { DataLoader } from './types';
2
+ import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
3
3
  /**
4
- * Factory of client-side storage loader
4
+ * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
5
+ * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
5
6
  *
6
- * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
7
- * and extended with a `mySegmentsData` property.
8
- * @returns function to preload the storage
7
+ * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
8
+ * @param storage object containing `splits` and `segments` cache (client-side variant)
9
+ * @param userKey user key (matching key) of the provided MySegmentsCache
10
+ *
11
+ * @TODO extend to load largeSegments
12
+ * @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.
13
+ * @TODO add logs, and input validation in this module, in favor of size reduction.
14
+ * @TODO unit tests
9
15
  */
10
- export declare function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader;
16
+ export declare function loadData(preloadedData: SplitIO.PreloadedData, storage: {
17
+ splits?: ISplitsCacheSync;
18
+ segments: ISegmentsCacheSync;
19
+ largeSegments?: ISegmentsCacheSync;
20
+ }, userKey?: string): void;
21
+ export declare function getSnapshot(storage: IStorageSync, userKeys?: string[]): SplitIO.PreloadedData;
@@ -35,12 +35,6 @@ export declare class SplitsCacheInLocal extends AbstractSplitsCacheSync {
35
35
  getSplitNames(): string[];
36
36
  trafficTypeExists(trafficType: string): boolean;
37
37
  usesSegments(): boolean;
38
- /**
39
- * Check if the splits information is already stored in browser LocalStorage.
40
- * In this function we could add more code to check if the data is valid.
41
- * @override
42
- */
43
- checkCache(): boolean;
44
38
  /**
45
39
  * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
46
40
  *
@@ -192,7 +192,6 @@ export interface ISplitsCacheBase {
192
192
  trafficTypeExists(trafficType: string): MaybeThenable<boolean>;
193
193
  usesSegments(): MaybeThenable<boolean>;
194
194
  clear(): MaybeThenable<boolean | void>;
195
- checkCache(): MaybeThenable<boolean>;
196
195
  killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>;
197
196
  getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>;
198
197
  }
@@ -208,7 +207,6 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
208
207
  trafficTypeExists(trafficType: string): boolean;
209
208
  usesSegments(): boolean;
210
209
  clear(): void;
211
- checkCache(): boolean;
212
210
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean;
213
211
  getNamesByFlagSets(flagSets: string[]): ISet<string>[];
214
212
  }
@@ -224,7 +222,6 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
224
222
  trafficTypeExists(trafficType: string): Promise<boolean>;
225
223
  usesSegments(): Promise<boolean>;
226
224
  clear(): Promise<boolean | void>;
227
- checkCache(): Promise<boolean>;
228
225
  killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>;
229
226
  getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>;
230
227
  }
@@ -394,7 +391,6 @@ export interface IStorageSync extends IStorageBase<ISplitsCacheSync, ISegmentsCa
394
391
  export interface IStorageAsync extends IStorageBase<ISplitsCacheAsync, ISegmentsCacheAsync, IImpressionsCacheAsync | IImpressionsCacheSync, IImpressionCountsCacheBase, IEventsCacheAsync | IEventsCacheSync, ITelemetryCacheAsync | ITelemetryCacheSync, IUniqueKeysCacheBase> {
395
392
  }
396
393
  /** StorageFactory */
397
- export declare type DataLoader = (storage: IStorageSync, matchingKey: string) => void;
398
394
  export interface IStorageFactoryParams {
399
395
  settings: ISettings;
400
396
  /**
@@ -402,6 +398,7 @@ export interface IStorageFactoryParams {
402
398
  * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
403
399
  */
404
400
  onReadyCb: (error?: any) => void;
401
+ onReadyFromCacheCb: (error?: any) => void;
405
402
  }
406
403
  export declare type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
407
404
  export declare type IStorageSyncFactory = {