@splitsoftware/splitio-commons 1.17.1-rc.0 → 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 (61) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/sdkClient/identity.js +7 -0
  3. package/cjs/sdkClient/sdkClient.js +5 -5
  4. package/cjs/sdkClient/sdkClientMethod.js +3 -1
  5. package/cjs/sdkClient/sdkClientMethodCS.js +8 -13
  6. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +8 -13
  7. package/cjs/sdkFactory/index.js +8 -2
  8. package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
  9. package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
  10. package/cjs/storages/dataLoader.js +65 -32
  11. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  12. package/cjs/storages/inLocalStorage/index.js +4 -1
  13. package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
  14. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -7
  15. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
  16. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -12
  17. package/esm/sdkClient/identity.js +3 -0
  18. package/esm/sdkClient/sdkClient.js +5 -5
  19. package/esm/sdkClient/sdkClientMethod.js +3 -1
  20. package/esm/sdkClient/sdkClientMethodCS.js +6 -11
  21. package/esm/sdkClient/sdkClientMethodCSWithTT.js +6 -11
  22. package/esm/sdkFactory/index.js +9 -3
  23. package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
  24. package/esm/storages/AbstractSplitsCacheSync.js +0 -7
  25. package/esm/storages/dataLoader.js +62 -30
  26. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  27. package/esm/storages/inLocalStorage/index.js +5 -2
  28. package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
  29. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -8
  30. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
  31. package/esm/utils/settingsValidation/storage/storageCS.js +0 -10
  32. package/package.json +1 -1
  33. package/src/sdkClient/identity.ts +5 -0
  34. package/src/sdkClient/sdkClient.ts +5 -5
  35. package/src/sdkClient/sdkClientMethod.ts +4 -1
  36. package/src/sdkClient/sdkClientMethodCS.ts +6 -12
  37. package/src/sdkClient/sdkClientMethodCSWithTT.ts +6 -12
  38. package/src/sdkFactory/index.ts +11 -4
  39. package/src/sdkFactory/types.ts +2 -1
  40. package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
  41. package/src/storages/AbstractSplitsCacheSync.ts +0 -8
  42. package/src/storages/dataLoader.ts +63 -32
  43. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -10
  44. package/src/storages/inLocalStorage/index.ts +6 -2
  45. package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
  46. package/src/storages/types.ts +1 -6
  47. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +3 -7
  48. package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -11
  49. package/src/types.ts +16 -9
  50. package/src/utils/settingsValidation/storage/storageCS.ts +0 -13
  51. package/types/sdkClient/identity.d.ts +0 -4
  52. package/types/sdkClient/sdkClientMethod.d.ts +1 -1
  53. package/types/sdkFactory/types.d.ts +2 -1
  54. package/types/storages/AbstractMySegmentsCacheSync.d.ts +39 -0
  55. package/types/storages/AbstractSplitsCacheAsync.d.ts +0 -5
  56. package/types/storages/AbstractSplitsCacheSync.d.ts +0 -5
  57. package/types/storages/dataLoader.d.ts +17 -6
  58. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -6
  59. package/types/storages/types.d.ts +1 -4
  60. package/types/types.d.ts +15 -9
  61. package/types/utils/settingsValidation/storage/storageCS.d.ts +0 -5
@@ -3,11 +3,11 @@ import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
3
3
  import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
4
4
  import { eventTrackerFactory } from '../trackers/eventTracker';
5
5
  import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
6
- import { SplitIO } from '../types';
6
+ 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,9 +46,12 @@ 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);`
51
53
 
54
+ const clients: Record<string, IBasicClient> = {};
52
55
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
53
56
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
54
57
 
@@ -73,7 +76,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
73
76
  // splitApi is used by SyncManager and Browser signal listener
74
77
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
75
78
 
76
- const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
79
+ const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
77
80
 
78
81
  const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
79
82
  ctx.syncManager = syncManager;
@@ -105,5 +108,9 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
105
108
  Logger: createLoggerAPI(log),
106
109
 
107
110
  settings,
111
+
112
+ destroy() {
113
+ return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
114
+ }
108
115
  }, extraProps && extraProps(ctx));
109
116
  }
@@ -8,7 +8,7 @@ import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/
8
8
  import { ISyncManager } from '../sync/types';
9
9
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
10
10
  import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
11
- import { SplitIO, ISettings, IEventEmitter } from '../types';
11
+ import { SplitIO, ISettings, IEventEmitter, IBasicClient } from '../types';
12
12
 
13
13
  /**
14
14
  * Environment related dependencies.
@@ -49,6 +49,7 @@ export interface ISdkFactoryContext {
49
49
  signalListener?: ISignalListener
50
50
  splitApi?: ISplitApi
51
51
  syncManager?: ISyncManager,
52
+ clients: Record<string, IBasicClient>,
52
53
  }
53
54
 
54
55
  export interface ISdkFactoryContextSync extends ISdkFactoryContext {
@@ -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
@@ -426,7 +427,7 @@ export interface IStatusInterface extends IEventEmitter {
426
427
  * @interface IBasicClient
427
428
  * @extends IStatusInterface
428
429
  */
429
- interface IBasicClient extends IStatusInterface {
430
+ export interface IBasicClient extends IStatusInterface {
430
431
  /**
431
432
  * Flush data
432
433
  * @function flush
@@ -459,6 +460,12 @@ interface IBasicSDK {
459
460
  * @property Logger
460
461
  */
461
462
  Logger: ILoggerAPI
463
+ /**
464
+ * Destroy all the clients created by this factory.
465
+ * @function destroy
466
+ * @returns {Promise<void>}
467
+ */
468
+ destroy(): Promise<void>
462
469
  }
463
470
  /****** Exposed namespace ******/
464
471
  /**
@@ -765,21 +772,20 @@ export namespace SplitIO {
765
772
  * If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
766
773
  * @TODO configurable expiration time policy?
767
774
  */
768
- lastUpdated: number,
775
+ // lastUpdated: number,
769
776
  /**
770
777
  * Change number of the preloaded data.
771
778
  * If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
772
779
  */
773
780
  since: number,
774
781
  /**
775
- * Map of feature flags to their stringified definitions.
782
+ * List of feature flag definitions.
783
+ * @TODO rename to flags
776
784
  */
777
- splitsData: {
778
- [splitName: string]: string
779
- },
785
+ splitsData: ISplit[],
780
786
  /**
781
787
  * Optional map of user keys to their list of segments.
782
- * @TODO remove when releasing first version
788
+ * @TODO rename to memberships
783
789
  */
784
790
  mySegmentsData?: {
785
791
  [key: string]: string[]
@@ -787,9 +793,10 @@ export namespace SplitIO {
787
793
  /**
788
794
  * Optional map of segments to their stringified definitions.
789
795
  * This property is ignored if `mySegmentsData` was provided.
796
+ * @TODO rename to segments
790
797
  */
791
798
  segmentsData?: {
792
- [segmentName: string]: string
799
+ [segmentName: string]: string[]
793
800
  },
794
801
  }
795
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');
@@ -1,6 +1,2 @@
1
1
  import { SplitIO } from '../types';
2
2
  export declare function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string): string;
3
- export declare function parseInstanceId(instanceId: string): {
4
- key: SplitIO.SplitKey;
5
- trafficType?: string;
6
- };
@@ -1,6 +1,6 @@
1
1
  import { SplitIO } from '../types';
2
2
  import { ISdkFactoryContext } from '../sdkFactory/types';
3
3
  /**
4
- * Factory of client method for server-side SDKs (ISDK and IAsyncSDK)
4
+ * Factory of client method for server-side SDKs
5
5
  */
6
6
  export declare function sdkClientMethodFactory(params: ISdkFactoryContext): () => SplitIO.IClient | SplitIO.IAsyncClient;
@@ -8,7 +8,7 @@ import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/
8
8
  import { ISyncManager } from '../sync/types';
9
9
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
10
10
  import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
11
- import { SplitIO, ISettings, IEventEmitter } from '../types';
11
+ import { SplitIO, ISettings, IEventEmitter, IBasicClient } from '../types';
12
12
  /**
13
13
  * Environment related dependencies.
14
14
  */
@@ -47,6 +47,7 @@ export interface ISdkFactoryContext {
47
47
  signalListener?: ISignalListener;
48
48
  splitApi?: ISplitApi;
49
49
  syncManager?: ISyncManager;
50
+ clients: Record<string, IBasicClient>;
50
51
  }
51
52
  export interface ISdkFactoryContextSync extends ISdkFactoryContext {
52
53
  storage: IStorageSync;
@@ -0,0 +1,39 @@
1
+ import { IMySegmentsResponse } from '../dtos/types';
2
+ import { MySegmentsData } from '../sync/polling/types';
3
+ import { ISegmentsCacheSync } from './types';
4
+ /**
5
+ * This class provides a skeletal implementation of the ISegmentsCacheSync interface
6
+ * to minimize the effort required to implement this interface.
7
+ */
8
+ export declare abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync {
9
+ protected abstract addSegment(name: string): boolean;
10
+ protected abstract removeSegment(name: string): boolean;
11
+ protected abstract setChangeNumber(changeNumber?: number): boolean | void;
12
+ /**
13
+ * For server-side synchronizer: check if `key` is in `name` segment.
14
+ * For client-side synchronizer: check if `name` segment is in the cache. `key` is undefined.
15
+ */
16
+ abstract isInSegment(name: string, key?: string): boolean;
17
+ /**
18
+ * clear the cache.
19
+ */
20
+ clear(): void;
21
+ registerSegments(): boolean;
22
+ update(): boolean;
23
+ /**
24
+ * For server-side synchronizer: get the list of segments to fetch changes.
25
+ * Also used for the `seC` (segment count) telemetry stat.
26
+ */
27
+ abstract getRegisteredSegments(): string[];
28
+ /**
29
+ * Only used for the `skC`(segment keys count) telemetry stat: 1 for client-side, and total count of keys in server-side.
30
+ * @TODO for client-side it should be the number of clients, but it requires a refactor of MySegments caches to simplify the code.
31
+ */
32
+ abstract getKeysCount(): number;
33
+ abstract getChangeNumber(name: string): number;
34
+ /**
35
+ * For server-side synchronizer: the method is not used.
36
+ * For client-side synchronizer: it resets or updates the cache.
37
+ */
38
+ resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean;
39
+ }
@@ -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.