@splitsoftware/splitio-commons 1.6.2-rc.8 → 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 (177) hide show
  1. package/CHANGES.txt +4 -1
  2. package/cjs/evaluator/index.js +5 -5
  3. package/cjs/listeners/browser.js +9 -11
  4. package/cjs/sdkClient/client.js +19 -7
  5. package/cjs/sdkFactory/index.js +7 -25
  6. package/cjs/services/splitApi.js +4 -4
  7. package/cjs/storages/AbstractSplitsCacheAsync.js +1 -1
  8. package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
  9. package/cjs/storages/KeyBuilderSS.js +9 -9
  10. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
  11. package/cjs/storages/inLocalStorage/index.js +15 -11
  12. package/cjs/storages/inMemory/InMemoryStorage.js +11 -8
  13. package/cjs/storages/inMemory/InMemoryStorageCS.js +11 -8
  14. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +65 -37
  15. package/cjs/storages/inMemory/{uniqueKeysCacheInMemory.js → UniqueKeysCacheInMemory.js} +24 -25
  16. package/cjs/storages/inMemory/{uniqueKeysCacheInMemoryCS.js → UniqueKeysCacheInMemoryCS.js} +10 -12
  17. package/cjs/storages/inRedis/EventsCacheInRedis.js +1 -1
  18. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +37 -2
  19. package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  20. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  21. package/cjs/storages/inRedis/{uniqueKeysCacheInRedis.js → UniqueKeysCacheInRedis.js} +16 -4
  22. package/cjs/storages/inRedis/index.js +6 -4
  23. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +81 -0
  24. package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  25. package/cjs/storages/pluggable/TelemetryCachePluggable.js +126 -0
  26. package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +61 -0
  27. package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
  28. package/cjs/storages/pluggable/index.js +51 -18
  29. package/cjs/storages/utils.js +73 -0
  30. package/cjs/sync/submitters/submitterManager.js +1 -1
  31. package/cjs/sync/submitters/telemetrySubmitter.js +4 -37
  32. package/cjs/sync/submitters/uniqueKeysSubmitter.js +4 -3
  33. package/cjs/trackers/impressionObserver/utils.js +1 -17
  34. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  35. package/cjs/utils/lang/maps.js +15 -7
  36. package/cjs/utils/redis/RedisMock.js +31 -0
  37. package/cjs/utils/settingsValidation/index.js +7 -4
  38. package/esm/evaluator/index.js +5 -5
  39. package/esm/listeners/browser.js +9 -11
  40. package/esm/sdkClient/client.js +19 -7
  41. package/esm/sdkFactory/index.js +7 -25
  42. package/esm/services/splitApi.js +4 -4
  43. package/esm/storages/AbstractSplitsCacheAsync.js +1 -1
  44. package/esm/storages/AbstractSplitsCacheSync.js +1 -1
  45. package/esm/storages/KeyBuilderSS.js +8 -8
  46. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
  47. package/esm/storages/inLocalStorage/index.js +16 -12
  48. package/esm/storages/inMemory/InMemoryStorage.js +13 -10
  49. package/esm/storages/inMemory/InMemoryStorageCS.js +12 -9
  50. package/esm/storages/inMemory/TelemetryCacheInMemory.js +64 -37
  51. package/esm/storages/inMemory/{uniqueKeysCacheInMemory.js → UniqueKeysCacheInMemory.js} +22 -24
  52. package/esm/storages/inMemory/{uniqueKeysCacheInMemoryCS.js → UniqueKeysCacheInMemoryCS.js} +10 -12
  53. package/esm/storages/inRedis/EventsCacheInRedis.js +1 -1
  54. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +37 -2
  55. package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  56. package/esm/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  57. package/esm/storages/inRedis/{uniqueKeysCacheInRedis.js → UniqueKeysCacheInRedis.js} +15 -3
  58. package/esm/storages/inRedis/index.js +5 -3
  59. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +78 -0
  60. package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  61. package/esm/storages/pluggable/TelemetryCachePluggable.js +126 -0
  62. package/esm/storages/pluggable/UniqueKeysCachePluggable.js +58 -0
  63. package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
  64. package/esm/storages/pluggable/index.js +52 -19
  65. package/esm/storages/utils.js +65 -0
  66. package/esm/sync/submitters/submitterManager.js +1 -1
  67. package/esm/sync/submitters/telemetrySubmitter.js +4 -36
  68. package/esm/sync/submitters/uniqueKeysSubmitter.js +4 -3
  69. package/esm/trackers/impressionObserver/utils.js +1 -15
  70. package/esm/trackers/uniqueKeysTracker.js +1 -1
  71. package/esm/utils/lang/maps.js +15 -7
  72. package/esm/utils/redis/RedisMock.js +28 -0
  73. package/esm/utils/settingsValidation/index.js +7 -4
  74. package/package.json +2 -2
  75. package/src/consent/sdkUserConsent.ts +1 -1
  76. package/src/evaluator/index.ts +6 -6
  77. package/src/listeners/browser.ts +9 -13
  78. package/src/logger/.DS_Store +0 -0
  79. package/src/sdkClient/client.ts +21 -8
  80. package/src/sdkClient/sdkClient.ts +1 -1
  81. package/src/sdkFactory/index.ts +10 -33
  82. package/src/sdkFactory/types.ts +2 -2
  83. package/src/services/splitApi.ts +6 -6
  84. package/src/services/types.ts +2 -2
  85. package/src/storages/AbstractSplitsCacheAsync.ts +1 -1
  86. package/src/storages/AbstractSplitsCacheSync.ts +1 -1
  87. package/src/storages/KeyBuilderSS.ts +13 -11
  88. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -1
  89. package/src/storages/inLocalStorage/index.ts +17 -12
  90. package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
  91. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +2 -2
  92. package/src/storages/inMemory/InMemoryStorage.ts +14 -10
  93. package/src/storages/inMemory/InMemoryStorageCS.ts +13 -10
  94. package/src/storages/inMemory/TelemetryCacheInMemory.ts +72 -35
  95. package/src/storages/inMemory/{uniqueKeysCacheInMemory.ts → UniqueKeysCacheInMemory.ts} +26 -28
  96. package/src/storages/inMemory/{uniqueKeysCacheInMemoryCS.ts → UniqueKeysCacheInMemoryCS.ts} +15 -17
  97. package/src/storages/inRedis/EventsCacheInRedis.ts +1 -1
  98. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +51 -8
  99. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
  100. package/src/storages/inRedis/TelemetryCacheInRedis.ts +122 -1
  101. package/src/storages/inRedis/{uniqueKeysCacheInRedis.ts → UniqueKeysCacheInRedis.ts} +25 -12
  102. package/src/storages/inRedis/index.ts +6 -3
  103. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +92 -0
  104. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
  105. package/src/storages/pluggable/TelemetryCachePluggable.ts +147 -1
  106. package/src/storages/pluggable/UniqueKeysCachePluggable.ts +67 -0
  107. package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
  108. package/src/storages/pluggable/index.ts +56 -20
  109. package/src/storages/types.ts +53 -70
  110. package/src/storages/utils.ts +78 -0
  111. package/src/sync/submitters/submitter.ts +2 -2
  112. package/src/sync/submitters/submitterManager.ts +1 -1
  113. package/src/sync/submitters/telemetrySubmitter.ts +9 -39
  114. package/src/sync/submitters/types.ts +33 -17
  115. package/src/sync/submitters/uniqueKeysSubmitter.ts +6 -5
  116. package/src/trackers/impressionObserver/utils.ts +1 -16
  117. package/src/trackers/impressionsTracker.ts +2 -2
  118. package/src/trackers/strategy/strategyDebug.ts +4 -4
  119. package/src/trackers/strategy/strategyNone.ts +9 -9
  120. package/src/trackers/strategy/strategyOptimized.ts +9 -9
  121. package/src/trackers/uniqueKeysTracker.ts +6 -6
  122. package/src/types.ts +0 -2
  123. package/src/utils/lang/maps.ts +20 -8
  124. package/src/utils/redis/RedisMock.ts +33 -0
  125. package/src/utils/settingsValidation/index.ts +5 -5
  126. package/types/services/types.d.ts +2 -2
  127. package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
  128. package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
  129. package/types/storages/KeyBuilderSS.d.ts +5 -2
  130. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -1
  131. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +23 -9
  132. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +9 -9
  133. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +2 -4
  134. package/types/storages/inRedis/EventsCacheInRedis.d.ts +1 -1
  135. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +3 -1
  136. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
  137. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +16 -1
  138. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +8 -2
  139. package/types/storages/pluggable/ImpressionCountsCachePluggable.d.ts +16 -0
  140. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
  141. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +17 -1
  142. package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +20 -0
  143. package/types/storages/types.d.ts +42 -49
  144. package/types/storages/utils.d.ts +8 -0
  145. package/types/sync/submitters/submitter.d.ts +2 -2
  146. package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
  147. package/types/sync/submitters/types.d.ts +27 -18
  148. package/types/trackers/impressionObserver/utils.d.ts +0 -8
  149. package/types/trackers/strategy/strategyNone.d.ts +2 -2
  150. package/types/trackers/strategy/strategyOptimized.d.ts +2 -2
  151. package/types/trackers/uniqueKeysTracker.d.ts +1 -1
  152. package/types/types.d.ts +0 -2
  153. package/types/utils/lang/maps.d.ts +6 -2
  154. package/types/utils/redis/RedisMock.d.ts +4 -0
  155. package/types/utils/settingsValidation/index.d.ts +0 -1
  156. package/cjs/storages/metadataBuilder.js +0 -12
  157. package/esm/storages/metadataBuilder.js +0 -8
  158. package/src/storages/metadataBuilder.ts +0 -11
  159. package/types/sdkClient/types.d.ts +0 -18
  160. package/types/storages/inMemory/CountsCacheInMemory.d.ts +0 -20
  161. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +0 -20
  162. package/types/storages/inRedis/CountsCacheInRedis.d.ts +0 -9
  163. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +0 -9
  164. package/types/sync/offline/LocalhostFromFile.d.ts +0 -2
  165. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +0 -2
  166. package/types/sync/submitters/eventsSyncTask.d.ts +0 -8
  167. package/types/sync/submitters/impressionCountsSubmitterInRedis.d.ts +0 -5
  168. package/types/sync/submitters/impressionCountsSyncTask.d.ts +0 -13
  169. package/types/sync/submitters/impressionsSyncTask.d.ts +0 -14
  170. package/types/sync/submitters/metricsSyncTask.d.ts +0 -12
  171. package/types/sync/submitters/submitterSyncTask.d.ts +0 -10
  172. package/types/sync/submitters/uniqueKeysSubmitterInRedis.d.ts +0 -5
  173. package/types/sync/syncTaskComposite.d.ts +0 -5
  174. package/types/trackers/filter/bloomFilter.d.ts +0 -10
  175. package/types/trackers/filter/dictionaryFilter.d.ts +0 -8
  176. package/types/trackers/filter/types.d.ts +0 -5
  177. package/types/utils/timeTracker/index.d.ts +0 -70
@@ -39,25 +39,25 @@ export function inMemoryWrapperFactory(connDelay?: number): IPluggableStorageWra
39
39
  getKeysByPrefix(prefix: string) {
40
40
  return Promise.resolve(Object.keys(_cache).filter(key => startsWith(key, prefix)));
41
41
  },
42
- incr(key: string) {
42
+ incr(key: string, increment = 1) {
43
43
  if (key in _cache) {
44
- const count = toNumber(_cache[key]) + 1;
44
+ const count = toNumber(_cache[key]) + increment;
45
45
  if (isNaN(count)) return Promise.reject('Given key is not a number');
46
46
  _cache[key] = count + '';
47
47
  return Promise.resolve(count);
48
48
  } else {
49
- _cache[key] = '1';
49
+ _cache[key] = '' + increment;
50
50
  return Promise.resolve(1);
51
51
  }
52
52
  },
53
- decr(key: string) {
53
+ decr(key: string, decrement = 1) {
54
54
  if (key in _cache) {
55
- const count = toNumber(_cache[key]) - 1;
55
+ const count = toNumber(_cache[key]) - decrement;
56
56
  if (isNaN(count)) return Promise.reject('Given key is not a number');
57
57
  _cache[key] = count + '';
58
58
  return Promise.resolve(count);
59
59
  } else {
60
- _cache[key] = '-1';
60
+ _cache[key] = '-' + decrement;
61
61
  return Promise.resolve(-1);
62
62
  }
63
63
  },
@@ -1,4 +1,4 @@
1
- import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types';
1
+ import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams, ITelemetryCacheAsync } from '../types';
2
2
 
3
3
  import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { SplitsCachePluggable } from './SplitsCachePluggable';
@@ -8,10 +8,17 @@ import { EventsCachePluggable } from './EventsCachePluggable';
8
8
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
9
9
  import { isObject } from '../../utils/lang';
10
10
  import { validatePrefix } from '../KeyBuilder';
11
- import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
11
+ import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
12
12
  import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
13
13
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
14
14
  import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
15
+ import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
16
+ import { TelemetryCachePluggable } from './TelemetryCachePluggable';
17
+ import { ImpressionCountsCachePluggable } from './ImpressionCountsCachePluggable';
18
+ import { UniqueKeysCachePluggable } from './UniqueKeysCachePluggable';
19
+ import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
20
+ import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
21
+ import { metadataBuilder } from '../utils';
15
22
 
16
23
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
17
24
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -35,16 +42,6 @@ function validatePluggableStorageOptions(options: any) {
35
42
  if (missingMethods.length) throw new Error(`${NO_VALID_WRAPPER_INTERFACE} The following methods are missing or invalid: ${missingMethods}`);
36
43
  }
37
44
 
38
- // subscription to wrapper connect event in order to emit SDK_READY event
39
- function wrapperConnect(wrapper: IPluggableStorageWrapper, onReadyCb: (error?: any) => void) {
40
- wrapper.connect().then(() => {
41
- onReadyCb();
42
- // At the moment, we don't synchronize config with pluggable storage
43
- }).catch((e) => {
44
- onReadyCb(e || new Error('Error connecting wrapper'));
45
- });
46
- }
47
-
48
45
  // Async return type in `client.track` method on consumer partial mode
49
46
  // No need to promisify impressions cache
50
47
  function promisifyEventsTrack(events: any) {
@@ -64,31 +61,70 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
64
61
 
65
62
  const prefix = validatePrefix(options.prefix);
66
63
 
67
- function PluggableStorageFactory({ log, metadata, onReadyCb, mode, eventsQueueSize, impressionsQueueSize, optimize }: IStorageFactoryParams): IStorageAsync {
64
+ function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
65
+ const { onReadyCb, settings, settings: { log, mode, sync: { impressionsMode }, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
66
+ const metadata = metadataBuilder(settings);
68
67
  const keys = new KeyBuilderSS(prefix, metadata);
69
68
  const wrapper = wrapperAdapter(log, options.wrapper);
69
+
70
+ const isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
70
71
  const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
71
72
 
73
+ const telemetry = shouldRecordTelemetry(params) || isSyncronizer ?
74
+ isPartialConsumer ?
75
+ new TelemetryCacheInMemory() :
76
+ new TelemetryCachePluggable(log, keys, wrapper) :
77
+ undefined;
78
+
79
+ const impressionCountsCache = impressionsMode !== DEBUG || isSyncronizer ?
80
+ isPartialConsumer ?
81
+ new ImpressionCountsCacheInMemory() :
82
+ new ImpressionCountsCachePluggable(log, keys.buildImpressionsCountKey(), wrapper) :
83
+ undefined;
84
+
85
+ const uniqueKeysCache = impressionsMode === NONE || isSyncronizer ?
86
+ isPartialConsumer ?
87
+ settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
88
+ new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
89
+ undefined;
90
+
72
91
  // Connects to wrapper and emits SDK_READY event on main client
73
- wrapperConnect(wrapper, onReadyCb);
92
+ const connectPromise = wrapper.connect().then(() => {
93
+ onReadyCb();
94
+
95
+ // Start periodic flush of async storages if not running synchronizer (producer mode)
96
+ if (!isSyncronizer) {
97
+ if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
98
+ if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
99
+ if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
100
+ }
101
+ }).catch((e) => {
102
+ e = e || new Error('Error connecting wrapper');
103
+ onReadyCb(e);
104
+ return e;
105
+ });
74
106
 
75
107
  return {
76
108
  splits: new SplitsCachePluggable(log, keys, wrapper),
77
109
  segments: new SegmentsCachePluggable(log, keys, wrapper),
78
110
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
79
- impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
111
+ impressionCounts: impressionCountsCache,
80
112
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
81
- // @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
82
- // telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
113
+ telemetry,
114
+ uniqueKeys: uniqueKeysCache,
83
115
 
84
- // Disconnect the underlying storage
116
+ // Stop periodic flush and disconnect the underlying storage
85
117
  destroy() {
86
- return wrapper.disconnect();
118
+ return Promise.all(isSyncronizer ? [] : [
119
+ impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).stop && (impressionCountsCache as ImpressionCountsCachePluggable).stop(),
120
+ uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).stop && (uniqueKeysCache as UniqueKeysCachePluggable).stop(),
121
+ ]).then(() => wrapper.disconnect());
87
122
  },
88
123
 
89
124
  // emits SDK_READY event on shared clients and returns a reference to the storage
90
125
  shared(_, onReadyCb) {
91
- wrapperConnect(wrapper, onReadyCb);
126
+ connectPromise.then(onReadyCb);
127
+
92
128
  return {
93
129
  ...this,
94
130
  // no-op destroy, to disconnect the wrapper only when the main client is destroyed
@@ -1,7 +1,6 @@
1
- import { MaybeThenable, IMetadata, ISplitFiltersValidation, ISplit } from '../dtos/types';
2
- import { ILogger } from '../logger/types';
3
- import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs } from '../sync/submitters/types';
4
- import { SplitIO, ImpressionDTO, SDKMode } from '../types';
1
+ import { MaybeThenable, ISplit } from '../dtos/types';
2
+ import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload } from '../sync/submitters/types';
3
+ import { SplitIO, ImpressionDTO, ISettings } from '../types';
5
4
 
6
5
  /**
7
6
  * Interface of a pluggable storage wrapper.
@@ -70,23 +69,25 @@ export interface IPluggableStorageWrapper {
70
69
  /** Integer operations */
71
70
 
72
71
  /**
73
- * Increments in 1 the given `key` value or set it to 1 if the value doesn't exist.
72
+ * Increments the number stored at `key` by `increment`, or set it to `increment` if the value doesn't exist.
74
73
  *
75
74
  * @function incr
76
75
  * @param {string} key Key to increment
76
+ * @param {number} increment Value to increment by. Defaults to 1.
77
77
  * @returns {Promise<number>} A promise that resolves with the value of key after the increment. The promise rejects if the operation fails,
78
78
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
79
79
  */
80
- incr: (key: string) => Promise<number>
80
+ incr: (key: string, increment?: number) => Promise<number>
81
81
  /**
82
- * Decrements in 1 the given `key` value or set it to -1 if the value doesn't exist.
82
+ * Decrements the number stored at `key` by `decrement`, or set it to minus `decrement` if the value doesn't exist.
83
83
  *
84
84
  * @function decr
85
85
  * @param {string} key Key to decrement
86
+ * @param {number} decrement Value to decrement by. Defaults to 1.
86
87
  * @returns {Promise<number>} A promise that resolves with the value of key after the decrement. The promise rejects if the operation fails,
87
88
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
88
89
  */
89
- decr: (key: string) => Promise<number>
90
+ decr: (key: string, decrement?: number) => Promise<number>
90
91
 
91
92
  /** Queue operations */
92
93
 
@@ -283,19 +284,29 @@ export interface ISegmentsCacheAsync extends ISegmentsCacheBase {
283
284
  /** Recorder storages (impressions, events and telemetry) */
284
285
 
285
286
  export interface IImpressionsCacheBase {
286
- // Consumer API method, used by impressions tracker, in standalone and consumer modes, to push impressions into the storage.
287
+ // Used by impressions tracker, in DEBUG and OPTIMIZED impression modes, to push impressions into the storage.
287
288
  track(data: ImpressionDTO[]): MaybeThenable<void>
288
289
  }
289
290
 
290
291
  export interface IEventsCacheBase {
291
- // Consumer API method, used by events tracker, in standalone and consumer modes, to push events into the storage.
292
+ // Used by events tracker to push events into the storage.
292
293
  track(data: SplitIO.EventData, size?: number): MaybeThenable<boolean>
293
294
  }
294
295
 
295
- /** Impressions and events cache for standalone mode (sync) */
296
+ export interface IImpressionCountsCacheBase {
297
+ // Used by impressions tracker, in OPTIMIZED and NONE impression modes, to count impressions.
298
+ track(featureName: string, timeFrame: number, amount: number): void
299
+ }
300
+
301
+ export interface IUniqueKeysCacheBase {
302
+ // Used by impressions tracker, in NONE impression mode, to track unique keys.
303
+ track(key: string, value: string): void
304
+ }
296
305
 
297
- // Producer API methods for sync recorder storages, used by submitters in standalone mode to pop data and post it to Split BE.
298
- export interface IRecorderCacheProducerSync<T> {
306
+ /** Impressions and events cache for standalone and partial consumer modes (sync methods) */
307
+
308
+ // API methods for sync recorder storages, used by submitters in standalone mode to pop data and post it to Split BE.
309
+ export interface IRecorderCacheSync<T> {
299
310
  // @TODO names are inconsistent with spec
300
311
  /* Checks if cache is empty. Returns true if the cache was just created or cleared */
301
312
  isEmpty(): boolean
@@ -305,23 +316,29 @@ export interface IRecorderCacheProducerSync<T> {
305
316
  pop(toMerge?: T): T
306
317
  }
307
318
 
308
-
309
- export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheProducerSync<ImpressionDTO[]> {
319
+ export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheSync<ImpressionDTO[]> {
310
320
  track(data: ImpressionDTO[]): void
311
321
  /* Registers callback for full queue */
312
322
  setOnFullQueueCb(cb: () => void): void
313
323
  }
314
324
 
315
- export interface IEventsCacheSync extends IEventsCacheBase, IRecorderCacheProducerSync<SplitIO.EventData[]> {
325
+ export interface IEventsCacheSync extends IEventsCacheBase, IRecorderCacheSync<SplitIO.EventData[]> {
316
326
  track(data: SplitIO.EventData, size?: number): boolean
317
327
  /* Registers callback for full queue */
318
328
  setOnFullQueueCb(cb: () => void): void
319
329
  }
320
330
 
321
- /** Impressions and events cache for consumer and producer mode (async) */
331
+ /* Named `ImpressionsCounter` in spec */
332
+ export interface IImpressionCountsCacheSync extends IImpressionCountsCacheBase, IRecorderCacheSync<Record<string, number>> { }
322
333
 
323
- // Producer API methods for async recorder storages, used by submitters in producer mode to pop data and post it to Split BE.
324
- export interface IRecorderCacheProducerAsync<T> {
334
+ export interface IUniqueKeysCacheSync extends IUniqueKeysCacheBase, IRecorderCacheSync<UniqueKeysPayloadSs | UniqueKeysPayloadCs> {
335
+ setOnFullQueueCb(cb: () => void): void,
336
+ }
337
+
338
+ /** Impressions and events cache for consumer and producer modes (async methods) */
339
+
340
+ // API methods for async recorder storages, used by submitters in producer mode (synchronizer) to pop data and post it to Split BE.
341
+ export interface IRecorderCacheAsync<T> {
325
342
  /* returns the number of stored items */
326
343
  count(): Promise<number>
327
344
  /* removes the given number of items from the store. If not provided, it deletes all items */
@@ -330,43 +347,18 @@ export interface IRecorderCacheProducerAsync<T> {
330
347
  popNWithMetadata(count: number): Promise<T>
331
348
  }
332
349
 
333
- export interface IImpressionsCacheAsync extends IImpressionsCacheBase, IRecorderCacheProducerAsync<StoredImpressionWithMetadata[]> {
350
+ export interface IImpressionsCacheAsync extends IImpressionsCacheBase, IRecorderCacheAsync<StoredImpressionWithMetadata[]> {
334
351
  // Consumer API method, used by impressions tracker (in standalone and consumer modes) to push data into.
335
352
  // The result promise can reject.
336
353
  track(data: ImpressionDTO[]): Promise<void>
337
354
  }
338
355
 
339
- export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheProducerAsync<StoredEventWithMetadata[]> {
356
+ export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheAsync<StoredEventWithMetadata[]> {
340
357
  // Consumer API method, used by events tracker (in standalone and consumer modes) to push data into.
341
358
  // The result promise cannot reject.
342
359
  track(data: SplitIO.EventData, size?: number): Promise<boolean>
343
360
  }
344
361
 
345
- /**
346
- * Impression counts cache for impressions dedup in standalone and producer mode.
347
- * Only in memory. Named `ImpressionsCounter` in spec.
348
- */
349
- export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<Record<string, number>> {
350
- // Used by impressions tracker
351
- track(featureName: string, timeFrame: number, amount: number): void
352
-
353
- // Used by impressions count submitter in standalone and producer mode
354
- isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
355
- pop(toMerge?: Record<string, number> ): Record<string, number> // pop cache data
356
- }
357
-
358
- export interface IUniqueKeysCacheBase {
359
- // Used by unique Keys tracker
360
- track(key: string, value: string): void
361
-
362
- // Used by unique keys submitter in standalone and producer mode
363
- isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
364
- pop(): UniqueKeysPayloadSs | UniqueKeysPayloadCs // pop cache data
365
- /* Registers callback for full queue */
366
- setOnFullQueueCb(cb: () => void): void,
367
- clear(): void
368
- }
369
-
370
362
  /**
371
363
  * Telemetry storage interface for standalone and partial consumer modes.
372
364
  * Methods are sync because data is stored in memory.
@@ -426,7 +418,7 @@ export interface ITelemetryEvaluationProducerSync {
426
418
 
427
419
  export interface ITelemetryStorageProducerSync extends ITelemetryInitProducerSync, ITelemetryRuntimeProducerSync, ITelemetryEvaluationProducerSync { }
428
420
 
429
- export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITelemetryStorageProducerSync { }
421
+ export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITelemetryStorageProducerSync, IRecorderCacheSync<TelemetryUsageStatsPayload> { }
430
422
 
431
423
  /**
432
424
  * Telemetry storage interface for consumer mode.
@@ -434,18 +426,19 @@ export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITel
434
426
  */
435
427
 
436
428
  export interface ITelemetryEvaluationConsumerAsync {
437
- popExceptions(): Promise<MethodExceptions>;
438
- popLatencies(): Promise<MethodLatencies>;
429
+ popLatencies(): Promise<MultiMethodLatencies>;
430
+ popExceptions(): Promise<MultiMethodExceptions>;
431
+ popConfigs(): Promise<MultiConfigs>;
439
432
  }
440
433
 
441
434
  export interface ITelemetryEvaluationProducerAsync {
442
435
  recordLatency(method: Method, latencyMs: number): Promise<any>;
443
436
  recordException(method: Method): Promise<any>;
437
+ recordConfig(): Promise<any>;
444
438
  }
445
439
 
446
440
  // ATM it only implements the producer API, used by the SDK in consumer mode.
447
- // @TODO implement consumer API for JS Synchronizer.
448
- export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync { }
441
+ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync, ITelemetryEvaluationConsumerAsync { }
449
442
 
450
443
  /**
451
444
  * Storages
@@ -455,7 +448,7 @@ export interface IStorageBase<
455
448
  TSplitsCache extends ISplitsCacheBase,
456
449
  TSegmentsCache extends ISegmentsCacheBase,
457
450
  TImpressionsCache extends IImpressionsCacheBase,
458
- TImpressionsCountCache extends IImpressionCountsCacheSync,
451
+ TImpressionsCountCache extends IImpressionCountsCacheBase,
459
452
  TEventsCache extends IEventsCacheBase,
460
453
  TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
461
454
  TUniqueKeysCache extends IUniqueKeysCacheBase
@@ -478,16 +471,16 @@ export interface IStorageSync extends IStorageBase<
478
471
  IImpressionCountsCacheSync,
479
472
  IEventsCacheSync,
480
473
  ITelemetryCacheSync,
481
- IUniqueKeysCacheBase
474
+ IUniqueKeysCacheSync
482
475
  > { }
483
476
 
484
477
  export interface IStorageAsync extends IStorageBase<
485
478
  ISplitsCacheAsync,
486
479
  ISegmentsCacheAsync,
487
480
  IImpressionsCacheAsync | IImpressionsCacheSync,
488
- IImpressionCountsCacheSync,
481
+ IImpressionCountsCacheBase,
489
482
  IEventsCacheAsync | IEventsCacheSync,
490
- ITelemetryCacheAsync,
483
+ ITelemetryCacheAsync | ITelemetryCacheSync,
491
484
  IUniqueKeysCacheBase
492
485
  > { }
493
486
 
@@ -496,22 +489,12 @@ export interface IStorageAsync extends IStorageBase<
496
489
  export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
497
490
 
498
491
  export interface IStorageFactoryParams {
499
- log: ILogger,
500
- impressionsQueueSize?: number,
501
- uniqueKeysCacheSize?: number;
502
- eventsQueueSize?: number,
503
- optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
504
- mode: SDKMode,
505
- impressionsMode?: string,
506
- // ATM, only used by InLocalStorage
507
- matchingKey?: string, /* undefined on server-side SDKs */
508
- splitFiltersValidation?: ISplitFiltersValidation,
509
-
510
- // This callback is invoked when the storage is ready to be used. Error-first callback style: if an error is passed,
511
- // it means that the storge fail to connect and shouldn't be used.
512
- // It is meant for emitting SDK_READY event in consumer mode, and for synchronizer to wait before using the storage.
492
+ settings: ISettings,
493
+ /**
494
+ * Error-first callback invoked when the storage is ready to be used. An error means that the storage failed to connect and shouldn't be used.
495
+ * It is meant for emitting SDK_READY event in consumer mode, and waiting before using the storage in the synchronizer.
496
+ */
513
497
  onReadyCb: (error?: any) => void,
514
- metadata: IMetadata,
515
498
  }
516
499
 
517
500
  export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
@@ -0,0 +1,78 @@
1
+ // Shared utils for Redis and Pluggable storage
2
+
3
+ import { IMetadata } from '../dtos/types';
4
+ import { Method, StoredImpressionWithMetadata } from '../sync/submitters/types';
5
+ import { ImpressionDTO, ISettings } from '../types';
6
+ import { UNKNOWN } from '../utils/constants';
7
+ import { MAX_LATENCY_BUCKET_COUNT } from './inMemory/TelemetryCacheInMemory';
8
+ import { METHOD_NAMES } from './KeyBuilderSS';
9
+
10
+ export function metadataBuilder(settings: Pick<ISettings, 'version' | 'runtime'>): IMetadata {
11
+ return {
12
+ s: settings.version,
13
+ i: settings.runtime.ip || UNKNOWN,
14
+ n: settings.runtime.hostname || UNKNOWN,
15
+ };
16
+ }
17
+
18
+ // Converts impressions to be stored in Redis or pluggable storage.
19
+ export function impressionsToJSON(impressions: ImpressionDTO[], metadata: IMetadata): string[] {
20
+ return impressions.map(impression => {
21
+ const impressionWithMetadata: StoredImpressionWithMetadata = {
22
+ m: metadata,
23
+ i: {
24
+ k: impression.keyName,
25
+ b: impression.bucketingKey,
26
+ f: impression.feature,
27
+ t: impression.treatment,
28
+ r: impression.label,
29
+ c: impression.changeNumber,
30
+ m: impression.time,
31
+ pt: impression.pt,
32
+ }
33
+ };
34
+
35
+ return JSON.stringify(impressionWithMetadata);
36
+ });
37
+ }
38
+
39
+ // Utilities used by TelemetryCacheInRedis and TelemetryCachePluggable
40
+
41
+ const REVERSE_METHOD_NAMES = Object.keys(METHOD_NAMES).reduce((acc, key) => {
42
+ acc[METHOD_NAMES[key as Method]] = key as Method;
43
+ return acc;
44
+ }, {} as Record<string, Method>);
45
+
46
+
47
+ export function parseMetadata(field: string): [metadata: string] | string {
48
+ const parts = field.split('/');
49
+ if (parts.length !== 3) return `invalid subsection count. Expected 3, got: ${parts.length}`;
50
+
51
+ const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */] = parts;
52
+ return [JSON.stringify({ s, n, i })];
53
+ }
54
+
55
+ export function parseExceptionField(field: string): [metadata: string, method: Method] | string {
56
+ const parts = field.split('/');
57
+ if (parts.length !== 4) return `invalid subsection count. Expected 4, got: ${parts.length}`;
58
+
59
+ const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m] = parts;
60
+ const method = REVERSE_METHOD_NAMES[m];
61
+ if (!method) return `unknown method '${m}'`;
62
+
63
+ return [JSON.stringify({ s, n, i }), method];
64
+ }
65
+
66
+ export function parseLatencyField(field: string): [metadata: string, method: Method, bucket: number] | string {
67
+ const parts = field.split('/');
68
+ if (parts.length !== 5) return `invalid subsection count. Expected 5, got: ${parts.length}`;
69
+
70
+ const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m, b] = parts;
71
+ const method = REVERSE_METHOD_NAMES[m];
72
+ if (!method) return `unknown method '${m}'`;
73
+
74
+ const bucket = parseInt(b);
75
+ if (isNaN(bucket) || bucket >= MAX_LATENCY_BUCKET_COUNT) return `invalid bucket. Expected a number between 0 and ${MAX_LATENCY_BUCKET_COUNT - 1}, got: ${b}`;
76
+
77
+ return [JSON.stringify({ s, n, i }), method, bucket];
78
+ }
@@ -1,6 +1,6 @@
1
1
  import { syncTaskFactory } from '../syncTask';
2
2
  import { ISyncTask } from '../types';
3
- import { IRecorderCacheProducerSync } from '../../storages/types';
3
+ import { IRecorderCacheSync } from '../../storages/types';
4
4
  import { ILogger } from '../../logger/types';
5
5
  import { SUBMITTERS_PUSH, SUBMITTERS_PUSH_FAILS, SUBMITTERS_PUSH_RETRY } from '../../logger/constants';
6
6
  import { IResponse } from '../../services/types';
@@ -11,7 +11,7 @@ import { IResponse } from '../../services/types';
11
11
  export function submitterFactory<T>(
12
12
  log: ILogger,
13
13
  postClient: (body: string) => Promise<IResponse>,
14
- sourceCache: IRecorderCacheProducerSync<T>,
14
+ sourceCache: IRecorderCacheSync<T>,
15
15
  postRate: number,
16
16
  dataName: string,
17
17
  fromCacheToPayload?: (cacheData: T) => any,
@@ -16,7 +16,7 @@ export function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmit
16
16
  const impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
17
17
  if (impressionCountsSubmitter) submitters.push(impressionCountsSubmitter);
18
18
  const telemetrySubmitter = telemetrySubmitterFactory(params);
19
- if (params.uniqueKeysTracker) submitters.push(uniqueKeysSubmitterFactory(params));
19
+ if (params.storage.uniqueKeys) submitters.push(uniqueKeysSubmitterFactory(params));
20
20
 
21
21
  return {
22
22
  // `onlyTelemetry` true if SDK is created with userConsent not GRANTED
@@ -1,7 +1,7 @@
1
- import { ISegmentsCacheSync, ISplitsCacheSync, ITelemetryCacheSync } from '../../storages/types';
1
+ import { ITelemetryCacheSync } from '../../storages/types';
2
2
  import { submitterFactory, firstPushWindowDecorator } from './submitter';
3
- import { TelemetryUsageStatsPayload, TelemetryConfigStatsPayload, TelemetryConfigStats } from './types';
4
- import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, NONE, DEBUG_ENUM, OPTIMIZED_ENUM, NONE_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
3
+ import { TelemetryConfigStatsPayload, TelemetryConfigStats } from './types';
4
+ import { CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, NONE, DEBUG_ENUM, OPTIMIZED_ENUM, NONE_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
5
5
  import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
6
6
  import { ConsentStatus, ISettings, SDKMode } from '../../types';
7
7
  import { base } from '../../utils/settingsValidation';
@@ -10,40 +10,6 @@ import { timer } from '../../utils/timeTracker/timer';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
11
  import { objectAssign } from '../../utils/lang/objectAssign';
12
12
 
13
- /**
14
- * Converts data from telemetry cache into /metrics/usage request payload.
15
- */
16
- export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, splits: ISplitsCacheSync, segments: ISegmentsCacheSync) {
17
- return {
18
- isEmpty() { return false; }, // There is always data in telemetry cache
19
- clear() { }, // No-op
20
-
21
- // @TODO consider moving inside telemetry cache for code size reduction
22
- pop(): TelemetryUsageStatsPayload {
23
- return {
24
- lS: telemetry.getLastSynchronization(),
25
- mL: telemetry.popLatencies(),
26
- mE: telemetry.popExceptions(),
27
- hE: telemetry.popHttpErrors(),
28
- hL: telemetry.popHttpLatencies(),
29
- tR: telemetry.popTokenRefreshes(),
30
- aR: telemetry.popAuthRejections(),
31
- iQ: telemetry.getImpressionStats(QUEUED),
32
- iDe: telemetry.getImpressionStats(DEDUPED),
33
- iDr: telemetry.getImpressionStats(DROPPED),
34
- spC: splits.getSplitNames().length,
35
- seC: segments.getRegisteredSegments().length,
36
- skC: segments.getKeysCount(),
37
- sL: telemetry.getSessionLength(),
38
- eQ: telemetry.getEventStats(QUEUED),
39
- eD: telemetry.getEventStats(DROPPED),
40
- sE: telemetry.popStreamingEvents(),
41
- t: telemetry.popTags(),
42
- };
43
- }
44
- };
45
- }
46
-
47
13
  const OPERATION_MODE_MAP = {
48
14
  [STANDALONE_MODE]: STANDALONE_ENUM,
49
15
  [CONSUMER_MODE]: CONSUMER_ENUM,
@@ -130,14 +96,18 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
130
96
  * Submitter that periodically posts telemetry data
131
97
  */
132
98
  export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
133
- const { storage: { splits, segments, telemetry }, platform: { now } } = params;
99
+ const { storage: { telemetry }, platform: { now } } = params;
134
100
  if (!telemetry || !now) return; // No submitter created if telemetry cache is not defined
135
101
 
136
102
  const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, readiness, sdkReadinessManager } = params;
137
103
  const startTime = timer(now);
138
104
 
139
105
  const submitter = firstPushWindowDecorator(
140
- submitterFactory(log, splitApi.postMetricsUsage, telemetryCacheStatsAdapter(telemetry, splits, segments), telemetryRefreshRate, 'telemetry stats', undefined, 0, true),
106
+ submitterFactory(
107
+ log, splitApi.postMetricsUsage,
108
+ telemetry,
109
+ telemetryRefreshRate, 'telemetry stats', undefined, 0, true
110
+ ),
141
111
  telemetryRefreshRate
142
112
  );
143
113