@splitsoftware/splitio-commons 1.6.2-rc.5 → 1.6.2-rc.8

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 (78) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/evaluator/index.js +10 -11
  3. package/cjs/integrations/ga/GaToSplit.js +8 -5
  4. package/cjs/sdkClient/sdkClient.js +3 -1
  5. package/cjs/sdkFactory/index.js +2 -2
  6. package/cjs/sdkManager/index.js +3 -11
  7. package/cjs/storages/AbstractSplitsCacheAsync.js +7 -9
  8. package/cjs/storages/AbstractSplitsCacheSync.js +7 -9
  9. package/cjs/storages/dataLoader.js +1 -1
  10. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +5 -6
  11. package/cjs/storages/inMemory/SplitsCacheInMemory.js +7 -10
  12. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +10 -6
  13. package/cjs/storages/inRedis/SplitsCacheInRedis.js +15 -9
  14. package/cjs/storages/inRedis/index.js +4 -3
  15. package/cjs/storages/inRedis/uniqueKeysCacheInRedis.js +11 -7
  16. package/cjs/storages/pluggable/SplitsCachePluggable.js +14 -9
  17. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  18. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -1
  19. package/cjs/trackers/strategy/strategyOptimized.js +2 -1
  20. package/cjs/trackers/telemetryTracker.js +6 -0
  21. package/cjs/trackers/uniqueKeysTracker.js +8 -1
  22. package/esm/evaluator/index.js +10 -11
  23. package/esm/integrations/ga/GaToSplit.js +8 -5
  24. package/esm/sdkClient/sdkClient.js +3 -1
  25. package/esm/sdkFactory/index.js +2 -2
  26. package/esm/sdkManager/index.js +3 -11
  27. package/esm/storages/AbstractSplitsCacheAsync.js +7 -9
  28. package/esm/storages/AbstractSplitsCacheSync.js +7 -9
  29. package/esm/storages/dataLoader.js +1 -1
  30. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +5 -6
  31. package/esm/storages/inMemory/SplitsCacheInMemory.js +7 -10
  32. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +10 -6
  33. package/esm/storages/inRedis/SplitsCacheInRedis.js +15 -9
  34. package/esm/storages/inRedis/index.js +4 -3
  35. package/esm/storages/inRedis/uniqueKeysCacheInRedis.js +11 -7
  36. package/esm/storages/pluggable/SplitsCachePluggable.js +14 -9
  37. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  38. package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -1
  39. package/esm/trackers/strategy/strategyOptimized.js +2 -1
  40. package/esm/trackers/telemetryTracker.js +6 -0
  41. package/esm/trackers/uniqueKeysTracker.js +8 -1
  42. package/package.json +1 -1
  43. package/src/evaluator/index.ts +8 -9
  44. package/src/integrations/ga/GaToSplit.ts +9 -5
  45. package/src/integrations/types.ts +2 -1
  46. package/src/sdkClient/sdkClient.ts +3 -1
  47. package/src/sdkFactory/index.ts +2 -2
  48. package/src/sdkManager/index.ts +3 -12
  49. package/src/storages/AbstractSplitsCacheAsync.ts +12 -14
  50. package/src/storages/AbstractSplitsCacheSync.ts +14 -16
  51. package/src/storages/dataLoader.ts +1 -1
  52. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +8 -10
  53. package/src/storages/inMemory/SplitsCacheInMemory.ts +10 -14
  54. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +10 -6
  55. package/src/storages/inRedis/SplitsCacheInRedis.ts +21 -17
  56. package/src/storages/inRedis/index.ts +5 -4
  57. package/src/storages/inRedis/uniqueKeysCacheInRedis.ts +11 -8
  58. package/src/storages/pluggable/SplitsCachePluggable.ts +20 -17
  59. package/src/storages/types.ts +13 -13
  60. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
  61. package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -2
  62. package/src/trackers/strategy/strategyOptimized.ts +1 -1
  63. package/src/trackers/telemetryTracker.ts +7 -2
  64. package/src/trackers/types.ts +6 -0
  65. package/src/trackers/uniqueKeysTracker.ts +13 -2
  66. package/types/integrations/types.d.ts +2 -1
  67. package/types/storages/AbstractSplitsCacheAsync.d.ts +6 -5
  68. package/types/storages/AbstractSplitsCacheSync.d.ts +5 -5
  69. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +3 -3
  70. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +3 -2
  71. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +5 -4
  72. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +6 -5
  73. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +5 -4
  74. package/types/storages/pluggable/SplitsCachePluggable.d.ts +6 -5
  75. package/types/storages/types.d.ts +13 -13
  76. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  77. package/types/trackers/types.d.ts +6 -0
  78. package/types/trackers/uniqueKeysTracker.d.ts +1 -1
@@ -10,11 +10,15 @@ var noopFilterAdapter = {
10
10
  * or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
11
11
  *
12
12
  * @param log Logger instance
13
- * @param filterAdapter filter adapter
14
13
  * @param uniqueKeysCache cache to save unique keys
14
+ * @param filterAdapter filter adapter
15
15
  */
16
16
  export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
17
17
  if (filterAdapter === void 0) { filterAdapter = noopFilterAdapter; }
18
+ var intervalId;
19
+ if (filterAdapter.refreshRate) {
20
+ intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
21
+ }
18
22
  return {
19
23
  track: function (key, featureName) {
20
24
  if (!filterAdapter.add(key, featureName)) {
@@ -22,6 +26,9 @@ export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
22
26
  return;
23
27
  }
24
28
  uniqueKeysCache.track(key, featureName);
29
+ },
30
+ stop: function () {
31
+ clearInterval(intervalId);
25
32
  }
26
33
  };
27
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.6.2-rc.5",
3
+ "version": "1.6.2-rc.8",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -68,28 +68,28 @@ export function evaluateFeatures(
68
68
  attributes: SplitIO.Attributes | undefined,
69
69
  storage: IStorageSync | IStorageAsync,
70
70
  ): MaybeThenable<Record<string, IEvaluationResult>> {
71
- let stringifiedSplits;
71
+ let parsedSplits;
72
72
 
73
73
  try {
74
- stringifiedSplits = storage.splits.getSplits(splitNames);
74
+ parsedSplits = storage.splits.getSplits(splitNames);
75
75
  } catch (e) {
76
76
  // Exception on sync `getSplits` storage. Not possible ATM with InMemory and InLocal storages.
77
77
  return treatmentsException(splitNames);
78
78
  }
79
79
 
80
- return (thenable(stringifiedSplits)) ?
81
- stringifiedSplits.then(splits => getEvaluations(log, splitNames, splits, key, attributes, storage))
80
+ return thenable(parsedSplits) ?
81
+ parsedSplits.then(splits => getEvaluations(log, splitNames, splits, key, attributes, storage))
82
82
  .catch(() => {
83
83
  // Exception on async `getSplits` storage. For example, when the storage is redis or
84
84
  // pluggable and there is a connection issue and we can't retrieve the split to be evaluated
85
85
  return treatmentsException(splitNames);
86
86
  }) :
87
- getEvaluations(log, splitNames, stringifiedSplits, key, attributes, storage);
87
+ getEvaluations(log, splitNames, parsedSplits, key, attributes, storage);
88
88
  }
89
89
 
90
90
  function getEvaluation(
91
91
  log: ILogger,
92
- stringifiedSplit: string | null,
92
+ splitJSON: ISplit | null,
93
93
  key: SplitIO.SplitKey,
94
94
  attributes: SplitIO.Attributes | undefined,
95
95
  storage: IStorageSync | IStorageAsync,
@@ -100,8 +100,7 @@ function getEvaluation(
100
100
  config: null
101
101
  };
102
102
 
103
- if (stringifiedSplit) {
104
- const splitJSON: ISplit = JSON.parse(stringifiedSplit);
103
+ if (splitJSON) {
105
104
  const split = Engine.parse(log, splitJSON, storage);
106
105
  evaluation = split.getTreatment(key, attributes, evaluateFeature);
107
106
 
@@ -125,7 +124,7 @@ function getEvaluation(
125
124
  function getEvaluations(
126
125
  log: ILogger,
127
126
  splitNames: string[],
128
- splits: Record<string, string | null>,
127
+ splits: Record<string, ISplit | null>,
129
128
  key: SplitIO.SplitKey,
130
129
  attributes: SplitIO.Attributes | undefined,
131
130
  storage: IStorageSync | IStorageAsync,
@@ -12,6 +12,7 @@ import { SplitIO } from '../../types';
12
12
  import { Identity, GoogleAnalyticsToSplitOptions } from './types';
13
13
  import { ILogger } from '../../logger/types';
14
14
  import { IIntegrationFactoryParams } from '../types';
15
+ import { ITelemetryTracker } from '../../trackers/types';
15
16
 
16
17
  const logPrefix = 'ga-to-split: ';
17
18
  const logNameMapper = 'ga-to-split:mapper';
@@ -25,7 +26,7 @@ const logNameMapper = 'ga-to-split:mapper';
25
26
  * @param log Logger instance.
26
27
  * @param autoRequire If true, log error when auto-require script is not detected
27
28
  */
28
- function providePlugin(window: any, pluginName: string, pluginConstructor: Function, log: ILogger, autoRequire?: boolean) {
29
+ function providePlugin(window: any, pluginName: string, pluginConstructor: Function, log: ILogger, autoRequire: boolean, telemetryTracker?: ITelemetryTracker) {
29
30
  // get reference to global command queue. Init it if not defined yet.
30
31
  const gaAlias = window.GoogleAnalyticsObject || 'ga';
31
32
  window[gaAlias] = window[gaAlias] || function () {
@@ -35,10 +36,13 @@ function providePlugin(window: any, pluginName: string, pluginConstructor: Funct
35
36
  // provides the plugin for use with analytics.js.
36
37
  window[gaAlias]('provide', pluginName, pluginConstructor);
37
38
 
38
- if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
39
- // Expecting spy on ga.q push method but not found
39
+ const hasAutoRequire = window[gaAlias].q && window[gaAlias].q.push !== [].push;
40
+ if (autoRequire && !hasAutoRequire) { // Expecting spy on ga.q push method but not found
40
41
  log.error(logPrefix + 'integration is configured to autorequire the splitTracker plugin, but the necessary script does not seem to have run. Please check the docs.');
41
42
  }
43
+ if (telemetryTracker && hasAutoRequire) {
44
+ telemetryTracker.addTag('integration:ga-autorequire');
45
+ }
42
46
  }
43
47
 
44
48
  // Default mapping: object used for building the default mapper from hits to Split events
@@ -191,7 +195,7 @@ export function fixEventTypeId(log: ILogger, eventTypeId: any) {
191
195
  */
192
196
  export function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, params: IIntegrationFactoryParams) {
193
197
 
194
- const { storage, settings: { core: coreSettings, log } } = params;
198
+ const { storage, settings: { core: coreSettings, log }, telemetryTracker } = params;
195
199
 
196
200
  const defaultOptions = {
197
201
  prefix: defaultPrefix,
@@ -291,5 +295,5 @@ export function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, params: IIn
291
295
  }
292
296
 
293
297
  // Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
294
- providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire === true);
298
+ providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire === true, telemetryTracker);
295
299
  }
@@ -1,5 +1,5 @@
1
1
  import { IEventsCacheBase } from '../storages/types';
2
- import { IEventsHandler, IImpressionsHandler } from '../trackers/types';
2
+ import { IEventsHandler, IImpressionsHandler, ITelemetryTracker } from '../trackers/types';
3
3
  import { ISettings, SplitIO } from '../types';
4
4
 
5
5
  export interface IIntegration {
@@ -11,6 +11,7 @@ export type IIntegrationManager = IEventsHandler & IImpressionsHandler;
11
11
  export interface IIntegrationFactoryParams {
12
12
  storage: { events: IEventsCacheBase }
13
13
  settings: ISettings
14
+ telemetryTracker: ITelemetryTracker
14
15
  }
15
16
 
16
17
  export type IntegrationFactory = {
@@ -9,7 +9,7 @@ import { ISdkFactoryContext } from '../sdkFactory/types';
9
9
  * Creates an Sdk client, i.e., a base client with status and destroy interface
10
10
  */
11
11
  export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
12
- const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker } = params;
12
+ const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params;
13
13
 
14
14
  return objectAssign(
15
15
  // Proto-linkage of the readiness Event Emitter
@@ -39,6 +39,8 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
39
39
 
40
40
  // Release the API Key if it is the main client
41
41
  if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);
42
+
43
+ if (uniqueKeysTracker) uniqueKeysTracker.stop();
42
44
 
43
45
  // Cleanup storage
44
46
  return storage.destroy();
@@ -68,7 +68,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
68
68
  const storage = storageFactory(storageFactoryParams);
69
69
  // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
70
70
 
71
- const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
71
+ const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
72
+ const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
72
73
 
73
74
  const observer = impressionsObserverFactory();
74
75
  const uniqueKeysTracker = storageFactoryParams.impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
@@ -87,7 +88,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
87
88
 
88
89
  const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
89
90
  const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
90
- const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
91
91
 
92
92
  // splitApi is used by SyncManager and Browser signal listener
93
93
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
@@ -18,16 +18,7 @@ function collectTreatments(splitObject: ISplit) {
18
18
  return allTreatmentsCondition ? allTreatmentsCondition.partitions.map(v => v.treatment) : [];
19
19
  }
20
20
 
21
- function objectToView(json: string | null): SplitIO.SplitView | null {
22
- let splitObject: ISplit | null;
23
-
24
- try {
25
- // @ts-expect-error
26
- splitObject = JSON.parse(json);
27
- } catch (e) {
28
- return null;
29
- }
30
-
21
+ function objectToView(splitObject: ISplit | null): SplitIO.SplitView | null {
31
22
  if (!splitObject) return null;
32
23
 
33
24
  return {
@@ -40,10 +31,10 @@ function objectToView(json: string | null): SplitIO.SplitView | null {
40
31
  };
41
32
  }
42
33
 
43
- function objectsToViews(jsons: string[]) {
34
+ function objectsToViews(splitObjects: ISplit[]) {
44
35
  let views: SplitIO.SplitView[] = [];
45
36
 
46
- jsons.forEach(split => {
37
+ splitObjects.forEach(split => {
47
38
  const view = objectToView(split);
48
39
  if (view) views.push(view);
49
40
  });
@@ -1,5 +1,6 @@
1
1
  import { ISplitsCacheAsync } from './types';
2
2
  import { ISplit } from '../dtos/types';
3
+ import { objectAssign } from '../utils/lang/objectAssign';
3
4
 
4
5
  /**
5
6
  * This class provides a skeletal implementation of the ISplitsCacheAsync interface
@@ -7,14 +8,14 @@ import { ISplit } from '../dtos/types';
7
8
  */
8
9
  export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
9
10
 
10
- abstract addSplit(name: string, split: string): Promise<boolean>
11
- abstract addSplits(entries: [string, string][]): Promise<boolean[] | void>
11
+ abstract addSplit(name: string, split: ISplit): Promise<boolean>
12
+ abstract addSplits(entries: [string, ISplit][]): Promise<boolean[] | void>
12
13
  abstract removeSplits(names: string[]): Promise<boolean[] | void>
13
- abstract getSplit(name: string): Promise<string | null>
14
- abstract getSplits(names: string[]): Promise<Record<string, string | null>>
14
+ abstract getSplit(name: string): Promise<ISplit | null>
15
+ abstract getSplits(names: string[]): Promise<Record<string, ISplit | null>>
15
16
  abstract setChangeNumber(changeNumber: number): Promise<boolean | void>
16
17
  abstract getChangeNumber(): Promise<number>
17
- abstract getAll(): Promise<string[]>
18
+ abstract getAll(): Promise<ISplit[]>
18
19
  abstract getSplitNames(): Promise<string[]>
19
20
  abstract trafficTypeExists(trafficType: string): Promise<boolean>
20
21
  abstract clear(): Promise<boolean | void>
@@ -47,16 +48,13 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
47
48
  killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean> {
48
49
  return this.getSplit(name).then(split => {
49
50
 
50
- if (split) {
51
- const parsedSplit: ISplit = JSON.parse(split);
52
- if (!parsedSplit.changeNumber || parsedSplit.changeNumber < changeNumber) {
53
- parsedSplit.killed = true;
54
- parsedSplit.defaultTreatment = defaultTreatment;
55
- parsedSplit.changeNumber = changeNumber;
56
- const newSplit = JSON.stringify(parsedSplit);
51
+ if (split && (!split.changeNumber || split.changeNumber < changeNumber)) {
52
+ const newSplit = objectAssign({}, split);
53
+ newSplit.killed = true;
54
+ newSplit.defaultTreatment = defaultTreatment;
55
+ newSplit.changeNumber = changeNumber;
57
56
 
58
- return this.addSplit(name, newSplit);
59
- }
57
+ return this.addSplit(name, newSplit);
60
58
  }
61
59
  return false;
62
60
  }).catch(() => false);
@@ -1,5 +1,6 @@
1
1
  import { ISplitsCacheSync } from './types';
2
2
  import { ISplit } from '../dtos/types';
3
+ import { objectAssign } from '../utils/lang/objectAssign';
3
4
 
4
5
  /**
5
6
  * This class provides a skeletal implementation of the ISplitsCacheSync interface
@@ -7,9 +8,9 @@ import { ISplit } from '../dtos/types';
7
8
  */
8
9
  export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
9
10
 
10
- abstract addSplit(name: string, split: string): boolean;
11
+ abstract addSplit(name: string, split: ISplit): boolean
11
12
 
12
- addSplits(entries: [string, string][]): boolean[] {
13
+ addSplits(entries: [string, ISplit][]): boolean[] {
13
14
  return entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1]));
14
15
  }
15
16
 
@@ -19,10 +20,10 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
19
20
  return names.map(name => this.removeSplit(name));
20
21
  }
21
22
 
22
- abstract getSplit(name: string): string | null
23
+ abstract getSplit(name: string): ISplit | null
23
24
 
24
- getSplits(names: string[]): Record<string, string | null> {
25
- const splits: Record<string, string | null> = {};
25
+ getSplits(names: string[]): Record<string, ISplit | null> {
26
+ const splits: Record<string, ISplit | null> = {};
26
27
  names.forEach(name => {
27
28
  splits[name] = this.getSplit(name);
28
29
  });
@@ -33,8 +34,8 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
33
34
 
34
35
  abstract getChangeNumber(): number
35
36
 
36
- getAll(): string[] {
37
- return this.getSplitNames().map(key => this.getSplit(key) as string);
37
+ getAll(): ISplit[] {
38
+ return this.getSplitNames().map(key => this.getSplit(key) as ISplit);
38
39
  }
39
40
 
40
41
  abstract getSplitNames(): string[]
@@ -66,16 +67,13 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
66
67
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean {
67
68
  const split = this.getSplit(name);
68
69
 
69
- if (split) {
70
- const parsedSplit: ISplit = JSON.parse(split);
71
- if (!parsedSplit.changeNumber || parsedSplit.changeNumber < changeNumber) {
72
- parsedSplit.killed = true;
73
- parsedSplit.defaultTreatment = defaultTreatment;
74
- parsedSplit.changeNumber = changeNumber;
75
- const newSplit = JSON.stringify(parsedSplit);
70
+ if (split && (!split.changeNumber || split.changeNumber < changeNumber)) {
71
+ const newSplit = objectAssign({}, split);
72
+ newSplit.killed = true;
73
+ newSplit.defaultTreatment = defaultTreatment;
74
+ newSplit.changeNumber = changeNumber;
76
75
 
77
- return this.addSplit(name, newSplit);
78
- }
76
+ return this.addSplit(name, newSplit);
79
77
  }
80
78
  return false;
81
79
  }
@@ -39,7 +39,7 @@ export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoa
39
39
  storage.splits.setChangeNumber(since);
40
40
 
41
41
  // 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 => [splitName, splitsData[splitName]]));
42
+ storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])));
43
43
 
44
44
  // add mySegments data
45
45
  let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
@@ -38,7 +38,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
38
38
  else localStorage.removeItem(key);
39
39
  }
40
40
 
41
- private _decrementCounts(split: ISplit) {
41
+ private _decrementCounts(split: ISplit | null) {
42
42
  try {
43
43
  if (split) {
44
44
  if (split.trafficTypeName) {
@@ -99,18 +99,16 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
99
99
  this.hasSync = false;
100
100
  }
101
101
 
102
- addSplit(name: string, split: string) {
102
+ addSplit(name: string, split: ISplit) {
103
103
  try {
104
104
  const splitKey = this.keys.buildSplitKey(name);
105
105
  const splitFromLocalStorage = localStorage.getItem(splitKey);
106
106
  const previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
107
107
  this._decrementCounts(previousSplit);
108
108
 
109
- localStorage.setItem(splitKey, split);
109
+ localStorage.setItem(splitKey, JSON.stringify(split));
110
110
 
111
- const parsedSplit = split ? JSON.parse(split) : null;
112
-
113
- this._incrementCounts(parsedSplit);
111
+ this._incrementCounts(split);
114
112
 
115
113
  return true;
116
114
  } catch (e) {
@@ -124,8 +122,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
124
122
  const split = this.getSplit(name);
125
123
  localStorage.removeItem(this.keys.buildSplitKey(name));
126
124
 
127
- const parsedSplit = JSON.parse(split as string);
128
- this._decrementCounts(parsedSplit);
125
+ this._decrementCounts(split);
129
126
 
130
127
  return true;
131
128
  } catch (e) {
@@ -135,7 +132,8 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
135
132
  }
136
133
 
137
134
  getSplit(name: string) {
138
- return localStorage.getItem(this.keys.buildSplitKey(name));
135
+ const item = localStorage.getItem(this.keys.buildSplitKey(name));
136
+ return item && JSON.parse(item);
139
137
  }
140
138
 
141
139
  setChangeNumber(changeNumber: number): boolean {
@@ -184,7 +182,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
184
182
  return n;
185
183
  }
186
184
 
187
- getSplitNames() {
185
+ getSplitNames(): string[] {
188
186
  const len = localStorage.length;
189
187
  const accum = [];
190
188
 
@@ -8,7 +8,7 @@ import { isFiniteNumber } from '../../utils/lang';
8
8
  */
9
9
  export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
10
10
 
11
- private splitsCache: Record<string, string> = {};
11
+ private splitsCache: Record<string, ISplit> = {};
12
12
  private ttCache: Record<string, number> = {};
13
13
  private changeNumber: number = -1;
14
14
  private splitsWithSegmentsCount: number = 0;
@@ -20,10 +20,9 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
20
20
  this.splitsWithSegmentsCount = 0;
21
21
  }
22
22
 
23
- addSplit(name: string, split: string): boolean {
24
- const splitFromMemory = this.getSplit(name);
25
- if (splitFromMemory) { // We had this Split already
26
- const previousSplit: ISplit = JSON.parse(splitFromMemory);
23
+ addSplit(name: string, split: ISplit): boolean {
24
+ const previousSplit = this.getSplit(name);
25
+ if (previousSplit) { // We had this Split already
27
26
 
28
27
  if (previousSplit.trafficTypeName) {
29
28
  const previousTtName = previousSplit.trafficTypeName;
@@ -36,20 +35,18 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
36
35
  }
37
36
  }
38
37
 
39
- const parsedSplit: ISplit = JSON.parse(split);
40
-
41
- if (parsedSplit) {
38
+ if (split) {
42
39
  // Store the Split.
43
40
  this.splitsCache[name] = split;
44
41
  // Update TT cache
45
- const ttName = parsedSplit.trafficTypeName;
42
+ const ttName = split.trafficTypeName;
46
43
  if (ttName) { // safeguard
47
44
  if (!this.ttCache[ttName]) this.ttCache[ttName] = 0;
48
45
  this.ttCache[ttName]++;
49
46
  }
50
47
 
51
48
  // Add to segments count for the new version of the Split
52
- if (usesSegments(parsedSplit)) this.splitsWithSegmentsCount++;
49
+ if (usesSegments(split)) this.splitsWithSegmentsCount++;
53
50
 
54
51
  return true;
55
52
  } else {
@@ -63,8 +60,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
63
60
  // Delete the Split
64
61
  delete this.splitsCache[name];
65
62
 
66
- const parsedSplit: ISplit = JSON.parse(split);
67
- const ttName = parsedSplit.trafficTypeName;
63
+ const ttName = split.trafficTypeName;
68
64
 
69
65
  if (ttName) { // safeguard
70
66
  this.ttCache[ttName]--; // Update tt cache
@@ -72,7 +68,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
72
68
  }
73
69
 
74
70
  // Update the segments count.
75
- if (usesSegments(parsedSplit)) this.splitsWithSegmentsCount--;
71
+ if (usesSegments(split)) this.splitsWithSegmentsCount--;
76
72
 
77
73
  return true;
78
74
  } else {
@@ -80,7 +76,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
80
76
  }
81
77
  }
82
78
 
83
- getSplit(name: string): string | null {
79
+ getSplit(name: string): ISplit | null {
84
80
  return this.splitsCache[name] || null;
85
81
  }
86
82
 
@@ -8,19 +8,22 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
8
8
  private readonly log: ILogger;
9
9
  private readonly key: string;
10
10
  private readonly redis: Redis;
11
- private handle: any;
11
+ private readonly refreshRate: number;
12
+ private intervalId: any;
12
13
 
13
- constructor(log: ILogger, key: string, redis: Redis, impressionCountsCacheSize?: number) {
14
+ constructor(log: ILogger, key: string, redis: Redis, impressionCountsCacheSize?: number, refreshRate: number = REFRESH_RATE) {
14
15
  super(impressionCountsCacheSize);
15
16
  this.log = log;
16
17
  this.key = key;
17
18
  this.redis = redis;
19
+ this.refreshRate = refreshRate;
18
20
  this.onFullQueue = () => { this.postImpressionCountsInRedis(); };
19
21
  }
20
22
 
21
23
  postImpressionCountsInRedis(){
22
24
  const counts = this.pop();
23
25
  const keys = Object.keys(counts);
26
+ if (!keys) return Promise.resolve(false);
24
27
  const pipeline = this.redis.pipeline();
25
28
  keys.forEach(key => {
26
29
  pipeline.hincrby(this.key, key, counts[key]);
@@ -34,15 +37,16 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
34
37
  })
35
38
  .catch(err => {
36
39
  this.log.error(`${LOG_PREFIX}Error in impression counts pipeline: ${err}.`);
37
- return false;
40
+ return Promise.resolve(false);
38
41
  });
39
42
  }
40
43
 
41
- start(refreshRate: number = REFRESH_RATE) {
42
- this.handle = setInterval(this.postImpressionCountsInRedis.bind(this), refreshRate);
44
+ start() {
45
+ this.intervalId = setInterval(this.postImpressionCountsInRedis.bind(this), this.refreshRate);
43
46
  }
44
47
 
45
48
  stop() {
46
- clearInterval(this.handle);
49
+ clearInterval(this.intervalId);
50
+ return this.postImpressionCountsInRedis();
47
51
  }
48
52
  }
@@ -65,22 +65,22 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
65
65
  * The returned promise is resolved when the operation success
66
66
  * or rejected if it fails (e.g., redis operation fails)
67
67
  */
68
- addSplit(name: string, split: string): Promise<boolean> {
68
+ addSplit(name: string, split: ISplit): Promise<boolean> {
69
69
  const splitKey = this.keys.buildSplitKey(name);
70
70
  return this.redis.get(splitKey).then(splitFromStorage => {
71
71
 
72
72
  // handling parsing errors
73
- let parsedPreviousSplit, parsedSplit;
73
+ let parsedPreviousSplit, newStringifiedSplit;
74
74
  try {
75
75
  parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined;
76
- parsedSplit = JSON.parse(split);
76
+ newStringifiedSplit = JSON.stringify(split);
77
77
  } catch (e) {
78
78
  throw new Error('Error parsing split definition: ' + e);
79
79
  }
80
80
 
81
81
  return Promise.all([
82
- this.redis.set(splitKey, split),
83
- this._incrementCounts(parsedSplit),
82
+ this.redis.set(splitKey, newStringifiedSplit),
83
+ this._incrementCounts(split),
84
84
  // If it's an update, we decrement the traffic type of the existing split,
85
85
  parsedPreviousSplit && this._decrementCounts(parsedPreviousSplit)
86
86
  ]);
@@ -92,7 +92,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
92
92
  * The returned promise is resolved when the operation success
93
93
  * or rejected if it fails (e.g., redis operation fails)
94
94
  */
95
- addSplits(entries: [string, string][]): Promise<boolean[]> {
95
+ addSplits(entries: [string, ISplit][]): Promise<boolean[]> {
96
96
  return Promise.all(entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1])));
97
97
  }
98
98
 
@@ -104,8 +104,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
104
104
  removeSplit(name: string): Promise<number> {
105
105
  return this.getSplit(name).then((split) => {
106
106
  if (split) {
107
- const parsedSplit = JSON.parse(split);
108
- this._decrementCounts(parsedSplit);
107
+ this._decrementCounts(split);
109
108
  }
110
109
  return this.redis.del(this.keys.buildSplitKey(name));
111
110
  });
@@ -124,14 +123,15 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
124
123
  * Get split definition or null if it's not defined.
125
124
  * Returned promise is rejected if redis operation fails.
126
125
  */
127
- getSplit(name: string): Promise<string | null> {
126
+ getSplit(name: string): Promise<ISplit | null> {
128
127
  if (this.redisError) {
129
128
  this.log.error(LOG_PREFIX + this.redisError);
130
129
 
131
130
  return Promise.reject(this.redisError);
132
131
  }
133
132
 
134
- return this.redis.get(this.keys.buildSplitKey(name));
133
+ return this.redis.get(this.keys.buildSplitKey(name))
134
+ .then(maybeSplit => maybeSplit && JSON.parse(maybeSplit));
135
135
  }
136
136
 
137
137
  /**
@@ -169,10 +169,13 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
169
169
  * @TODO we need to benchmark which is the maximun number of commands we could
170
170
  * pipeline without kill redis performance.
171
171
  */
172
- getAll(): Promise<string[]> {
173
- return this.redis.keys(this.keys.searchPatternForSplitKeys()).then(
174
- (listOfKeys) => this.redis.pipeline(listOfKeys.map(k => ['get', k])).exec()
175
- ).then(processPipelineAnswer);
172
+ getAll(): Promise<ISplit[]> {
173
+ return this.redis.keys(this.keys.searchPatternForSplitKeys())
174
+ .then((listOfKeys) => this.redis.pipeline(listOfKeys.map(k => ['get', k])).exec())
175
+ .then(processPipelineAnswer)
176
+ .then((splitDefinitions) => splitDefinitions.map((splitDefinition) => {
177
+ return JSON.parse(splitDefinition as string);
178
+ }));
176
179
  }
177
180
 
178
181
  /**
@@ -226,19 +229,20 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
226
229
  * Fetches multiple splits definitions.
227
230
  * Returned promise is rejected if redis operation fails.
228
231
  */
229
- getSplits(names: string[]): Promise<Record<string, string | null>> {
232
+ getSplits(names: string[]): Promise<Record<string, ISplit | null>> {
230
233
  if (this.redisError) {
231
234
  this.log.error(LOG_PREFIX + this.redisError);
232
235
 
233
236
  return Promise.reject(this.redisError);
234
237
  }
235
238
 
236
- const splits: Record<string, string | null> = {};
239
+ const splits: Record<string, ISplit | null> = {};
237
240
  const keys = names.map(name => this.keys.buildSplitKey(name));
238
241
  return this.redis.mget(...keys)
239
242
  .then(splitDefinitions => {
240
243
  names.forEach((name, idx) => {
241
- splits[name] = splitDefinitions[idx];
244
+ const split = splitDefinitions[idx];
245
+ splits[name] = split && JSON.parse(split);
242
246
  });
243
247
  return Promise.resolve(splits);
244
248
  })
@@ -52,10 +52,11 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
52
52
 
53
53
  // When using REDIS we should:
54
54
  // 1- Disconnect from the storage
55
- destroy() {
56
- redisClient.disconnect();
57
- if (impressionCountsCache) impressionCountsCache.stop();
58
- if (uniqueKeysCache) uniqueKeysCache.stop();
55
+ destroy(): Promise<void>{
56
+ let promises = [];
57
+ if (impressionCountsCache) promises.push(impressionCountsCache.stop());
58
+ if (uniqueKeysCache) promises.push(uniqueKeysCache.stop());
59
+ return Promise.all(promises).then(() => { redisClient.disconnect(); });
59
60
  // @TODO check that caches works as expected when redisClient is disconnected
60
61
  }
61
62
  };