@splitsoftware/splitio-commons 1.6.2-rc.9 → 1.7.0

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 (176) hide show
  1. package/CHANGES.txt +3 -1
  2. package/cjs/consent/sdkUserConsent.js +2 -2
  3. package/cjs/listeners/browser.js +11 -12
  4. package/cjs/logger/constants.js +2 -1
  5. package/cjs/sdkClient/sdkClient.js +3 -1
  6. package/cjs/sdkFactory/index.js +26 -26
  7. package/cjs/services/splitApi.js +20 -0
  8. package/cjs/storages/AbstractSplitsCacheAsync.js +1 -1
  9. package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
  10. package/cjs/storages/KeyBuilderSS.js +10 -43
  11. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
  12. package/cjs/storages/inLocalStorage/index.js +17 -9
  13. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  14. package/cjs/storages/inMemory/InMemoryStorage.js +13 -6
  15. package/cjs/storages/inMemory/InMemoryStorageCS.js +13 -6
  16. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +60 -35
  17. package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +72 -0
  18. package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +76 -0
  19. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +85 -0
  20. package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  21. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
  22. package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +71 -0
  23. package/cjs/storages/inRedis/constants.js +4 -1
  24. package/cjs/storages/inRedis/index.js +20 -3
  25. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +81 -0
  26. package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  27. package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
  28. package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +61 -0
  29. package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
  30. package/cjs/storages/pluggable/index.js +38 -9
  31. package/cjs/storages/utils.js +73 -0
  32. package/cjs/sync/submitters/submitterManager.js +3 -0
  33. package/cjs/sync/submitters/telemetrySubmitter.js +5 -40
  34. package/cjs/sync/submitters/uniqueKeysSubmitter.js +27 -0
  35. package/cjs/trackers/impressionObserver/utils.js +1 -17
  36. package/cjs/trackers/impressionsTracker.js +22 -41
  37. package/cjs/trackers/strategy/strategyDebug.js +25 -0
  38. package/cjs/trackers/strategy/strategyNone.js +29 -0
  39. package/cjs/trackers/strategy/strategyOptimized.js +35 -0
  40. package/cjs/trackers/uniqueKeysTracker.js +38 -0
  41. package/cjs/utils/constants/index.js +4 -2
  42. package/cjs/utils/redis/RedisMock.js +31 -0
  43. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  44. package/cjs/utils/settingsValidation/index.js +7 -1
  45. package/esm/consent/sdkUserConsent.js +2 -2
  46. package/esm/listeners/browser.js +12 -13
  47. package/esm/logger/constants.js +1 -0
  48. package/esm/sdkClient/sdkClient.js +3 -1
  49. package/esm/sdkFactory/index.js +26 -26
  50. package/esm/services/splitApi.js +20 -0
  51. package/esm/storages/AbstractSplitsCacheAsync.js +1 -1
  52. package/esm/storages/AbstractSplitsCacheSync.js +1 -1
  53. package/esm/storages/KeyBuilderSS.js +7 -37
  54. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
  55. package/esm/storages/inLocalStorage/index.js +18 -10
  56. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
  57. package/esm/storages/inMemory/InMemoryStorage.js +14 -7
  58. package/esm/storages/inMemory/InMemoryStorageCS.js +14 -7
  59. package/esm/storages/inMemory/TelemetryCacheInMemory.js +61 -36
  60. package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +68 -0
  61. package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +73 -0
  62. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +82 -0
  63. package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  64. package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
  65. package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +68 -0
  66. package/esm/storages/inRedis/constants.js +3 -0
  67. package/esm/storages/inRedis/index.js +21 -4
  68. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +78 -0
  69. package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  70. package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
  71. package/esm/storages/pluggable/UniqueKeysCachePluggable.js +58 -0
  72. package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
  73. package/esm/storages/pluggable/index.js +39 -10
  74. package/esm/storages/utils.js +65 -0
  75. package/esm/sync/submitters/submitterManager.js +3 -0
  76. package/esm/sync/submitters/telemetrySubmitter.js +5 -39
  77. package/esm/sync/submitters/uniqueKeysSubmitter.js +23 -0
  78. package/esm/trackers/impressionObserver/utils.js +1 -15
  79. package/esm/trackers/impressionsTracker.js +22 -41
  80. package/esm/trackers/strategy/strategyDebug.js +21 -0
  81. package/esm/trackers/strategy/strategyNone.js +25 -0
  82. package/esm/trackers/strategy/strategyOptimized.js +31 -0
  83. package/esm/trackers/uniqueKeysTracker.js +34 -0
  84. package/esm/utils/constants/index.js +2 -0
  85. package/esm/utils/redis/RedisMock.js +28 -0
  86. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  87. package/esm/utils/settingsValidation/index.js +7 -1
  88. package/package.json +1 -2
  89. package/src/consent/sdkUserConsent.ts +2 -2
  90. package/src/listeners/browser.ts +12 -15
  91. package/src/logger/constants.ts +1 -0
  92. package/src/sdkClient/sdkClient.ts +3 -1
  93. package/src/sdkFactory/index.ts +29 -31
  94. package/src/sdkFactory/types.ts +7 -4
  95. package/src/services/splitApi.ts +22 -0
  96. package/src/services/types.ts +6 -0
  97. package/src/storages/AbstractSplitsCacheAsync.ts +1 -1
  98. package/src/storages/AbstractSplitsCacheSync.ts +1 -1
  99. package/src/storages/KeyBuilderSS.ts +9 -43
  100. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -1
  101. package/src/storages/inLocalStorage/index.ts +18 -10
  102. package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
  103. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +16 -1
  104. package/src/storages/inMemory/InMemoryStorage.ts +14 -7
  105. package/src/storages/inMemory/InMemoryStorageCS.ts +14 -7
  106. package/src/storages/inMemory/TelemetryCacheInMemory.ts +69 -34
  107. package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +80 -0
  108. package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +86 -0
  109. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +95 -0
  110. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
  111. package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
  112. package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +77 -0
  113. package/src/storages/inRedis/constants.ts +3 -0
  114. package/src/storages/inRedis/index.ts +18 -5
  115. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +92 -0
  116. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
  117. package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
  118. package/src/storages/pluggable/UniqueKeysCachePluggable.ts +67 -0
  119. package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
  120. package/src/storages/pluggable/index.ts +41 -9
  121. package/src/storages/types.ts +56 -55
  122. package/src/storages/utils.ts +78 -0
  123. package/src/sync/submitters/submitter.ts +2 -2
  124. package/src/sync/submitters/submitterManager.ts +2 -0
  125. package/src/sync/submitters/telemetrySubmitter.ts +8 -43
  126. package/src/sync/submitters/types.ts +29 -27
  127. package/src/sync/submitters/uniqueKeysSubmitter.ts +36 -0
  128. package/src/trackers/impressionObserver/utils.ts +1 -16
  129. package/src/trackers/impressionsTracker.ts +25 -46
  130. package/src/trackers/strategy/strategyDebug.ts +28 -0
  131. package/src/trackers/strategy/strategyNone.ts +34 -0
  132. package/src/trackers/strategy/strategyOptimized.ts +42 -0
  133. package/src/trackers/types.ts +28 -0
  134. package/src/trackers/uniqueKeysTracker.ts +48 -0
  135. package/src/types.ts +1 -1
  136. package/src/utils/constants/index.ts +2 -0
  137. package/src/utils/redis/RedisMock.ts +33 -0
  138. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  139. package/src/utils/settingsValidation/index.ts +5 -1
  140. package/types/logger/constants.d.ts +1 -0
  141. package/types/sdkFactory/types.d.ts +4 -2
  142. package/types/services/types.d.ts +4 -0
  143. package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
  144. package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
  145. package/types/storages/KeyBuilderSS.d.ts +3 -3
  146. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -1
  147. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +5 -1
  148. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +20 -9
  149. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +35 -0
  150. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +35 -0
  151. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +16 -0
  152. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
  153. package/types/storages/inRedis/constants.d.ts +3 -0
  154. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +21 -0
  155. package/types/storages/pluggable/ImpressionCountsCachePluggable.d.ts +16 -0
  156. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
  157. package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +20 -0
  158. package/types/storages/types.d.ts +39 -38
  159. package/types/storages/utils.d.ts +8 -0
  160. package/types/sync/submitters/submitter.d.ts +2 -2
  161. package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
  162. package/types/sync/submitters/types.d.ts +27 -7
  163. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +5 -0
  164. package/types/trackers/impressionObserver/utils.d.ts +0 -8
  165. package/types/trackers/impressionsTracker.d.ts +4 -6
  166. package/types/trackers/strategy/strategyDebug.d.ts +9 -0
  167. package/types/trackers/strategy/strategyNone.d.ts +10 -0
  168. package/types/trackers/strategy/strategyOptimized.d.ts +11 -0
  169. package/types/trackers/types.d.ts +23 -0
  170. package/types/trackers/uniqueKeysTracker.d.ts +13 -0
  171. package/types/types.d.ts +1 -1
  172. package/types/utils/constants/index.d.ts +2 -0
  173. package/types/utils/redis/RedisMock.d.ts +4 -0
  174. package/cjs/storages/metadataBuilder.js +0 -12
  175. package/esm/storages/metadataBuilder.js +0 -8
  176. package/src/storages/metadataBuilder.ts +0 -11
@@ -61,7 +61,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
61
61
  * @param {string} name
62
62
  * @param {string} defaultTreatment
63
63
  * @param {number} changeNumber
64
- * @returns {Promise} a promise that is resolved once the split kill is performed. The fulfillment value is a boolean: `true` if the kill success updating the split or `false` if no split is updated,
64
+ * @returns {boolean} `true` if the operation successed updating the split, or `false` if no split is updated,
65
65
  * for instance, if the `changeNumber` is old, or if the split is not found (e.g., `/splitchanges` hasn't been fetched yet), or if the storage fails to apply the update.
66
66
  */
67
67
  killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean {
@@ -1,9 +1,8 @@
1
1
  import { KeyBuilder } from './KeyBuilder';
2
2
  import { IMetadata } from '../dtos/types';
3
3
  import { Method } from '../sync/submitters/types';
4
- import { MAX_LATENCY_BUCKET_COUNT } from './inMemory/TelemetryCacheInMemory';
5
4
 
6
- const METHOD_NAMES: Record<Method, string> = {
5
+ export const METHOD_NAMES: Record<Method, string> = {
7
6
  t: 'treatment',
8
7
  ts: 'treatments',
9
8
  tc: 'treatmentWithConfig',
@@ -34,6 +33,14 @@ export class KeyBuilderSS extends KeyBuilder {
34
33
  return `${this.prefix}.impressions`;
35
34
  }
36
35
 
36
+ buildImpressionsCountKey() {
37
+ return `${this.prefix}.impressions.count`;
38
+ }
39
+
40
+ buildUniqueKeysKey() {
41
+ return `${this.prefix}.uniquekeys`;
42
+ }
43
+
37
44
  buildEventsKey() {
38
45
  return `${this.prefix}.events`;
39
46
  }
@@ -57,44 +64,3 @@ export class KeyBuilderSS extends KeyBuilder {
57
64
  }
58
65
 
59
66
  }
60
-
61
- // Used by consumer methods of TelemetryCacheInRedis and TelemetryCachePluggable
62
-
63
- const REVERSE_METHOD_NAMES = Object.keys(METHOD_NAMES).reduce((acc, key) => {
64
- acc[METHOD_NAMES[key as Method]] = key as Method;
65
- return acc;
66
- }, {} as Record<string, Method>);
67
-
68
-
69
- export function parseMetadata(field: string): [metadata: string] | string {
70
- const parts = field.split('/');
71
- if (parts.length !== 3) return `invalid subsection count. Expected 4, got: ${parts.length}`;
72
-
73
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */] = parts;
74
- return [JSON.stringify({ s, n, i })];
75
- }
76
-
77
- export function parseExceptionField(field: string): [metadata: string, method: Method] | string {
78
- const parts = field.split('/');
79
- if (parts.length !== 4) return `invalid subsection count. Expected 4, got: ${parts.length}`;
80
-
81
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m] = parts;
82
- const method = REVERSE_METHOD_NAMES[m];
83
- if (!method) return `unknown method '${m}'`;
84
-
85
- return [JSON.stringify({ s, n, i }), method];
86
- }
87
-
88
- export function parseLatencyField(field: string): [metadata: string, method: Method, bucket: number] | string {
89
- const parts = field.split('/');
90
- if (parts.length !== 5) return `invalid subsection count. Expected 5, got: ${parts.length}`;
91
-
92
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m, b] = parts;
93
- const method = REVERSE_METHOD_NAMES[m];
94
- if (!method) return `unknown method '${m}'`;
95
-
96
- const bucket = parseInt(b);
97
- if (isNaN(bucket) || bucket >= MAX_LATENCY_BUCKET_COUNT) return `invalid bucket. Expected a number between 0 and 22, got: ${b}`;
98
-
99
- return [JSON.stringify({ s, n, i }), method, bucket];
100
- }
@@ -229,7 +229,6 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
229
229
 
230
230
  /**
231
231
  * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
232
- * Clean operation (clear) also updates `lastUpdated` timestamp with current time.
233
232
  *
234
233
  * @param {number | undefined} expirationTimestamp if the value is not a number, data will not be cleaned
235
234
  */
@@ -12,8 +12,10 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
12
12
  import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
13
13
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
14
14
  import { LOG_PREFIX } from './constants';
15
- import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
15
+ import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
16
16
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
17
+ import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
18
+ import { getMatching } from '../../utils/key';
17
19
 
18
20
  export interface InLocalStorageOptions {
19
21
  prefix?: string
@@ -30,21 +32,26 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
30
32
 
31
33
  // Fallback to InMemoryStorage if LocalStorage API is not available
32
34
  if (!isLocalStorageAvailable()) {
33
- params.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
35
+ params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
34
36
  return InMemoryStorageCSFactory(params);
35
37
  }
36
38
 
37
- const log = params.log;
38
- const keys = new KeyBuilderCS(prefix, params.matchingKey as string);
39
+ const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
40
+ const matchingKey = getMatching(settings.core.key);
41
+ const keys = new KeyBuilderCS(prefix, matchingKey as string);
39
42
  const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
40
43
 
44
+ const splits = new SplitsCacheInLocal(log, keys, expirationTimestamp, __splitFiltersValidation);
45
+ const segments = new MySegmentsCacheInLocal(log, keys);
46
+
41
47
  return {
42
- splits: new SplitsCacheInLocal(log, keys, expirationTimestamp, params.splitFiltersValidation),
43
- segments: new MySegmentsCacheInLocal(log, keys),
44
- impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
45
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
46
- events: new EventsCacheInMemory(params.eventsQueueSize),
47
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
48
+ splits,
49
+ segments,
50
+ impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
51
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
52
+ events: new EventsCacheInMemory(eventsQueueSize),
53
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
54
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
48
55
 
49
56
  destroy() {
50
57
  this.splits = new SplitsCacheInMemory();
@@ -52,6 +59,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
52
59
  this.impressions.clear();
53
60
  this.impressionCounts && this.impressionCounts.clear();
54
61
  this.events.clear();
62
+ this.uniqueKeys?.clear();
55
63
  },
56
64
 
57
65
  // When using shared instanciation with MEMORY we reuse everything but segments (they are customer per key).
@@ -7,10 +7,10 @@ export class AttributesCacheInMemory {
7
7
 
8
8
  /**
9
9
  * Create or update the value for the given attribute
10
- *
10
+ *
11
11
  * @param {string} attributeName attribute name
12
12
  * @param {Object} attributeValue attribute value
13
- * @returns {boolean} the attribute was stored
13
+ * @returns {boolean} the attribute was stored
14
14
  */
15
15
  setAttribute(attributeName: string, attributeValue: Object): boolean {
16
16
  this.attributesCache[attributeName] = attributeValue;
@@ -19,7 +19,7 @@ export class AttributesCacheInMemory {
19
19
 
20
20
  /**
21
21
  * Retrieves the value of a given attribute
22
- *
22
+ *
23
23
  * @param {string} attributeName attribute name
24
24
  * @returns {Object?} stored attribute value
25
25
  */
@@ -29,7 +29,7 @@ export class AttributesCacheInMemory {
29
29
 
30
30
  /**
31
31
  * Create or update all the given attributes
32
- *
32
+ *
33
33
  * @param {[string, Object]} attributes attributes to create or update
34
34
  * @returns {boolean} attributes were stored
35
35
  */
@@ -40,7 +40,7 @@ export class AttributesCacheInMemory {
40
40
 
41
41
  /**
42
42
  * Retrieve the full attributes map
43
- *
43
+ *
44
44
  * @returns {Map<string, Object>} stored attributes
45
45
  */
46
46
  getAll(): Record<string, Object> {
@@ -49,7 +49,7 @@ export class AttributesCacheInMemory {
49
49
 
50
50
  /**
51
51
  * Removes a given attribute from the map
52
- *
52
+ *
53
53
  * @param {string} attributeName attribute to remove
54
54
  * @returns {boolean} attribute removed
55
55
  */
@@ -63,7 +63,7 @@ export class AttributesCacheInMemory {
63
63
 
64
64
  /**
65
65
  * Clears all attributes stored in the SDK
66
- *
66
+ *
67
67
  */
68
68
  clear() {
69
69
  this.attributesCache = {};
@@ -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 { STORAGE_MEMORY } from '../../utils/constants';
7
+ import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
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
@@ -13,14 +14,19 @@ import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheI
13
14
  * @param params parameters required by EventsCacheSync
14
15
  */
15
16
  export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync {
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
18
+
19
+ const splits = new SplitsCacheInMemory();
20
+ const segments = new SegmentsCacheInMemory();
16
21
 
17
22
  return {
18
- splits: new SplitsCacheInMemory(),
19
- segments: new SegmentsCacheInMemory(),
20
- impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
21
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
22
- events: new EventsCacheInMemory(params.eventsQueueSize),
23
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
23
+ splits,
24
+ segments,
25
+ impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
27
+ events: new EventsCacheInMemory(eventsQueueSize),
28
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
29
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
24
30
 
25
31
  // When using MEMORY we should clean all the caches to leave them empty
26
32
  destroy() {
@@ -29,6 +35,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
29
35
  this.impressions.clear();
30
36
  this.impressionCounts && this.impressionCounts.clear();
31
37
  this.events.clear();
38
+ this.uniqueKeys?.clear();
32
39
  }
33
40
  };
34
41
  }
@@ -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 { 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
@@ -13,14 +14,19 @@ import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheI
13
14
  * @param params parameters required by EventsCacheSync
14
15
  */
15
16
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
18
+
19
+ const splits = new SplitsCacheInMemory();
20
+ const segments = new MySegmentsCacheInMemory();
16
21
 
17
22
  return {
18
- splits: new SplitsCacheInMemory(),
19
- segments: new MySegmentsCacheInMemory(),
20
- impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
21
- impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
22
- events: new EventsCacheInMemory(params.eventsQueueSize),
23
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
23
+ splits,
24
+ segments,
25
+ impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
+ impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
27
+ events: new EventsCacheInMemory(eventsQueueSize),
28
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
29
+ uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
24
30
 
25
31
  // When using MEMORY we should clean all the caches to leave them empty
26
32
  destroy() {
@@ -29,6 +35,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
29
35
  this.impressions.clear();
30
36
  this.impressionCounts && this.impressionCounts.clear();
31
37
  this.events.clear();
38
+ this.uniqueKeys?.clear();
32
39
  },
33
40
 
34
41
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
@@ -1,14 +1,16 @@
1
- import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies } from '../../sync/submitters/types';
2
- import { LOCALHOST_MODE } from '../../utils/constants';
1
+ import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies, TelemetryUsageStatsPayload } from '../../sync/submitters/types';
2
+ import { DEDUPED, DROPPED, LOCALHOST_MODE, QUEUED } from '../../utils/constants';
3
3
  import { findLatencyIndex } from '../findLatencyIndex';
4
- import { IStorageFactoryParams, ITelemetryCacheSync } from '../types';
4
+ import { ISegmentsCacheSync, ISplitsCacheSync, IStorageFactoryParams, ITelemetryCacheSync } from '../types';
5
5
 
6
6
  const MAX_STREAMING_EVENTS = 20;
7
7
  const MAX_TAGS = 10;
8
8
  export const MAX_LATENCY_BUCKET_COUNT = 23;
9
9
 
10
10
  export function newBuckets() {
11
- return new Array(MAX_LATENCY_BUCKET_COUNT).fill(0);
11
+ // MAX_LATENCY_BUCKET_COUNT (length) is 23
12
+ // Not using Array.fill for old browsers compatibility
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];
12
14
  }
13
15
 
14
16
  const ACCEPTANCE_RANGE = 0.001;
@@ -17,12 +19,48 @@ const ACCEPTANCE_RANGE = 0.001;
17
19
  * Record telemetry if mode is not localhost.
18
20
  * All factory instances track telemetry on server-side, and 0.1% on client-side.
19
21
  */
20
- export function shouldRecordTelemetry(params: IStorageFactoryParams) {
21
- return params.mode !== LOCALHOST_MODE && (params.matchingKey === undefined || Math.random() <= ACCEPTANCE_RANGE);
22
+ export function shouldRecordTelemetry({ settings }: IStorageFactoryParams) {
23
+ return settings.mode !== LOCALHOST_MODE && (settings.core.key === undefined || Math.random() <= ACCEPTANCE_RANGE);
22
24
  }
23
25
 
24
26
  export class TelemetryCacheInMemory implements ITelemetryCacheSync {
25
27
 
28
+ constructor(private splits?: ISplitsCacheSync, private segments?: ISegmentsCacheSync) { }
29
+
30
+ // isEmpty flag
31
+ private e = true;
32
+
33
+ isEmpty() { return this.e; }
34
+
35
+ clear() { /* no-op */ }
36
+
37
+ pop(): TelemetryUsageStatsPayload {
38
+ this.e = true;
39
+
40
+ return {
41
+ lS: this.getLastSynchronization(),
42
+ mL: this.popLatencies(),
43
+ mE: this.popExceptions(),
44
+ hE: this.popHttpErrors(),
45
+ hL: this.popHttpLatencies(),
46
+ tR: this.popTokenRefreshes(),
47
+ aR: this.popAuthRejections(),
48
+ iQ: this.getImpressionStats(QUEUED),
49
+ iDe: this.getImpressionStats(DEDUPED),
50
+ iDr: this.getImpressionStats(DROPPED),
51
+ spC: this.splits && this.splits.getSplitNames().length,
52
+ seC: this.segments && this.segments.getRegisteredSegments().length,
53
+ skC: this.segments && this.segments.getKeysCount(),
54
+ sL: this.getSessionLength(),
55
+ eQ: this.getEventStats(QUEUED),
56
+ eD: this.getEventStats(DROPPED),
57
+ sE: this.popStreamingEvents(),
58
+ t: this.popTags(),
59
+ };
60
+ }
61
+
62
+ /** Config stats */
63
+
26
64
  private timeUntilReady?: number;
27
65
 
28
66
  getTimeUntilReady() {
@@ -53,6 +91,8 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
53
91
  this.notReadyUsage++;
54
92
  }
55
93
 
94
+ /** Usage stats */
95
+
56
96
  private impressionStats = [0, 0, 0];
57
97
 
58
98
  getImpressionStats(type: ImpressionDataType) {
@@ -61,6 +101,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
61
101
 
62
102
  recordImpressionStats(type: ImpressionDataType, count: number) {
63
103
  this.impressionStats[type] += count;
104
+ this.e = false;
64
105
  }
65
106
 
66
107
  private eventStats = [0, 0];
@@ -71,9 +112,9 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
71
112
 
72
113
  recordEventStats(type: EventDataType, count: number) {
73
114
  this.eventStats[type] += count;
115
+ this.e = false;
74
116
  }
75
117
 
76
- // @ts-expect-error
77
118
  private lastSync: LastSync = {};
78
119
 
79
120
  getLastSynchronization() {
@@ -82,40 +123,35 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
82
123
 
83
124
  recordSuccessfulSync(resource: OperationType, timeMs: number) {
84
125
  this.lastSync[resource] = timeMs;
126
+ this.e = false;
85
127
  }
86
128
 
87
- // @ts-expect-error
88
129
  private httpErrors: HttpErrors = {};
89
130
 
90
131
  popHttpErrors() {
91
- const result = this.httpErrors; // @ts-expect-error
132
+ const result = this.httpErrors;
92
133
  this.httpErrors = {};
93
134
  return result;
94
135
  }
95
136
 
96
137
  recordHttpError(resource: OperationType, status: number) {
97
- if (!this.httpErrors[resource]) this.httpErrors[resource] = {};
98
- if (!this.httpErrors[resource][status]) {
99
- this.httpErrors[resource][status] = 1;
100
- } else {
101
- this.httpErrors[resource][status]++;
102
- }
138
+ const statusErrors = (this.httpErrors[resource] = this.httpErrors[resource] || {});
139
+ statusErrors[status] = (statusErrors[status] || 0) + 1;
140
+ this.e = false;
103
141
  }
104
142
 
105
- // @ts-expect-error
106
143
  private httpLatencies: HttpLatencies = {};
107
144
 
108
145
  popHttpLatencies() {
109
- const result = this.httpLatencies; // @ts-expect-error
146
+ const result = this.httpLatencies;
110
147
  this.httpLatencies = {};
111
148
  return result;
112
149
  }
113
150
 
114
151
  recordHttpLatency(resource: OperationType, latencyMs: number) {
115
- if (!this.httpLatencies[resource]) {
116
- this.httpLatencies[resource] = newBuckets();
117
- }
118
- this.httpLatencies[resource][findLatencyIndex(latencyMs)]++;
152
+ const latencyBuckets = (this.httpLatencies[resource] = this.httpLatencies[resource] || newBuckets());
153
+ latencyBuckets[findLatencyIndex(latencyMs)]++;
154
+ this.e = false;
119
155
  }
120
156
 
121
157
  private authRejections = 0;
@@ -128,6 +164,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
128
164
 
129
165
  recordAuthRejections() {
130
166
  this.authRejections++;
167
+ this.e = false;
131
168
  }
132
169
 
133
170
  private tokenRefreshes = 0;
@@ -140,6 +177,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
140
177
 
141
178
  recordTokenRefreshes() {
142
179
  this.tokenRefreshes++;
180
+ this.e = false;
143
181
  }
144
182
 
145
183
  private streamingEvents: StreamingEvent[] = []
@@ -152,6 +190,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
152
190
  if (this.streamingEvents.length < MAX_STREAMING_EVENTS) {
153
191
  this.streamingEvents.push(streamingEvent);
154
192
  }
193
+ this.e = false;
155
194
  }
156
195
 
157
196
  private tags: string[] = [];
@@ -164,6 +203,7 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
164
203
  if (this.tags.length < MAX_TAGS) {
165
204
  this.tags.push(tag);
166
205
  }
206
+ this.e = false;
167
207
  }
168
208
 
169
209
  private sessionLength?: number;
@@ -174,39 +214,34 @@ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
174
214
 
175
215
  recordSessionLength(ms: number) {
176
216
  this.sessionLength = ms;
217
+ this.e = false;
177
218
  }
178
219
 
179
- // @ts-expect-error
180
220
  private exceptions: MethodExceptions = {};
181
221
 
182
222
  popExceptions() {
183
- const result = this.exceptions; // @ts-expect-error
223
+ const result = this.exceptions;
184
224
  this.exceptions = {};
185
225
  return result;
186
226
  }
187
227
 
188
228
  recordException(method: Method) {
189
- if (!this.exceptions[method]) {
190
- this.exceptions[method] = 1;
191
- } else {
192
- this.exceptions[method]++;
193
- }
229
+ this.exceptions[method] = (this.exceptions[method] || 0) + 1;
230
+ this.e = false;
194
231
  }
195
232
 
196
- // @ts-expect-error
197
233
  private latencies: MethodLatencies = {};
198
234
 
199
235
  popLatencies() {
200
- const result = this.latencies; // @ts-expect-error
236
+ const result = this.latencies;
201
237
  this.latencies = {};
202
238
  return result;
203
239
  }
204
240
 
205
241
  recordLatency(method: Method, latencyMs: number) {
206
- if (!this.latencies[method]) {
207
- this.latencies[method] = newBuckets();
208
- }
209
- this.latencies[method][findLatencyIndex(latencyMs)]++;
242
+ const latencyBuckets = (this.latencies[method] = this.latencies[method] || newBuckets());
243
+ latencyBuckets[findLatencyIndex(latencyMs)]++;
244
+ this.e = false;
210
245
  }
211
246
 
212
247
  }
@@ -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
+ }