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

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 (73) hide show
  1. package/CHANGES.txt +1 -0
  2. package/cjs/readiness/readinessManager.js +7 -5
  3. package/cjs/sdkClient/sdkClientMethodCS.js +7 -4
  4. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +7 -4
  5. package/cjs/sdkFactory/index.js +33 -10
  6. package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
  7. package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
  8. package/cjs/storages/dataLoader.js +64 -32
  9. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  10. package/cjs/storages/inLocalStorage/index.js +6 -1
  11. package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
  12. package/cjs/storages/inRedis/RedisAdapter.js +1 -1
  13. package/cjs/storages/pluggable/index.js +37 -32
  14. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -7
  15. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
  16. package/cjs/trackers/eventTracker.js +11 -9
  17. package/cjs/trackers/impressionsTracker.js +15 -13
  18. package/cjs/trackers/uniqueKeysTracker.js +5 -3
  19. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -12
  20. package/esm/readiness/readinessManager.js +7 -5
  21. package/esm/sdkClient/sdkClientMethodCS.js +7 -4
  22. package/esm/sdkClient/sdkClientMethodCSWithTT.js +7 -4
  23. package/esm/sdkFactory/index.js +34 -11
  24. package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
  25. package/esm/storages/AbstractSplitsCacheSync.js +0 -7
  26. package/esm/storages/dataLoader.js +61 -30
  27. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  28. package/esm/storages/inLocalStorage/index.js +7 -2
  29. package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
  30. package/esm/storages/inRedis/RedisAdapter.js +1 -1
  31. package/esm/storages/pluggable/index.js +37 -32
  32. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -8
  33. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
  34. package/esm/trackers/eventTracker.js +11 -9
  35. package/esm/trackers/impressionsTracker.js +15 -13
  36. package/esm/trackers/uniqueKeysTracker.js +5 -3
  37. package/esm/utils/settingsValidation/storage/storageCS.js +0 -10
  38. package/package.json +1 -1
  39. package/src/readiness/readinessManager.ts +9 -7
  40. package/src/readiness/types.ts +1 -0
  41. package/src/sdkClient/sdkClientMethodCS.ts +5 -2
  42. package/src/sdkClient/sdkClientMethodCSWithTT.ts +5 -2
  43. package/src/sdkFactory/index.ts +37 -12
  44. package/src/sdkFactory/types.ts +3 -0
  45. package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
  46. package/src/storages/AbstractSplitsCacheSync.ts +0 -8
  47. package/src/storages/dataLoader.ts +62 -32
  48. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -10
  49. package/src/storages/inLocalStorage/index.ts +8 -2
  50. package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
  51. package/src/storages/inRedis/RedisAdapter.ts +1 -1
  52. package/src/storages/pluggable/index.ts +38 -33
  53. package/src/storages/types.ts +2 -6
  54. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +3 -7
  55. package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -11
  56. package/src/trackers/eventTracker.ts +10 -7
  57. package/src/trackers/impressionsTracker.ts +12 -9
  58. package/src/trackers/types.ts +1 -0
  59. package/src/trackers/uniqueKeysTracker.ts +6 -4
  60. package/src/types.ts +9 -8
  61. package/src/utils/settingsValidation/storage/storageCS.ts +0 -13
  62. package/types/readiness/types.d.ts +1 -0
  63. package/types/sdkFactory/types.d.ts +2 -0
  64. package/types/storages/AbstractSplitsCacheAsync.d.ts +0 -5
  65. package/types/storages/AbstractSplitsCacheSync.d.ts +0 -5
  66. package/types/storages/dataLoader.d.ts +17 -6
  67. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -6
  68. package/types/storages/types.d.ts +2 -4
  69. package/types/trackers/eventTracker.d.ts +1 -1
  70. package/types/trackers/impressionsTracker.d.ts +1 -1
  71. package/types/trackers/types.d.ts +1 -0
  72. package/types/types.d.ts +8 -8
  73. package/types/utils/settingsValidation/storage/storageCS.d.ts +0 -5
@@ -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
 
@@ -20,7 +20,7 @@ const DEFAULT_OPTIONS = {
20
20
  const DEFAULT_LIBRARY_OPTIONS = {
21
21
  enableOfflineQueue: false,
22
22
  connectTimeout: DEFAULT_OPTIONS.connectionTimeout,
23
- lazyConnect: false
23
+ lazyConnect: false // @TODO true to avoid side-effects on instantiation.
24
24
  };
25
25
 
26
26
  interface IRedisCommand {
@@ -1,4 +1,4 @@
1
- import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams, ITelemetryCacheAsync } from '../types';
1
+ import { IPluggableStorageWrapper, IStorageAsyncFactory, IStorageFactoryParams, ITelemetryCacheAsync } from '../types';
2
2
 
3
3
  import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { SplitsCachePluggable } from './SplitsCachePluggable';
@@ -62,11 +62,12 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
62
62
 
63
63
  const prefix = validatePrefix(options.prefix);
64
64
 
65
- function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
65
+ function PluggableStorageFactory(params: IStorageFactoryParams) {
66
66
  const { onReadyCb, settings, settings: { log, mode, sync: { impressionsMode }, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
67
67
  const metadata = metadataBuilder(settings);
68
68
  const keys = new KeyBuilderSS(prefix, metadata);
69
69
  const wrapper = wrapperAdapter(log, options.wrapper);
70
+ let connectPromise: Promise<void>;
70
71
 
71
72
  const isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
72
73
  const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
@@ -89,35 +90,6 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
89
90
  new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
90
91
  undefined;
91
92
 
92
- // Connects to wrapper and emits SDK_READY event on main client
93
- const connectPromise = wrapper.connect().then(() => {
94
- if (isSyncronizer) {
95
- // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
96
- return wrapper.get(keys.buildHashKey()).then((hash) => {
97
- const currentHash = getStorageHash(settings);
98
- if (hash !== currentHash) {
99
- log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
100
- return wrapper.getKeysByPrefix(`${keys.prefix}.`).then(storageKeys => {
101
- return Promise.all(storageKeys.map(storageKey => wrapper.del(storageKey)));
102
- }).then(() => wrapper.set(keys.buildHashKey(), currentHash));
103
- }
104
- }).then(() => {
105
- onReadyCb();
106
- });
107
- } else {
108
- // Start periodic flush of async storages if not running synchronizer (producer mode)
109
- if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
110
- if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
111
- if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
112
-
113
- onReadyCb();
114
- }
115
- }).catch((e) => {
116
- e = e || new Error('Error connecting wrapper');
117
- onReadyCb(e);
118
- return e; // Propagate error for shared clients
119
- });
120
-
121
93
  return {
122
94
  splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
123
95
  segments: new SegmentsCachePluggable(log, keys, wrapper),
@@ -127,6 +99,39 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
127
99
  telemetry,
128
100
  uniqueKeys: uniqueKeysCache,
129
101
 
102
+ init() {
103
+ if (connectPromise) return connectPromise;
104
+
105
+ // Connects to wrapper and emits SDK_READY event on main client
106
+ return connectPromise = wrapper.connect().then(() => {
107
+ if (isSyncronizer) {
108
+ // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
109
+ return wrapper.get(keys.buildHashKey()).then((hash) => {
110
+ const currentHash = getStorageHash(settings);
111
+ if (hash !== currentHash) {
112
+ log.info(LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
113
+ return wrapper.getKeysByPrefix(`${keys.prefix}.`).then(storageKeys => {
114
+ return Promise.all(storageKeys.map(storageKey => wrapper.del(storageKey)));
115
+ }).then(() => wrapper.set(keys.buildHashKey(), currentHash));
116
+ }
117
+ }).then(() => {
118
+ onReadyCb();
119
+ });
120
+ } else {
121
+ // Start periodic flush of async storages if not running synchronizer (producer mode)
122
+ if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
123
+ if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
124
+ if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
125
+
126
+ onReadyCb();
127
+ }
128
+ }).catch((e) => {
129
+ e = e || new Error('Error connecting wrapper');
130
+ onReadyCb(e);
131
+ return e; // Propagate error for shared clients
132
+ });
133
+ },
134
+
130
135
  // Stop periodic flush and disconnect the underlying storage
131
136
  destroy() {
132
137
  return Promise.all(isSyncronizer ? [] : [
@@ -136,8 +141,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
136
141
  },
137
142
 
138
143
  // emits SDK_READY event on shared clients and returns a reference to the storage
139
- shared(_, onReadyCb) {
140
- connectPromise.then(onReadyCb);
144
+ shared(_: string, onReadyCb: (error?: any) => void) {
145
+ this.init().then(onReadyCb);
141
146
 
142
147
  return {
143
148
  ...this,
@@ -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
  }
@@ -466,6 +462,7 @@ export interface IStorageBase<
466
462
  events: TEventsCache,
467
463
  telemetry?: TTelemetryCache,
468
464
  uniqueKeys?: TUniqueKeysCache,
465
+ init?: () => void | Promise<void>,
469
466
  destroy(): void | Promise<void>,
470
467
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
471
468
  }
@@ -495,8 +492,6 @@ export interface IStorageAsync extends IStorageBase<
495
492
 
496
493
  /** StorageFactory */
497
494
 
498
- export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
499
-
500
495
  export interface IStorageFactoryParams {
501
496
  settings: ISettings,
502
497
  /**
@@ -504,6 +499,7 @@ export interface IStorageFactoryParams {
504
499
  * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
505
500
  */
506
501
  onReadyCb: (error?: any) => void,
502
+ onReadyFromCacheCb: (error?: any) => void,
507
503
  }
508
504
 
509
505
  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
@@ -16,6 +16,7 @@ import { isConsumerMode } from '../utils/settingsValidation/mode';
16
16
  export function eventTrackerFactory(
17
17
  settings: ISettings,
18
18
  eventsCache: IEventsCacheBase,
19
+ whenInit: (cb: () => void) => void,
19
20
  integrationsManager?: IEventsHandler,
20
21
  telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync
21
22
  ): IEventTracker {
@@ -32,13 +33,15 @@ export function eventTrackerFactory(
32
33
  log.info(EVENTS_TRACKER_SUCCESS, [msg]);
33
34
  if (integrationsManager) {
34
35
  // Wrap in a timeout because we don't want it to be blocking.
35
- setTimeout(function () {
36
- // copy of event, to avoid unexpected behaviour if modified by integrations
37
- const eventDataCopy = objectAssign({}, eventData);
38
- if (properties) eventDataCopy.properties = objectAssign({}, properties);
39
- // integrationsManager does not throw errors (they are internally handled by each integration module)
40
- integrationsManager.handleEvent(eventDataCopy);
41
- }, 0);
36
+ whenInit(() => {
37
+ setTimeout(() => {
38
+ // copy of event, to avoid unexpected behaviour if modified by integrations
39
+ const eventDataCopy = objectAssign({}, eventData);
40
+ if (properties) eventDataCopy.properties = objectAssign({}, properties);
41
+ // integrationsManager does not throw errors (they are internally handled by each integration module)
42
+ integrationsManager.handleEvent(eventDataCopy);
43
+ });
44
+ });
42
45
  }
43
46
  } else {
44
47
  log.error(ERROR_EVENTS_TRACKER, [msg]);
@@ -19,6 +19,7 @@ export function impressionsTrackerFactory(
19
19
  settings: ISettings,
20
20
  impressionsCache: IImpressionsCacheBase,
21
21
  strategy: IStrategy,
22
+ whenInit: (cb: () => void) => void,
22
23
  integrationsManager?: IImpressionsHandler,
23
24
  telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync,
24
25
  ): IImpressionsTracker {
@@ -67,16 +68,18 @@ export function impressionsTrackerFactory(
67
68
  };
68
69
 
69
70
  // Wrap in a timeout because we don't want it to be blocking.
70
- setTimeout(function () {
71
- // integrationsManager.handleImpression does not throw errors
72
- if (integrationsManager) integrationsManager.handleImpression(impressionData);
71
+ whenInit(() => {
72
+ setTimeout(() => {
73
+ // integrationsManager.handleImpression does not throw errors
74
+ if (integrationsManager) integrationsManager.handleImpression(impressionData);
73
75
 
74
- try { // @ts-ignore. An exception on the listeners should not break the SDK.
75
- if (impressionListener) impressionListener.logImpression(impressionData);
76
- } catch (err) {
77
- log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
78
- }
79
- }, 0);
76
+ try { // @ts-ignore. An exception on the listeners should not break the SDK.
77
+ if (impressionListener) impressionListener.logImpression(impressionData);
78
+ } catch (err) {
79
+ log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
80
+ }
81
+ });
82
+ });
80
83
  }
81
84
  }
82
85
  }
@@ -65,6 +65,7 @@ export interface IImpressionSenderAdapter {
65
65
 
66
66
  /** Unique keys tracker */
67
67
  export interface IUniqueKeysTracker {
68
+ start(): void;
68
69
  stop(): void;
69
70
  track(key: string, featureName: string): void;
70
71
  }
@@ -25,10 +25,6 @@ export function uniqueKeysTrackerFactory(
25
25
  ): IUniqueKeysTracker {
26
26
  let intervalId: any;
27
27
 
28
- if (filterAdapter.refreshRate) {
29
- intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
30
- }
31
-
32
28
  return {
33
29
 
34
30
  track(key: string, featureName: string): void {
@@ -39,6 +35,12 @@ export function uniqueKeysTrackerFactory(
39
35
  uniqueKeysCache.track(key, featureName);
40
36
  },
41
37
 
38
+ start(): void {
39
+ if (filterAdapter.refreshRate) {
40
+ intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
41
+ }
42
+ },
43
+
42
44
  stop(): void {
43
45
  clearInterval(intervalId);
44
46
  }
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');
@@ -46,6 +46,7 @@ export interface IReadinessManager {
46
46
  timeout(): void;
47
47
  setDestroyed(): void;
48
48
  destroy(): void;
49
+ init(): void;
49
50
  /** for client-side */
50
51
  shared(): IReadinessManager;
51
52
  }
@@ -48,6 +48,7 @@ export interface ISdkFactoryContext {
48
48
  splitApi?: ISplitApi;
49
49
  syncManager?: ISyncManager;
50
50
  clients: Record<string, IBasicClient>;
51
+ whenInit(cb: () => void): void;
51
52
  }
52
53
  export interface ISdkFactoryContextSync extends ISdkFactoryContext {
53
54
  storage: IStorageSync;
@@ -63,6 +64,7 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
63
64
  * Object parameter with the modules required to create an SDK factory instance
64
65
  */
65
66
  export interface ISdkFactoryParams {
67
+ isPure?: boolean;
66
68
  settings: ISettings;
67
69
  platform: IPlatform;
68
70
  storageFactory: (params: IStorageFactoryParams) => IStorageSync | IStorageAsync;
@@ -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
+ }, matchingKey?: string): void;
21
+ export declare function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): 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
  }
@@ -385,6 +382,7 @@ export interface IStorageBase<TSplitsCache extends ISplitsCacheBase, TSegmentsCa
385
382
  events: TEventsCache;
386
383
  telemetry?: TTelemetryCache;
387
384
  uniqueKeys?: TUniqueKeysCache;
385
+ init?: () => void | Promise<void>;
388
386
  destroy(): void | Promise<void>;
389
387
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this;
390
388
  }
@@ -394,7 +392,6 @@ export interface IStorageSync extends IStorageBase<ISplitsCacheSync, ISegmentsCa
394
392
  export interface IStorageAsync extends IStorageBase<ISplitsCacheAsync, ISegmentsCacheAsync, IImpressionsCacheAsync | IImpressionsCacheSync, IImpressionCountsCacheBase, IEventsCacheAsync | IEventsCacheSync, ITelemetryCacheAsync | ITelemetryCacheSync, IUniqueKeysCacheBase> {
395
393
  }
396
394
  /** StorageFactory */
397
- export declare type DataLoader = (storage: IStorageSync, matchingKey: string) => void;
398
395
  export interface IStorageFactoryParams {
399
396
  settings: ISettings;
400
397
  /**
@@ -402,6 +399,7 @@ export interface IStorageFactoryParams {
402
399
  * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
403
400
  */
404
401
  onReadyCb: (error?: any) => void;
402
+ onReadyFromCacheCb: (error?: any) => void;
405
403
  }
406
404
  export declare type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
407
405
  export declare type IStorageSyncFactory = {
@@ -7,4 +7,4 @@ import { ISettings } from '../types';
7
7
  * @param eventsCache cache to save events
8
8
  * @param integrationsManager optional event handler used for integrations
9
9
  */
10
- export declare function eventTrackerFactory(settings: ISettings, eventsCache: IEventsCacheBase, integrationsManager?: IEventsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync): IEventTracker;
10
+ export declare function eventTrackerFactory(settings: ISettings, eventsCache: IEventsCacheBase, whenInit: (cb: () => void) => void, integrationsManager?: IEventsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync): IEventTracker;
@@ -10,4 +10,4 @@ import { ISettings } from '../types';
10
10
  * @param integrationsManager optional integrations manager
11
11
  * @param strategy strategy for impressions tracking.
12
12
  */
13
- export declare function impressionsTrackerFactory(settings: ISettings, impressionsCache: IImpressionsCacheBase, strategy: IStrategy, integrationsManager?: IImpressionsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync): IImpressionsTracker;
13
+ export declare function impressionsTrackerFactory(settings: ISettings, impressionsCache: IImpressionsCacheBase, strategy: IStrategy, whenInit: (cb: () => void) => void, integrationsManager?: IImpressionsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync): IImpressionsTracker;
@@ -54,6 +54,7 @@ export interface IImpressionSenderAdapter {
54
54
  }
55
55
  /** Unique keys tracker */
56
56
  export interface IUniqueKeysTracker {
57
+ start(): void;
57
58
  stop(): void;
58
59
  track(key: string, featureName: string): void;
59
60
  }