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

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 (107) hide show
  1. package/CHANGES.txt +4 -3
  2. package/cjs/readiness/readinessManager.js +13 -4
  3. package/cjs/sdkClient/sdkClientMethodCS.js +3 -7
  4. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +3 -7
  5. package/cjs/sdkFactory/index.js +8 -13
  6. package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
  7. package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
  8. package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
  9. package/cjs/storages/dataLoader.js +32 -64
  10. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
  11. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
  12. package/cjs/storages/inLocalStorage/index.js +1 -6
  13. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -17
  14. package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
  15. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
  16. package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
  17. package/cjs/storages/inRedis/RedisAdapter.js +1 -1
  18. package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
  19. package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
  20. package/cjs/storages/pluggable/index.js +32 -37
  21. package/cjs/sync/offline/syncManagerOffline.js +18 -11
  22. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +7 -2
  23. package/cjs/sync/polling/pollingManagerSS.js +3 -3
  24. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
  25. package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
  26. package/cjs/sync/syncManagerOnline.js +20 -21
  27. package/cjs/trackers/eventTracker.js +1 -1
  28. package/cjs/trackers/impressionsTracker.js +1 -1
  29. package/cjs/utils/settingsValidation/storage/storageCS.js +12 -1
  30. package/esm/readiness/readinessManager.js +13 -4
  31. package/esm/sdkClient/sdkClientMethodCS.js +3 -7
  32. package/esm/sdkClient/sdkClientMethodCSWithTT.js +3 -7
  33. package/esm/sdkFactory/index.js +9 -14
  34. package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
  35. package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
  36. package/esm/storages/AbstractSplitsCacheSync.js +7 -0
  37. package/esm/storages/dataLoader.js +30 -61
  38. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
  39. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
  40. package/esm/storages/inLocalStorage/index.js +2 -7
  41. package/esm/storages/inMemory/InMemoryStorageCS.js +5 -17
  42. package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
  43. package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
  44. package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
  45. package/esm/storages/inRedis/RedisAdapter.js +1 -1
  46. package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
  47. package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
  48. package/esm/storages/pluggable/index.js +32 -37
  49. package/esm/sync/offline/syncManagerOffline.js +18 -11
  50. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +8 -3
  51. package/esm/sync/polling/pollingManagerSS.js +3 -3
  52. package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
  53. package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
  54. package/esm/sync/syncManagerOnline.js +20 -21
  55. package/esm/trackers/eventTracker.js +1 -1
  56. package/esm/trackers/impressionsTracker.js +1 -1
  57. package/esm/utils/settingsValidation/storage/storageCS.js +10 -0
  58. package/package.json +1 -1
  59. package/src/readiness/readinessManager.ts +11 -4
  60. package/src/readiness/types.ts +2 -0
  61. package/src/sdkClient/sdkClientMethodCS.ts +1 -6
  62. package/src/sdkClient/sdkClientMethodCSWithTT.ts +1 -6
  63. package/src/sdkFactory/index.ts +9 -15
  64. package/src/sdkFactory/types.ts +1 -2
  65. package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
  66. package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
  67. package/src/storages/AbstractSplitsCacheSync.ts +8 -0
  68. package/src/storages/dataLoader.ts +32 -62
  69. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
  70. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -1
  71. package/src/storages/inLocalStorage/index.ts +2 -8
  72. package/src/storages/inMemory/InMemoryStorageCS.ts +5 -20
  73. package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
  74. package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
  75. package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
  76. package/src/storages/inRedis/RedisAdapter.ts +1 -1
  77. package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
  78. package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
  79. package/src/storages/pluggable/index.ts +33 -38
  80. package/src/storages/types.ts +9 -11
  81. package/src/sync/offline/syncManagerOffline.ts +21 -13
  82. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +7 -3
  83. package/src/sync/polling/pollingManagerSS.ts +2 -3
  84. package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
  85. package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -3
  86. package/src/sync/syncManagerOnline.ts +17 -17
  87. package/src/sync/types.ts +1 -1
  88. package/src/trackers/eventTracker.ts +1 -1
  89. package/src/trackers/impressionsTracker.ts +1 -1
  90. package/src/types.ts +8 -9
  91. package/src/utils/settingsValidation/storage/storageCS.ts +13 -0
  92. package/types/readiness/types.d.ts +2 -0
  93. package/types/sdkFactory/types.d.ts +1 -2
  94. package/types/storages/AbstractSplitsCacheAsync.d.ts +5 -0
  95. package/types/storages/AbstractSplitsCacheSync.d.ts +5 -0
  96. package/types/storages/dataLoader.d.ts +6 -17
  97. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
  98. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +6 -0
  99. package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
  100. package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
  101. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
  102. package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
  103. package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
  104. package/types/storages/types.d.ts +7 -11
  105. package/types/sync/types.d.ts +1 -1
  106. package/types/types.d.ts +8 -8
  107. package/types/utils/settingsValidation/storage/storageCS.d.ts +5 -0
@@ -1,4 +1,4 @@
1
- import { IPluggableStorageWrapper, IStorageAsyncFactory, IStorageFactoryParams, ITelemetryCacheAsync } from '../types';
1
+ import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams, ITelemetryCacheAsync } from '../types';
2
2
 
3
3
  import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { SplitsCachePluggable } from './SplitsCachePluggable';
@@ -62,12 +62,11 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
62
62
 
63
63
  const prefix = validatePrefix(options.prefix);
64
64
 
65
- function PluggableStorageFactory(params: IStorageFactoryParams) {
65
+ function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
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>;
71
70
 
72
71
  const isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
73
72
  const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
@@ -90,6 +89,35 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
90
89
  new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
91
90
  undefined;
92
91
 
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
+
93
121
  return {
94
122
  splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
95
123
  segments: new SegmentsCachePluggable(log, keys, wrapper),
@@ -99,39 +127,6 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
99
127
  telemetry,
100
128
  uniqueKeys: uniqueKeysCache,
101
129
 
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
-
135
130
  // Stop periodic flush and disconnect the underlying storage
136
131
  destroy() {
137
132
  return Promise.all(isSyncronizer ? [] : [
@@ -141,8 +136,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
141
136
  },
142
137
 
143
138
  // emits SDK_READY event on shared clients and returns a reference to the storage
144
- shared(_: string, onReadyCb: (error?: any) => void) {
145
- this.init().then(onReadyCb);
139
+ shared(_, onReadyCb) {
140
+ connectPromise.then(onReadyCb);
146
141
 
147
142
  return {
148
143
  ...this,
@@ -208,6 +208,8 @@ 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>,
211
213
  killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
212
214
  getNamesByFlagSets(flagSets: string[]): MaybeThenable<ISet<string>[]>
213
215
  }
@@ -224,6 +226,7 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
224
226
  trafficTypeExists(trafficType: string): boolean,
225
227
  usesSegments(): boolean,
226
228
  clear(): void,
229
+ checkCache(): boolean,
227
230
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
228
231
  getNamesByFlagSets(flagSets: string[]): ISet<string>[]
229
232
  }
@@ -240,6 +243,7 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
240
243
  trafficTypeExists(trafficType: string): Promise<boolean>,
241
244
  usesSegments(): Promise<boolean>,
242
245
  clear(): Promise<boolean | void>,
246
+ checkCache(): Promise<boolean>,
243
247
  killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
244
248
  getNamesByFlagSets(flagSets: string[]): Promise<ISet<string>[]>
245
249
  }
@@ -247,38 +251,32 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
247
251
  /** Segments cache */
248
252
 
249
253
  export interface ISegmentsCacheBase {
250
- addToSegment(name: string, segmentKeys: string[]): MaybeThenable<boolean | void> // different signature on Server and Client-Side
251
- removeFromSegment(name: string, segmentKeys: string[]): MaybeThenable<boolean | void> // different signature on Server and Client-Side
252
254
  isInSegment(name: string, key?: string): MaybeThenable<boolean> // different signature on Server and Client-Side
253
255
  registerSegments(names: string[]): MaybeThenable<boolean | void> // only for Server-Side
254
256
  getRegisteredSegments(): MaybeThenable<string[]> // only for Server-Side
255
- setChangeNumber(name: string, changeNumber: number): MaybeThenable<boolean | void> // only for Server-Side
256
257
  getChangeNumber(name: string): MaybeThenable<number> // only for Server-Side
258
+ update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): MaybeThenable<boolean> // only for Server-Side
257
259
  clear(): MaybeThenable<boolean | void>
258
260
  }
259
261
 
260
262
  // Same API for both variants: SegmentsCache and MySegmentsCache (client-side API)
261
263
  export interface ISegmentsCacheSync extends ISegmentsCacheBase {
262
- addToSegment(name: string, segmentKeys?: string[]): boolean
263
- removeFromSegment(name: string, segmentKeys?: string[]): boolean
264
264
  isInSegment(name: string, key?: string): boolean
265
265
  registerSegments(names: string[]): boolean
266
266
  getRegisteredSegments(): string[]
267
267
  getKeysCount(): number // only used for telemetry
268
- setChangeNumber(name: string, changeNumber: number): boolean | void
269
268
  getChangeNumber(name?: string): number
269
+ update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): boolean // only for Server-Side
270
270
  resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean // only for Sync Client-Side
271
271
  clear(): void
272
272
  }
273
273
 
274
274
  export interface ISegmentsCacheAsync extends ISegmentsCacheBase {
275
- addToSegment(name: string, segmentKeys: string[]): Promise<boolean | void>
276
- removeFromSegment(name: string, segmentKeys: string[]): Promise<boolean | void>
277
275
  isInSegment(name: string, key: string): Promise<boolean>
278
276
  registerSegments(names: string[]): Promise<boolean | void>
279
277
  getRegisteredSegments(): Promise<string[]>
280
- setChangeNumber(name: string, changeNumber: number): Promise<boolean | void>
281
278
  getChangeNumber(name: string): Promise<number>
279
+ update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number): Promise<boolean>
282
280
  clear(): Promise<boolean | void>
283
281
  }
284
282
 
@@ -462,7 +460,6 @@ export interface IStorageBase<
462
460
  events: TEventsCache,
463
461
  telemetry?: TTelemetryCache,
464
462
  uniqueKeys?: TUniqueKeysCache,
465
- init?: () => void | Promise<void>,
466
463
  destroy(): void | Promise<void>,
467
464
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
468
465
  }
@@ -492,6 +489,8 @@ export interface IStorageAsync extends IStorageBase<
492
489
 
493
490
  /** StorageFactory */
494
491
 
492
+ export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
493
+
495
494
  export interface IStorageFactoryParams {
496
495
  settings: ISettings,
497
496
  /**
@@ -499,7 +498,6 @@ export interface IStorageFactoryParams {
499
498
  * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
500
499
  */
501
500
  onReadyCb: (error?: any) => void,
502
- onReadyFromCacheCb: (error?: any) => void,
503
501
  }
504
502
 
505
503
  export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
@@ -1,4 +1,4 @@
1
- import { ISyncManager, ISyncManagerCS } from '../types';
1
+ import { ISyncManagerCS } from '../types';
2
2
  import { fromObjectSyncTaskFactory } from './syncTasks/fromObjectSyncTask';
3
3
  import { objectAssign } from '../../utils/lang/objectAssign';
4
4
  import { ISplitsParser } from './splitsParser/types';
@@ -29,26 +29,34 @@ export function syncManagerOfflineFactory(
29
29
  storage,
30
30
  }: ISdkFactoryContextSync): ISyncManagerCS {
31
31
 
32
+ const mainSyncManager = fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings);
33
+ const mainStart = mainSyncManager.start;
34
+ const sharedStarts: Array<() => void> = [];
35
+
32
36
  return objectAssign(
33
- fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings),
37
+ mainSyncManager,
34
38
  {
39
+ start() {
40
+ mainStart();
41
+ sharedStarts.forEach(cb => cb());
42
+ sharedStarts.length = 0;
43
+ },
35
44
  // fake flush, that resolves immediately
36
45
  flush,
37
46
 
38
47
  // [Only used for client-side]
39
- shared(matchingKey: string, readinessManager: IReadinessManager): ISyncManager {
48
+ shared(matchingKey: string, readinessManager: IReadinessManager) {
49
+ // In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
50
+ // SDK_READY cannot be emitted directly because this will not update the readiness status
51
+ function emitSdkReady() {
52
+ readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
53
+ }
54
+
55
+ if (mainSyncManager.isRunning()) setTimeout(emitSdkReady);
56
+ else sharedStarts.push(emitSdkReady);
57
+
40
58
  return {
41
- start() {
42
- // In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
43
- // SDK_READY cannot be emitted directly because this will not update the readiness status
44
- setTimeout(() => {
45
- readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
46
- }, 0);
47
- },
48
59
  stop() { },
49
- isRunning() {
50
- return true;
51
- },
52
60
  flush,
53
61
  };
54
62
  }
@@ -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 } from '../../../readiness/constants';
10
+ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
11
11
  import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
12
12
 
13
13
  /**
@@ -60,8 +60,12 @@ export function fromObjectUpdaterFactory(
60
60
 
61
61
  if (startingUp) {
62
62
  startingUp = false;
63
- // Emits SDK_READY
64
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
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
+ });
65
69
  }
66
70
  return true;
67
71
  });
@@ -1,7 +1,6 @@
1
1
  import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
2
2
  import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask';
3
3
  import { IPollingManager, ISegmentsSyncTask, ISplitsSyncTask } from './types';
4
- import { thenable } from '../../utils/promise/thenable';
5
4
  import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants';
6
5
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
7
6
 
@@ -29,9 +28,9 @@ export function pollingManagerSSFactory(
29
28
  log.debug(LOG_PREFIX_SYNC_POLLING + `Segments will be refreshed each ${settings.scheduler.segmentsRefreshRate} millis`);
30
29
 
31
30
  const startingUp = splitsSyncTask.start();
32
- if (thenable(startingUp)) {
31
+ if (startingUp) {
33
32
  startingUp.then(() => {
34
- segmentsSyncTask.start();
33
+ if (splitsSyncTask.isRunning()) segmentsSyncTask.start();
35
34
  });
36
35
  }
37
36
  },
@@ -1,12 +1,9 @@
1
1
  import { ISegmentChangesFetcher } from '../fetchers/types';
2
2
  import { ISegmentsCacheBase } from '../../../storages/types';
3
3
  import { IReadinessManager } from '../../../readiness/types';
4
- import { MaybeThenable } from '../../../dtos/types';
5
- import { findIndex } from '../../../utils/lang';
6
4
  import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
7
5
  import { ILogger } from '../../../logger/types';
8
6
  import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
9
- import { thenable } from '../../../utils/promise/thenable';
10
7
 
11
8
  type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise<boolean>
12
9
 
@@ -30,31 +27,22 @@ export function segmentChangesUpdaterFactory(
30
27
 
31
28
  let readyOnAlreadyExistentState = true;
32
29
 
33
- function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean) {
30
+ function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean): Promise<boolean> {
34
31
  log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
35
32
  let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
36
33
 
37
34
  return sincePromise.then(since => {
38
35
  // if fetchOnlyNew flag, avoid processing already fetched segments
39
- if (fetchOnlyNew && since !== -1) return -1;
40
-
41
- return segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
42
- let changeNumber = -1;
43
- const results: MaybeThenable<boolean | void>[] = [];
44
- changes.forEach(x => {
45
- if (x.added.length > 0) results.push(segments.addToSegment(segmentName, x.added));
46
- if (x.removed.length > 0) results.push(segments.removeFromSegment(segmentName, x.removed));
47
- if (x.added.length > 0 || x.removed.length > 0) {
48
- results.push(segments.setChangeNumber(segmentName, x.till));
49
- changeNumber = x.till;
50
- }
51
-
52
- log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processed ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
36
+ return fetchOnlyNew && since !== -1 ?
37
+ false :
38
+ segmentChangesFetcher(since, segmentName, noCache, till).then((changes) => {
39
+ return Promise.all(changes.map(x => {
40
+ log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
41
+ return segments.update(x.name, x.added, x.removed, x.till);
42
+ })).then((updates) => {
43
+ return updates.some(update => update);
44
+ });
53
45
  });
54
- // If at least one storage operation result is a promise, join all in a single promise.
55
- if (results.some(result => thenable(result))) return Promise.all(results).then(() => changeNumber);
56
- return changeNumber;
57
- });
58
46
  });
59
47
  }
60
48
  /**
@@ -75,16 +63,12 @@ export function segmentChangesUpdaterFactory(
75
63
  let segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
76
64
 
77
65
  return segmentsPromise.then(segmentNames => {
78
- // Async fetchers are collected here.
79
- const updaters: Promise<number>[] = [];
80
-
81
- for (let index = 0; index < segmentNames.length; index++) {
82
- updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
83
- }
66
+ // Async fetchers
67
+ const updaters = segmentNames.map(segmentName => updateSegment(segmentName, noCache, till, fetchOnlyNew));
84
68
 
85
69
  return Promise.all(updaters).then(shouldUpdateFlags => {
86
70
  // if at least one segment fetch succeeded, mark segments ready
87
- if (findIndex(shouldUpdateFlags, v => v !== -1) !== -1 || readyOnAlreadyExistentState) {
71
+ if (shouldUpdateFlags.some(update => update) || readyOnAlreadyExistentState) {
88
72
  readyOnAlreadyExistentState = false;
89
73
  if (readiness) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
90
74
  }
@@ -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 } from '../../../readiness/constants';
7
+ import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } 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,8 +153,7 @@ export function splitChangesUpdaterFactory(
153
153
  */
154
154
  function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
155
155
  log.debug(SYNC_SPLITS_FETCH, [since]);
156
-
157
- return Promise.resolve(splitUpdateNotification ?
156
+ const fetcherPromise = Promise.resolve(splitUpdateNotification ?
158
157
  { splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
159
158
  splitChangesFetcher(since, noCache, till, _promiseDecorator)
160
159
  )
@@ -201,6 +200,15 @@ export function splitChangesUpdaterFactory(
201
200
  }
202
201
  return false;
203
202
  });
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;
204
212
  }
205
213
 
206
214
  let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
@@ -143,27 +143,27 @@ export function syncManagerOnlineFactory(
143
143
 
144
144
  const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).add(matchingKey, readinessManager, storage);
145
145
 
146
- return {
147
- isRunning: mySegmentsSyncTask.isRunning,
148
- start() {
149
- if (syncEnabled) {
150
- if (pushManager) {
151
- if (pollingManager!.isRunning()) {
152
- // if doing polling, we must start the periodic fetch of data
153
- if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
154
- } else {
155
- // if not polling, we must execute the sync task for the initial fetch
156
- // of segments since `syncAll` was already executed when starting the main client
157
- mySegmentsSyncTask.execute();
158
- }
159
- pushManager.add(matchingKey, mySegmentsSyncTask);
160
- } else {
146
+ if (running) {
147
+ if (syncEnabled) {
148
+ if (pushManager) {
149
+ if (pollingManager!.isRunning()) {
150
+ // if doing polling, we must start the periodic fetch of data
161
151
  if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
152
+ } else {
153
+ // if not polling, we must execute the sync task for the initial fetch
154
+ // of segments since `syncAll` was already executed when starting the main client
155
+ mySegmentsSyncTask.execute();
162
156
  }
157
+ pushManager.add(matchingKey, mySegmentsSyncTask);
163
158
  } else {
164
- if (!readinessManager.isReady()) mySegmentsSyncTask.execute();
159
+ if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
165
160
  }
166
- },
161
+ } else {
162
+ if (!readinessManager.isReady()) mySegmentsSyncTask.execute();
163
+ }
164
+ }
165
+
166
+ return {
167
167
  stop() {
168
168
  // check in case `client.destroy()` has been invoked more than once for the same client
169
169
  const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).get(matchingKey);
package/src/sync/types.ts CHANGED
@@ -44,5 +44,5 @@ export interface ISyncManager extends ITask {
44
44
  }
45
45
 
46
46
  export interface ISyncManagerCS extends ISyncManager {
47
- shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager | undefined
47
+ shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): Pick<ISyncManager, 'stop' | 'flush'> | undefined
48
48
  }
@@ -32,8 +32,8 @@ export function eventTrackerFactory(
32
32
  if (tracked) {
33
33
  log.info(EVENTS_TRACKER_SUCCESS, [msg]);
34
34
  if (integrationsManager) {
35
- // Wrap in a timeout because we don't want it to be blocking.
36
35
  whenInit(() => {
36
+ // Wrap in a timeout because we don't want it to be blocking.
37
37
  setTimeout(() => {
38
38
  // copy of event, to avoid unexpected behaviour if modified by integrations
39
39
  const eventDataCopy = objectAssign({}, eventData);
@@ -67,8 +67,8 @@ export function impressionsTrackerFactory(
67
67
  sdkLanguageVersion: version
68
68
  };
69
69
 
70
- // Wrap in a timeout because we don't want it to be blocking.
71
70
  whenInit(() => {
71
+ // Wrap in a timeout because we don't want it to be blocking.
72
72
  setTimeout(() => {
73
73
  // integrationsManager.handleImpression does not throw errors
74
74
  if (integrationsManager) integrationsManager.handleImpression(impressionData);
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ISplit, ISplitFiltersValidation } from './dtos/types';
1
+ import { 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,7 +98,6 @@ export interface ISettings {
98
98
  eventsFirstPushWindow: number
99
99
  },
100
100
  readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
101
- readonly preloadedData?: SplitIO.PreloadedData,
102
101
  readonly integrations: Array<{
103
102
  readonly type: string,
104
103
  (params: IIntegrationFactoryParams): IIntegration | void
@@ -772,20 +771,21 @@ export namespace SplitIO {
772
771
  * If this value is older than 10 days ago (expiration time policy), the data is not used to update the storage content.
773
772
  * @TODO configurable expiration time policy?
774
773
  */
775
- // lastUpdated: number,
774
+ lastUpdated: number,
776
775
  /**
777
776
  * Change number of the preloaded data.
778
777
  * If this value is older than the current changeNumber at the storage, the data is not used to update the storage content.
779
778
  */
780
779
  since: number,
781
780
  /**
782
- * List of feature flag definitions.
783
- * @TODO rename to flags
781
+ * Map of feature flags to their stringified definitions.
784
782
  */
785
- splitsData: ISplit[],
783
+ splitsData: {
784
+ [splitName: string]: string
785
+ },
786
786
  /**
787
787
  * Optional map of user keys to their list of segments.
788
- * @TODO rename to memberships
788
+ * @TODO remove when releasing first version
789
789
  */
790
790
  mySegmentsData?: {
791
791
  [key: string]: string[]
@@ -793,10 +793,9 @@ 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
797
796
  */
798
797
  segmentsData?: {
799
- [segmentName: string]: string[]
798
+ [segmentName: string]: string
800
799
  },
801
800
  }
802
801
  /**
@@ -3,6 +3,14 @@ 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;
6
14
 
7
15
  /**
8
16
  * This function validates `settings.storage` object
@@ -22,6 +30,11 @@ export function validateStorageCS(settings: { log: ILogger, storage?: any, mode:
22
30
  log.error(ERROR_STORAGE_INVALID);
23
31
  }
24
32
 
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
+
25
38
  if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
26
39
  // Consumer modes require an async storage
27
40
  if (storage.type !== STORAGE_PLUGGABLE) throw new Error('A PluggableStorage instance is required on consumer mode');
@@ -9,6 +9,8 @@ export interface ISplitsEventEmitter extends IEventEmitter {
9
9
  once(event: ISplitsEvent, listener: (...args: any[]) => void): this;
10
10
  splitsArrived: boolean;
11
11
  splitsCacheLoaded: boolean;
12
+ initialized: boolean;
13
+ initCallbacks: (() => void)[];
12
14
  }
13
15
  /** Segments data emitter */
14
16
  declare type SDK_SEGMENTS_ARRIVED = 'state::segments-arrived';
@@ -48,7 +48,6 @@ export interface ISdkFactoryContext {
48
48
  splitApi?: ISplitApi;
49
49
  syncManager?: ISyncManager;
50
50
  clients: Record<string, IBasicClient>;
51
- whenInit(cb: () => void): void;
52
51
  }
53
52
  export interface ISdkFactoryContextSync extends ISdkFactoryContext {
54
53
  storage: IStorageSync;
@@ -64,7 +63,7 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
64
63
  * Object parameter with the modules required to create an SDK factory instance
65
64
  */
66
65
  export interface ISdkFactoryParams {
67
- isPure?: boolean;
66
+ lazyInit?: boolean;
68
67
  settings: ISettings;
69
68
  platform: IPlatform;
70
69
  storageFactory: (params: IStorageFactoryParams) => IStorageSync | IStorageAsync;
@@ -19,6 +19,11 @@ 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>;
22
27
  /**
23
28
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
24
29
  * Used for SPLIT_KILL push notifications.
@@ -19,6 +19,11 @@ 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;
22
27
  /**
23
28
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
24
29
  * Used for SPLIT_KILL push notifications.
@@ -1,21 +1,10 @@
1
1
  import { SplitIO } from '../types';
2
- import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
2
+ import { DataLoader } from './types';
3
3
  /**
4
- * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
5
- * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
4
+ * Factory of client-side storage loader
6
5
  *
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
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
15
9
  */
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;
10
+ export declare function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader;