@splitsoftware/splitio-commons 1.6.2-rc.1 → 1.6.2-rc.11

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 (197) hide show
  1. package/CHANGES.txt +4 -0
  2. package/cjs/consent/sdkUserConsent.js +2 -2
  3. package/cjs/evaluator/index.js +15 -16
  4. package/cjs/integrations/ga/GaToSplit.js +8 -5
  5. package/cjs/sdkClient/client.js +19 -7
  6. package/cjs/sdkClient/sdkClient.js +3 -1
  7. package/cjs/sdkFactory/index.js +15 -6
  8. package/cjs/sdkManager/index.js +3 -11
  9. package/cjs/services/splitApi.js +6 -6
  10. package/cjs/storages/AbstractSplitsCacheAsync.js +8 -10
  11. package/cjs/storages/AbstractSplitsCacheSync.js +8 -10
  12. package/cjs/storages/KeyBuilderSS.js +54 -9
  13. package/cjs/storages/dataLoader.js +1 -1
  14. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +5 -7
  15. package/cjs/storages/inLocalStorage/index.js +5 -1
  16. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  17. package/cjs/storages/inMemory/InMemoryStorage.js +6 -2
  18. package/cjs/storages/inMemory/InMemoryStorageCS.js +6 -2
  19. package/cjs/storages/inMemory/SplitsCacheInMemory.js +7 -10
  20. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +10 -5
  21. package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +72 -0
  22. package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +76 -0
  23. package/cjs/storages/inRedis/EventsCacheInRedis.js +1 -1
  24. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +85 -0
  25. package/cjs/storages/inRedis/SplitsCacheInRedis.js +15 -9
  26. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  27. package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +71 -0
  28. package/cjs/storages/inRedis/constants.js +4 -1
  29. package/cjs/storages/inRedis/index.js +17 -2
  30. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +81 -0
  31. package/cjs/storages/pluggable/SplitsCachePluggable.js +14 -9
  32. package/cjs/storages/pluggable/TelemetryCachePluggable.js +126 -0
  33. package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +61 -0
  34. package/cjs/storages/pluggable/index.js +46 -17
  35. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  36. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -1
  37. package/cjs/sync/submitters/telemetrySubmitter.js +8 -4
  38. package/cjs/sync/submitters/uniqueKeysSubmitter.js +16 -59
  39. package/cjs/trackers/impressionsTracker.js +17 -15
  40. package/cjs/trackers/strategy/strategyNone.js +1 -1
  41. package/cjs/trackers/strategy/strategyOptimized.js +2 -1
  42. package/cjs/trackers/telemetryTracker.js +6 -0
  43. package/cjs/trackers/uniqueKeysTracker.js +11 -42
  44. package/cjs/utils/constants/index.js +3 -2
  45. package/cjs/utils/lang/maps.js +15 -7
  46. package/cjs/utils/redis/RedisMock.js +31 -0
  47. package/cjs/utils/settingsValidation/index.js +0 -3
  48. package/esm/consent/sdkUserConsent.js +2 -2
  49. package/esm/evaluator/index.js +15 -16
  50. package/esm/integrations/ga/GaToSplit.js +8 -5
  51. package/esm/sdkClient/client.js +19 -7
  52. package/esm/sdkClient/sdkClient.js +3 -1
  53. package/esm/sdkFactory/index.js +16 -7
  54. package/esm/sdkManager/index.js +3 -11
  55. package/esm/services/splitApi.js +6 -6
  56. package/esm/storages/AbstractSplitsCacheAsync.js +8 -10
  57. package/esm/storages/AbstractSplitsCacheSync.js +8 -10
  58. package/esm/storages/KeyBuilderSS.js +50 -8
  59. package/esm/storages/dataLoader.js +1 -1
  60. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +5 -7
  61. package/esm/storages/inLocalStorage/index.js +6 -2
  62. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  63. package/esm/storages/inMemory/InMemoryStorage.js +8 -4
  64. package/esm/storages/inMemory/InMemoryStorageCS.js +7 -3
  65. package/esm/storages/inMemory/SplitsCacheInMemory.js +7 -10
  66. package/esm/storages/inMemory/TelemetryCacheInMemory.js +9 -5
  67. package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +68 -0
  68. package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +73 -0
  69. package/esm/storages/inRedis/EventsCacheInRedis.js +1 -1
  70. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +82 -0
  71. package/esm/storages/inRedis/SplitsCacheInRedis.js +15 -9
  72. package/esm/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  73. package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +68 -0
  74. package/esm/storages/inRedis/constants.js +3 -0
  75. package/esm/storages/inRedis/index.js +18 -3
  76. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +78 -0
  77. package/esm/storages/pluggable/SplitsCachePluggable.js +14 -9
  78. package/esm/storages/pluggable/TelemetryCachePluggable.js +126 -0
  79. package/esm/storages/pluggable/UniqueKeysCachePluggable.js +58 -0
  80. package/esm/storages/pluggable/index.js +47 -18
  81. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
  82. package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -1
  83. package/esm/sync/submitters/telemetrySubmitter.js +9 -5
  84. package/esm/sync/submitters/uniqueKeysSubmitter.js +15 -56
  85. package/esm/trackers/impressionsTracker.js +17 -15
  86. package/esm/trackers/strategy/strategyNone.js +1 -1
  87. package/esm/trackers/strategy/strategyOptimized.js +2 -1
  88. package/esm/trackers/telemetryTracker.js +6 -0
  89. package/esm/trackers/uniqueKeysTracker.js +11 -42
  90. package/esm/utils/constants/index.js +1 -0
  91. package/esm/utils/lang/maps.js +15 -7
  92. package/esm/utils/redis/RedisMock.js +28 -0
  93. package/esm/utils/settingsValidation/index.js +0 -3
  94. package/package.json +1 -2
  95. package/src/consent/sdkUserConsent.ts +2 -2
  96. package/src/evaluator/index.ts +14 -15
  97. package/src/integrations/ga/GaToSplit.ts +9 -5
  98. package/src/integrations/types.ts +2 -1
  99. package/src/logger/.DS_Store +0 -0
  100. package/src/sdkClient/client.ts +21 -8
  101. package/src/sdkClient/sdkClient.ts +3 -1
  102. package/src/sdkFactory/index.ts +17 -7
  103. package/src/sdkManager/index.ts +3 -12
  104. package/src/services/splitApi.ts +6 -6
  105. package/src/services/types.ts +2 -2
  106. package/src/storages/AbstractSplitsCacheAsync.ts +13 -15
  107. package/src/storages/AbstractSplitsCacheSync.ts +15 -17
  108. package/src/storages/KeyBuilderSS.ts +61 -9
  109. package/src/storages/dataLoader.ts +1 -1
  110. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +8 -11
  111. package/src/storages/inLocalStorage/index.ts +5 -2
  112. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +16 -1
  113. package/src/storages/inMemory/InMemoryStorage.ts +7 -4
  114. package/src/storages/inMemory/InMemoryStorageCS.ts +6 -3
  115. package/src/storages/inMemory/SplitsCacheInMemory.ts +10 -14
  116. package/src/storages/inMemory/TelemetryCacheInMemory.ts +10 -6
  117. package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +80 -0
  118. package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +86 -0
  119. package/src/storages/inRedis/EventsCacheInRedis.ts +1 -1
  120. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +95 -0
  121. package/src/storages/inRedis/SplitsCacheInRedis.ts +21 -17
  122. package/src/storages/inRedis/TelemetryCacheInRedis.ts +122 -2
  123. package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +77 -0
  124. package/src/storages/inRedis/constants.ts +3 -0
  125. package/src/storages/inRedis/index.ts +15 -5
  126. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +92 -0
  127. package/src/storages/pluggable/SplitsCachePluggable.ts +20 -17
  128. package/src/storages/pluggable/TelemetryCachePluggable.ts +147 -2
  129. package/src/storages/pluggable/UniqueKeysCachePluggable.ts +67 -0
  130. package/src/storages/pluggable/index.ts +51 -19
  131. package/src/storages/types.ts +38 -30
  132. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
  133. package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -2
  134. package/src/sync/submitters/telemetrySubmitter.ts +15 -8
  135. package/src/sync/submitters/types.ts +26 -12
  136. package/src/sync/submitters/uniqueKeysSubmitter.ts +18 -61
  137. package/src/trackers/impressionsTracker.ts +16 -15
  138. package/src/trackers/strategy/strategyNone.ts +1 -1
  139. package/src/trackers/strategy/strategyOptimized.ts +1 -1
  140. package/src/trackers/telemetryTracker.ts +7 -2
  141. package/src/trackers/types.ts +9 -7
  142. package/src/trackers/uniqueKeysTracker.ts +15 -47
  143. package/src/types.ts +0 -1
  144. package/src/utils/constants/index.ts +1 -0
  145. package/src/utils/lang/maps.ts +20 -8
  146. package/src/utils/redis/RedisMock.ts +33 -0
  147. package/src/utils/settingsValidation/index.ts +1 -4
  148. package/types/integrations/types.d.ts +2 -1
  149. package/types/services/types.d.ts +2 -2
  150. package/types/storages/AbstractSplitsCacheAsync.d.ts +7 -6
  151. package/types/storages/AbstractSplitsCacheSync.d.ts +6 -6
  152. package/types/storages/KeyBuilderSS.d.ts +9 -2
  153. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +3 -4
  154. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +5 -1
  155. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +3 -2
  156. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +6 -3
  157. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +35 -0
  158. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +35 -0
  159. package/types/storages/inRedis/EventsCacheInRedis.d.ts +1 -1
  160. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +16 -0
  161. package/types/storages/inRedis/SplitsCacheInRedis.d.ts +6 -5
  162. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +16 -1
  163. package/types/storages/inRedis/constants.d.ts +3 -0
  164. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +21 -0
  165. package/types/storages/pluggable/ImpressionCountsCachePluggable.d.ts +16 -0
  166. package/types/storages/pluggable/SplitsCachePluggable.d.ts +6 -5
  167. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +17 -1
  168. package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +20 -0
  169. package/types/storages/types.d.ts +35 -35
  170. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  171. package/types/sync/submitters/telemetrySubmitter.d.ts +1 -1
  172. package/types/sync/submitters/types.d.ts +19 -12
  173. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +0 -14
  174. package/types/trackers/types.d.ts +9 -11
  175. package/types/trackers/uniqueKeysTracker.d.ts +3 -3
  176. package/types/types.d.ts +0 -1
  177. package/types/utils/constants/index.d.ts +1 -0
  178. package/types/utils/lang/maps.d.ts +6 -2
  179. package/types/utils/redis/RedisMock.d.ts +4 -0
  180. package/types/utils/settingsValidation/index.d.ts +0 -1
  181. package/types/sdkClient/types.d.ts +0 -18
  182. package/types/storages/inMemory/CountsCacheInMemory.d.ts +0 -20
  183. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +0 -20
  184. package/types/storages/inRedis/CountsCacheInRedis.d.ts +0 -9
  185. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +0 -9
  186. package/types/sync/offline/LocalhostFromFile.d.ts +0 -2
  187. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +0 -2
  188. package/types/sync/submitters/eventsSyncTask.d.ts +0 -8
  189. package/types/sync/submitters/impressionCountsSyncTask.d.ts +0 -13
  190. package/types/sync/submitters/impressionsSyncTask.d.ts +0 -14
  191. package/types/sync/submitters/metricsSyncTask.d.ts +0 -12
  192. package/types/sync/submitters/submitterSyncTask.d.ts +0 -10
  193. package/types/sync/syncTaskComposite.d.ts +0 -5
  194. package/types/trackers/filter/bloomFilter.d.ts +0 -10
  195. package/types/trackers/filter/dictionaryFilter.d.ts +0 -8
  196. package/types/trackers/filter/types.d.ts +0 -5
  197. package/types/utils/timeTracker/index.d.ts +0 -70
@@ -1,8 +1,16 @@
1
1
  import { truncateTimeFrame } from '../../utils/time';
2
+ import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
2
3
  import { IImpressionCountsCacheSync } from '../types';
3
4
 
4
5
  export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync {
5
- private cache: Record<string, number> = {};
6
+ protected cache: Record<string, number> = {};
7
+ private readonly maxStorage: number;
8
+ protected onFullQueue?: () => void;
9
+ private cacheSize = 0;
10
+
11
+ constructor(impressionCountsCacheSize = DEFAULT_CACHE_SIZE) {
12
+ this.maxStorage = impressionCountsCacheSize;
13
+ }
6
14
 
7
15
  /**
8
16
  * Builds key to be stored in the cache with the featureName and the timeFrame truncated.
@@ -18,6 +26,13 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync
18
26
  const key = this._makeKey(featureName, timeFrame);
19
27
  const currentAmount = this.cache[key];
20
28
  this.cache[key] = currentAmount ? currentAmount + amount : amount;
29
+ if (this.onFullQueue) {
30
+ this.cacheSize = this.cacheSize + amount;
31
+ if (this.cacheSize >= this.maxStorage) {
32
+ this.onFullQueue();
33
+ this.cacheSize = 0;
34
+ }
35
+ }
21
36
  }
22
37
 
23
38
 
@@ -4,8 +4,9 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageFactoryParams, IStorageSync } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
- import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
7
+ import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
+ import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
+ import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
9
10
 
10
11
  /**
11
12
  * InMemory storage factory for standalone server-side SplitFactory
@@ -18,9 +19,10 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
18
19
  splits: new SplitsCacheInMemory(),
19
20
  segments: new SegmentsCacheInMemory(),
20
21
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
21
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
22
+ impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
22
23
  events: new EventsCacheInMemory(params.eventsQueueSize),
23
- telemetry: params.mode !== LOCALHOST_MODE ? new TelemetryCacheInMemory() : undefined, // Always track telemetry in standalone mode on server-side
24
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
25
+ uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
24
26
 
25
27
  // When using MEMORY we should clean all the caches to leave them empty
26
28
  destroy() {
@@ -29,6 +31,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
29
31
  this.impressions.clear();
30
32
  this.impressionCounts && this.impressionCounts.clear();
31
33
  this.events.clear();
34
+ this.uniqueKeys?.clear();
32
35
  }
33
36
  };
34
37
  }
@@ -4,8 +4,9 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageSync, IStorageFactoryParams } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
+ import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
9
10
 
10
11
  /**
11
12
  * InMemory storage factory for standalone client-side SplitFactory
@@ -18,9 +19,10 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
18
19
  splits: new SplitsCacheInMemory(),
19
20
  segments: new MySegmentsCacheInMemory(),
20
21
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
21
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
22
+ impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
22
23
  events: new EventsCacheInMemory(params.eventsQueueSize),
23
- telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
24
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
25
+ uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
24
26
 
25
27
  // When using MEMORY we should clean all the caches to leave them empty
26
28
  destroy() {
@@ -29,6 +31,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
29
31
  this.impressions.clear();
30
32
  this.impressionCounts && this.impressionCounts.clear();
31
33
  this.events.clear();
34
+ this.uniqueKeys?.clear();
32
35
  },
33
36
 
34
37
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
@@ -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
 
@@ -1,22 +1,26 @@
1
1
  import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies } from '../../sync/submitters/types';
2
+ import { LOCALHOST_MODE } from '../../utils/constants';
2
3
  import { findLatencyIndex } from '../findLatencyIndex';
3
- import { ITelemetryCacheSync } from '../types';
4
+ import { IStorageFactoryParams, ITelemetryCacheSync } from '../types';
4
5
 
5
6
  const MAX_STREAMING_EVENTS = 20;
6
7
  const MAX_TAGS = 10;
8
+ export const MAX_LATENCY_BUCKET_COUNT = 23;
7
9
 
8
- function newBuckets() {
9
- // MAX_LATENCY_BUCKET_COUNT (length) is 23;
10
+ export function newBuckets() {
11
+ // MAX_LATENCY_BUCKET_COUNT (length) is 23
12
+ // Not using Array.fill for old browsers compatibility
10
13
  return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
11
14
  }
12
15
 
13
16
  const ACCEPTANCE_RANGE = 0.001;
14
17
 
15
18
  /**
16
- * Used on client-side. 0.1% of instances will track telemetry
19
+ * Record telemetry if mode is not localhost.
20
+ * All factory instances track telemetry on server-side, and 0.1% on client-side.
17
21
  */
18
- export function shouldRecordTelemetry() {
19
- return Math.random() <= ACCEPTANCE_RANGE;
22
+ export function shouldRecordTelemetry(params: IStorageFactoryParams) {
23
+ return params.mode !== LOCALHOST_MODE && (params.matchingKey === undefined || Math.random() <= ACCEPTANCE_RANGE);
20
24
  }
21
25
 
22
26
  export class TelemetryCacheInMemory implements ITelemetryCacheSync {
@@ -0,0 +1,80 @@
1
+ import { IUniqueKeysCacheBase } from '../types';
2
+ import { ISet, setToArray, _Set } from '../../utils/lang/sets';
3
+ import { UniqueKeysPayloadSs } from '../../sync/submitters/types';
4
+ import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
5
+
6
+ /**
7
+ * Converts `uniqueKeys` data from cache into request payload for SS.
8
+ */
9
+ export function fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: ISet<string> }): UniqueKeysPayloadSs {
10
+ const payload = [];
11
+ const featureNames = Object.keys(uniqueKeys);
12
+ for (let i = 0; i < featureNames.length; i++) {
13
+ const featureName = featureNames[i];
14
+ const userKeys = setToArray(uniqueKeys[featureName]);
15
+ const uniqueKeysPayload = {
16
+ f: featureName,
17
+ ks: userKeys
18
+ };
19
+
20
+ payload.push(uniqueKeysPayload);
21
+ }
22
+ return { keys: payload };
23
+ }
24
+
25
+ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
26
+
27
+ protected onFullQueue?: () => void;
28
+ private readonly maxStorage: number;
29
+ private uniqueTrackerSize = 0;
30
+ protected uniqueKeysTracker: { [featureName: string]: ISet<string> };
31
+
32
+ constructor(uniqueKeysQueueSize = DEFAULT_CACHE_SIZE) {
33
+ this.maxStorage = uniqueKeysQueueSize;
34
+ this.uniqueKeysTracker = {};
35
+ }
36
+
37
+ setOnFullQueueCb(cb: () => void) {
38
+ this.onFullQueue = cb;
39
+ }
40
+
41
+ /**
42
+ * Store unique keys per feature.
43
+ */
44
+ track(userKey: string, featureName: string) {
45
+ if (!this.uniqueKeysTracker[featureName]) this.uniqueKeysTracker[featureName] = new _Set();
46
+ const tracker = this.uniqueKeysTracker[featureName];
47
+ if (!tracker.has(userKey)) {
48
+ tracker.add(userKey);
49
+ this.uniqueTrackerSize++;
50
+ }
51
+ if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
52
+ this.uniqueTrackerSize = 0;
53
+ this.onFullQueue();
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Clear the data stored on the cache.
59
+ */
60
+ clear() {
61
+ this.uniqueKeysTracker = {};
62
+ }
63
+
64
+ /**
65
+ * Pop the collected data, used as payload for posting.
66
+ */
67
+ pop() {
68
+ const data = this.uniqueKeysTracker;
69
+ this.uniqueKeysTracker = {};
70
+ return fromUniqueKeysCollector(data);
71
+ }
72
+
73
+ /**
74
+ * Check if the cache is empty.
75
+ */
76
+ isEmpty() {
77
+ return Object.keys(this.uniqueKeysTracker).length === 0;
78
+ }
79
+
80
+ }
@@ -0,0 +1,86 @@
1
+ import { IUniqueKeysCacheBase } from '../types';
2
+ import { ISet, setToArray, _Set } from '../../utils/lang/sets';
3
+ import { UniqueKeysPayloadCs } from '../../sync/submitters/types';
4
+ import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
5
+
6
+ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
7
+
8
+ private onFullQueue?: () => void;
9
+ private readonly maxStorage: number;
10
+ private uniqueTrackerSize = 0;
11
+ private uniqueKeysTracker: { [userKey: string]: ISet<string> };
12
+
13
+ /**
14
+ *
15
+ * @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
16
+ * Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
17
+ */
18
+ constructor(uniqueKeysQueueSize = DEFAULT_CACHE_SIZE) {
19
+ this.maxStorage = uniqueKeysQueueSize;
20
+ this.uniqueKeysTracker = {};
21
+ }
22
+
23
+ setOnFullQueueCb(cb: () => void) {
24
+ this.onFullQueue = cb;
25
+ }
26
+
27
+ /**
28
+ * Store unique keys per feature.
29
+ */
30
+ track(userKey: string, featureName: string) {
31
+
32
+ if (!this.uniqueKeysTracker[userKey]) this.uniqueKeysTracker[userKey] = new _Set();
33
+ const tracker = this.uniqueKeysTracker[userKey];
34
+ if (!tracker.has(featureName)) {
35
+ tracker.add(featureName);
36
+ this.uniqueTrackerSize++;
37
+ }
38
+ if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
39
+ this.uniqueTrackerSize = 0;
40
+ this.onFullQueue();
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Clear the data stored on the cache.
46
+ */
47
+ clear() {
48
+ this.uniqueKeysTracker = {};
49
+ }
50
+
51
+ /**
52
+ * Pop the collected data, used as payload for posting.
53
+ */
54
+ pop() {
55
+ const data = this.uniqueKeysTracker;
56
+ this.uniqueKeysTracker = {};
57
+ return this.fromUniqueKeysCollector(data);
58
+ }
59
+
60
+ /**
61
+ * Check if the cache is empty.
62
+ */
63
+ isEmpty() {
64
+ return Object.keys(this.uniqueKeysTracker).length === 0;
65
+ }
66
+
67
+ /**
68
+ * Converts `uniqueKeys` data from cache into request payload.
69
+ */
70
+ private fromUniqueKeysCollector(uniqueKeys: { [userKey: string]: ISet<string> }): UniqueKeysPayloadCs {
71
+ const payload = [];
72
+ const userKeys = Object.keys(uniqueKeys);
73
+ for (let k = 0; k < userKeys.length; k++) {
74
+ const userKey = userKeys[k];
75
+ const featureNames = setToArray(uniqueKeys[userKey]);
76
+ const uniqueKeysPayload = {
77
+ k: userKey,
78
+ fs: featureNames
79
+ };
80
+
81
+ payload.push(uniqueKeysPayload);
82
+ }
83
+ return { keys: payload };
84
+ }
85
+
86
+ }
@@ -59,7 +59,7 @@ export class EventsCacheInRedis implements IEventsCacheAsync {
59
59
 
60
60
  /**
61
61
  * Pop the given number of events from the storage.
62
- * The returned promise rejects if the wrapper operation fails.
62
+ * The returned promise rejects if the redis operation fails.
63
63
  *
64
64
  * NOTE: this method doesn't take into account MAX_EVENT_SIZE or MAX_QUEUE_BYTE_SIZE limits.
65
65
  * It is the submitter responsability to handle that.
@@ -0,0 +1,95 @@
1
+ import { Redis } from 'ioredis';
2
+ import { ILogger } from '../../logger/types';
3
+ import { ImpressionCountsPayload } from '../../sync/submitters/types';
4
+ import { forOwn } from '../../utils/lang';
5
+ import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
6
+ import { LOG_PREFIX, REFRESH_RATE, TTL_REFRESH } from './constants';
7
+
8
+ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory {
9
+
10
+ private readonly log: ILogger;
11
+ private readonly key: string;
12
+ private readonly redis: Redis;
13
+ private readonly refreshRate: number;
14
+ private intervalId: any;
15
+
16
+ constructor(log: ILogger, key: string, redis: Redis, impressionCountsCacheSize?: number, refreshRate = REFRESH_RATE) {
17
+ super(impressionCountsCacheSize);
18
+ this.log = log;
19
+ this.key = key;
20
+ this.redis = redis;
21
+ this.refreshRate = refreshRate;
22
+ this.onFullQueue = () => { this.postImpressionCountsInRedis(); };
23
+ }
24
+
25
+ private postImpressionCountsInRedis() {
26
+ const counts = this.pop();
27
+ const keys = Object.keys(counts);
28
+ if (!keys.length) return Promise.resolve(false);
29
+
30
+ const pipeline = this.redis.pipeline();
31
+ keys.forEach(key => {
32
+ pipeline.hincrby(this.key, key, counts[key]);
33
+ });
34
+ return pipeline.exec()
35
+ .then(data => {
36
+ // If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
37
+ if (data.length && data.length === keys.length) {
38
+ return this.redis.expire(this.key, TTL_REFRESH);
39
+ }
40
+ })
41
+ .catch(err => {
42
+ this.log.error(`${LOG_PREFIX}Error in impression counts pipeline: ${err}.`);
43
+ return false;
44
+ });
45
+ }
46
+
47
+ start() {
48
+ this.intervalId = setInterval(this.postImpressionCountsInRedis.bind(this), this.refreshRate);
49
+ }
50
+
51
+ stop() {
52
+ clearInterval(this.intervalId);
53
+ return this.postImpressionCountsInRedis();
54
+ }
55
+
56
+ // Async consumer API, used by synchronizer
57
+ getImpressionsCount(): Promise<ImpressionCountsPayload | undefined> {
58
+ return this.redis.hgetall(this.key)
59
+ .then(counts => {
60
+ if (!Object.keys(counts).length) return undefined;
61
+
62
+ this.redis.del(this.key).catch(() => { /* no-op */ });
63
+
64
+ const impressionsCount: ImpressionCountsPayload = { pf: [] };
65
+
66
+ forOwn(counts, (count, key) => {
67
+ const nameAndTime = key.split('::');
68
+ if (nameAndTime.length !== 2) {
69
+ this.log.error(`${LOG_PREFIX}Error spliting key ${key}`);
70
+ return;
71
+ }
72
+
73
+ const timeFrame = parseInt(nameAndTime[1]);
74
+ if (isNaN(timeFrame)) {
75
+ this.log.error(`${LOG_PREFIX}Error parsing time frame ${nameAndTime[1]}`);
76
+ return;
77
+ }
78
+
79
+ const rawCount = parseInt(count);
80
+ if (isNaN(rawCount)) {
81
+ this.log.error(`${LOG_PREFIX}Error parsing raw count ${count}`);
82
+ return;
83
+ }
84
+
85
+ impressionsCount.pf.push({
86
+ f: nameAndTime[0],
87
+ m: timeFrame,
88
+ rc: rawCount,
89
+ });
90
+ });
91
+
92
+ return impressionsCount;
93
+ });
94
+ }
95
+ }
@@ -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
  })