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

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 (164) hide show
  1. package/CHANGES.txt +2 -1
  2. package/cjs/consent/sdkUserConsent.js +2 -2
  3. package/cjs/evaluator/index.js +5 -5
  4. package/cjs/listeners/browser.js +1 -2
  5. package/cjs/logger/constants.js +1 -2
  6. package/cjs/sdkClient/client.js +19 -7
  7. package/cjs/sdkClient/sdkClient.js +1 -3
  8. package/cjs/sdkFactory/index.js +5 -23
  9. package/cjs/services/splitApi.js +4 -24
  10. package/cjs/storages/KeyBuilderSS.js +48 -15
  11. package/cjs/storages/inLocalStorage/index.js +1 -5
  12. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +1 -12
  13. package/cjs/storages/inMemory/InMemoryStorage.js +2 -6
  14. package/cjs/storages/inMemory/InMemoryStorageCS.js +2 -6
  15. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +9 -6
  16. package/cjs/storages/inRedis/EventsCacheInRedis.js +1 -1
  17. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  18. package/cjs/storages/inRedis/constants.js +1 -4
  19. package/cjs/storages/inRedis/index.js +2 -17
  20. package/cjs/storages/pluggable/TelemetryCachePluggable.js +126 -0
  21. package/cjs/storages/pluggable/index.js +19 -15
  22. package/cjs/sync/submitters/submitterManager.js +0 -3
  23. package/cjs/sync/submitters/telemetrySubmitter.js +7 -5
  24. package/cjs/trackers/impressionsTracker.js +41 -22
  25. package/cjs/utils/constants/index.js +2 -4
  26. package/cjs/utils/lang/maps.js +15 -7
  27. package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
  28. package/cjs/utils/settingsValidation/index.js +0 -3
  29. package/esm/consent/sdkUserConsent.js +2 -2
  30. package/esm/evaluator/index.js +5 -5
  31. package/esm/listeners/browser.js +2 -3
  32. package/esm/logger/constants.js +0 -1
  33. package/esm/sdkClient/client.js +19 -7
  34. package/esm/sdkClient/sdkClient.js +1 -3
  35. package/esm/sdkFactory/index.js +5 -23
  36. package/esm/services/splitApi.js +4 -24
  37. package/esm/storages/KeyBuilderSS.js +44 -14
  38. package/esm/storages/inLocalStorage/index.js +2 -6
  39. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +1 -12
  40. package/esm/storages/inMemory/InMemoryStorage.js +4 -8
  41. package/esm/storages/inMemory/InMemoryStorageCS.js +3 -7
  42. package/esm/storages/inMemory/TelemetryCacheInMemory.js +8 -6
  43. package/esm/storages/inRedis/EventsCacheInRedis.js +1 -1
  44. package/esm/storages/inRedis/TelemetryCacheInRedis.js +100 -0
  45. package/esm/storages/inRedis/constants.js +0 -3
  46. package/esm/storages/inRedis/index.js +3 -18
  47. package/esm/storages/pluggable/TelemetryCachePluggable.js +126 -0
  48. package/esm/storages/pluggable/index.js +19 -15
  49. package/esm/sync/submitters/submitterManager.js +0 -3
  50. package/esm/sync/submitters/telemetrySubmitter.js +8 -6
  51. package/esm/trackers/impressionsTracker.js +41 -22
  52. package/esm/utils/constants/index.js +0 -2
  53. package/esm/utils/lang/maps.js +15 -7
  54. package/esm/utils/settingsValidation/impressionsMode.js +3 -3
  55. package/esm/utils/settingsValidation/index.js +0 -3
  56. package/package.json +2 -1
  57. package/src/consent/sdkUserConsent.ts +2 -2
  58. package/src/evaluator/index.ts +6 -6
  59. package/src/listeners/browser.ts +2 -3
  60. package/src/logger/.DS_Store +0 -0
  61. package/src/logger/constants.ts +0 -1
  62. package/src/sdkClient/client.ts +21 -8
  63. package/src/sdkClient/sdkClient.ts +1 -3
  64. package/src/sdkFactory/index.ts +5 -26
  65. package/src/sdkFactory/types.ts +4 -7
  66. package/src/services/splitApi.ts +4 -26
  67. package/src/services/types.ts +2 -8
  68. package/src/storages/KeyBuilderSS.ts +53 -17
  69. package/src/storages/inLocalStorage/index.ts +2 -5
  70. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +1 -16
  71. package/src/storages/inMemory/InMemoryStorage.ts +4 -7
  72. package/src/storages/inMemory/InMemoryStorageCS.ts +3 -7
  73. package/src/storages/inMemory/TelemetryCacheInMemory.ts +9 -7
  74. package/src/storages/inRedis/EventsCacheInRedis.ts +1 -1
  75. package/src/storages/inRedis/TelemetryCacheInRedis.ts +122 -2
  76. package/src/storages/inRedis/constants.ts +0 -3
  77. package/src/storages/inRedis/index.ts +5 -15
  78. package/src/storages/pluggable/TelemetryCachePluggable.ts +147 -2
  79. package/src/storages/pluggable/index.ts +20 -16
  80. package/src/storages/types.ts +13 -31
  81. package/src/sync/submitters/submitterManager.ts +0 -2
  82. package/src/sync/submitters/telemetrySubmitter.ts +14 -9
  83. package/src/sync/submitters/types.ts +40 -26
  84. package/src/trackers/impressionsTracker.ts +48 -27
  85. package/src/trackers/types.ts +0 -28
  86. package/src/types.ts +1 -3
  87. package/src/utils/constants/index.ts +0 -2
  88. package/src/utils/lang/maps.ts +20 -8
  89. package/src/utils/settingsValidation/impressionsMode.ts +3 -3
  90. package/src/utils/settingsValidation/index.ts +0 -4
  91. package/types/logger/constants.d.ts +0 -1
  92. package/types/sdkFactory/types.d.ts +2 -4
  93. package/types/services/types.d.ts +2 -6
  94. package/types/storages/KeyBuilderSS.d.ts +7 -4
  95. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +1 -5
  96. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +6 -3
  97. package/types/storages/inRedis/EventsCacheInRedis.d.ts +1 -1
  98. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +16 -1
  99. package/types/storages/inRedis/constants.d.ts +0 -3
  100. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +17 -1
  101. package/types/storages/types.d.ts +10 -18
  102. package/types/sync/submitters/telemetrySubmitter.d.ts +1 -1
  103. package/types/sync/submitters/types.d.ts +13 -24
  104. package/types/trackers/impressionsTracker.d.ts +6 -4
  105. package/types/trackers/types.d.ts +0 -23
  106. package/types/types.d.ts +1 -3
  107. package/types/utils/constants/index.d.ts +0 -2
  108. package/types/utils/lang/maps.d.ts +6 -2
  109. package/types/utils/settingsValidation/index.d.ts +0 -1
  110. package/cjs/storages/inMemory/uniqueKeysCacheInMemory.js +0 -73
  111. package/cjs/storages/inMemory/uniqueKeysCacheInMemoryCS.js +0 -78
  112. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +0 -50
  113. package/cjs/storages/inRedis/uniqueKeysCacheInRedis.js +0 -59
  114. package/cjs/sync/submitters/uniqueKeysSubmitter.js +0 -26
  115. package/cjs/trackers/strategy/strategyDebug.js +0 -25
  116. package/cjs/trackers/strategy/strategyNone.js +0 -29
  117. package/cjs/trackers/strategy/strategyOptimized.js +0 -35
  118. package/cjs/trackers/uniqueKeysTracker.js +0 -38
  119. package/esm/storages/inMemory/uniqueKeysCacheInMemory.js +0 -70
  120. package/esm/storages/inMemory/uniqueKeysCacheInMemoryCS.js +0 -75
  121. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +0 -47
  122. package/esm/storages/inRedis/uniqueKeysCacheInRedis.js +0 -56
  123. package/esm/sync/submitters/uniqueKeysSubmitter.js +0 -22
  124. package/esm/trackers/strategy/strategyDebug.js +0 -21
  125. package/esm/trackers/strategy/strategyNone.js +0 -25
  126. package/esm/trackers/strategy/strategyOptimized.js +0 -31
  127. package/esm/trackers/uniqueKeysTracker.js +0 -34
  128. package/src/storages/inMemory/uniqueKeysCacheInMemory.ts +0 -82
  129. package/src/storages/inMemory/uniqueKeysCacheInMemoryCS.ts +0 -88
  130. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +0 -52
  131. package/src/storages/inRedis/uniqueKeysCacheInRedis.ts +0 -64
  132. package/src/sync/submitters/uniqueKeysSubmitter.ts +0 -35
  133. package/src/trackers/strategy/strategyDebug.ts +0 -28
  134. package/src/trackers/strategy/strategyNone.ts +0 -34
  135. package/src/trackers/strategy/strategyOptimized.ts +0 -42
  136. package/src/trackers/uniqueKeysTracker.ts +0 -48
  137. package/types/sdkClient/types.d.ts +0 -18
  138. package/types/storages/inMemory/CountsCacheInMemory.d.ts +0 -20
  139. package/types/storages/inMemory/LatenciesCacheInMemory.d.ts +0 -20
  140. package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +0 -35
  141. package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +0 -37
  142. package/types/storages/inRedis/CountsCacheInRedis.d.ts +0 -9
  143. package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +0 -14
  144. package/types/storages/inRedis/LatenciesCacheInRedis.d.ts +0 -9
  145. package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +0 -15
  146. package/types/sync/offline/LocalhostFromFile.d.ts +0 -2
  147. package/types/sync/offline/splitsParser/splitsParserFromFile.d.ts +0 -2
  148. package/types/sync/submitters/eventsSyncTask.d.ts +0 -8
  149. package/types/sync/submitters/impressionCountsSubmitterInRedis.d.ts +0 -5
  150. package/types/sync/submitters/impressionCountsSyncTask.d.ts +0 -13
  151. package/types/sync/submitters/impressionsSyncTask.d.ts +0 -14
  152. package/types/sync/submitters/metricsSyncTask.d.ts +0 -12
  153. package/types/sync/submitters/submitterSyncTask.d.ts +0 -10
  154. package/types/sync/submitters/uniqueKeysSubmitter.d.ts +0 -5
  155. package/types/sync/submitters/uniqueKeysSubmitterInRedis.d.ts +0 -5
  156. package/types/sync/syncTaskComposite.d.ts +0 -5
  157. package/types/trackers/filter/bloomFilter.d.ts +0 -10
  158. package/types/trackers/filter/dictionaryFilter.d.ts +0 -8
  159. package/types/trackers/filter/types.d.ts +0 -5
  160. package/types/trackers/strategy/strategyDebug.d.ts +0 -9
  161. package/types/trackers/strategy/strategyNone.d.ts +0 -10
  162. package/types/trackers/strategy/strategyOptimized.d.ts +0 -11
  163. package/types/trackers/uniqueKeysTracker.d.ts +0 -13
  164. package/types/utils/timeTracker/index.d.ts +0 -70
@@ -13,11 +13,6 @@ import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
13
13
  import { metadataBuilder } from '../storages/metadataBuilder';
14
14
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
15
15
  import { objectAssign } from '../utils/lang/objectAssign';
16
- import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
17
- import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
18
- import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
19
- import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
20
- import { NONE, OPTIMIZED } from '../utils/constants';
21
16
 
22
17
  /**
23
18
  * Modular SDK factory
@@ -26,8 +21,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
26
21
 
27
22
  const { settings, platform, storageFactory, splitApiFactory, extraProps,
28
23
  syncManagerFactory, SignalListener, impressionsObserverFactory,
29
- integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
30
- filterAdapterFactory } = params;
24
+ integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory } = params;
31
25
  const log = settings.log;
32
26
 
33
27
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid API Key, etc.
@@ -43,7 +37,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
43
37
  const storageFactoryParams: IStorageFactoryParams = {
44
38
  impressionsQueueSize: settings.scheduler.impressionsQueueSize,
45
39
  eventsQueueSize: settings.scheduler.eventsQueueSize,
46
- uniqueKeysCacheSize: settings.scheduler.uniqueKeysCacheSize,
47
40
  optimize: shouldBeOptimized(settings),
48
41
 
49
42
  // ATM, only used by InLocalStorage
@@ -52,7 +45,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
52
45
 
53
46
  // ATM, only used by PluggableStorage
54
47
  mode: settings.mode,
55
- impressionsMode: settings.sync.impressionsMode,
56
48
 
57
49
  // Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined,
58
50
  // or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events.
@@ -71,28 +63,15 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
71
63
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
72
64
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
73
65
 
74
- const observer = impressionsObserverFactory();
75
- const uniqueKeysTracker = storageFactoryParams.impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;
76
-
77
- let strategy;
78
- switch (storageFactoryParams.impressionsMode) {
79
- case OPTIMIZED:
80
- strategy = strategyOptimizedFactory(observer, storage.impressionCounts!);
81
- break;
82
- case NONE:
83
- strategy = strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!);
84
- break;
85
- default:
86
- strategy = strategyDebugFactory(observer);
87
- }
88
-
89
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
66
+ // trackers
67
+ const observer = impressionsObserverFactory && impressionsObserverFactory();
68
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, integrationsManager, observer, storage.impressionCounts, storage.telemetry);
90
69
  const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
91
70
 
92
71
  // splitApi is used by SyncManager and Browser signal listener
93
72
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
94
73
 
95
- const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
74
+ const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, readiness, settings, storage, platform };
96
75
 
97
76
  const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
98
77
  ctx.syncManager = syncManager;
@@ -6,7 +6,7 @@ import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
6
6
  import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
7
7
  import { ISyncManager } from '../sync/types';
8
8
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
9
- import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
9
+ import { IImpressionsTracker, IEventTracker, ITelemetryTracker } from '../trackers/types';
10
10
  import { SplitIO, ISettings, IEventEmitter } from '../types';
11
11
 
12
12
  /**
@@ -44,7 +44,6 @@ export interface ISdkFactoryContext {
44
44
  eventTracker: IEventTracker,
45
45
  telemetryTracker: ITelemetryTracker,
46
46
  storage: IStorageSync | IStorageAsync,
47
- uniqueKeysTracker?: IUniqueKeysTracker,
48
47
  signalListener?: ISignalListener
49
48
  splitApi?: ISplitApi
50
49
  syncManager?: ISyncManager,
@@ -97,11 +96,6 @@ export interface ISdkFactoryParams {
97
96
  // It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
98
97
  sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
99
98
 
100
- // Impression observer factory. If provided, will be used for impressions dedupe
101
- impressionsObserverFactory: () => IImpressionObserver
102
-
103
- filterAdapterFactory?: () => IFilterAdapter
104
-
105
99
  // Optional signal listener constructor. Used to handle special app states, like shutdown, app paused or resumed.
106
100
  // Pass only if `syncManager` (used by Node listener) and `splitApi` (used by Browser listener) are passed.
107
101
  SignalListener?: new (
@@ -113,6 +107,9 @@ export interface ISdkFactoryParams {
113
107
  // @TODO review impressionListener and integrations interfaces. What about handling impressionListener as an integration ?
114
108
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined,
115
109
 
110
+ // Impression observer factory. If provided, will be used for impressions dedupe
111
+ impressionsObserverFactory?: () => IImpressionObserver
112
+
116
113
  // Optional function to assign additional properties to the factory instance
117
114
  extraProps?: (params: ISdkFactoryContext) => object
118
115
  }
@@ -106,37 +106,15 @@ export function splitApiFactory(
106
106
  const url = `${urls.events}/testImpressions/count`;
107
107
  return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(IMPRESSIONS_COUNT));
108
108
  },
109
-
110
- /**
111
- * Post unique keys for client side.
112
- *
113
- * @param body unique keys payload
114
- * @param headers Optionals headers to overwrite default ones. For example, it is used in producer mode to overwrite metadata headers.
115
- */
116
- postUniqueKeysBulkCs(body: string, headers?: Record<string, string>) {
117
- const url = `${urls.telemetry}/v1/keys/cs`;
118
- return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
119
- },
120
-
121
- /**
122
- * Post unique keys for server side.
123
- *
124
- * @param body unique keys payload
125
- * @param headers Optionals headers to overwrite default ones. For example, it is used in producer mode to overwrite metadata headers.
126
- */
127
- postUniqueKeysBulkSs(body: string, headers?: Record<string, string>) {
128
- const url = `${urls.telemetry}/v1/keys/ss`;
129
- return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
130
- },
131
109
 
132
- postMetricsConfig(body: string) {
110
+ postMetricsConfig(body: string, headers?: Record<string, string>) {
133
111
  const url = `${urls.telemetry}/v1/metrics/config`;
134
- return splitHttpClient(url, { method: 'POST', body }, telemetryTracker.trackHttp(TELEMETRY), true);
112
+ return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY), true);
135
113
  },
136
114
 
137
- postMetricsUsage(body: string) {
115
+ postMetricsUsage(body: string, headers?: Record<string, string>) {
138
116
  const url = `${urls.telemetry}/v1/metrics/usage`;
139
- return splitHttpClient(url, { method: 'POST', body }, telemetryTracker.trackHttp(TELEMETRY), true);
117
+ return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY), true);
140
118
  }
141
119
  };
142
120
  }
@@ -43,17 +43,13 @@ export type IFetchMySegments = (userMatchingKey: string, noCache?: boolean) => P
43
43
 
44
44
  export type IPostEventsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>
45
45
 
46
- export type IPostUniqueKeysBulkCs = (body: string, headers?: Record<string, string>) => Promise<IResponse>
47
-
48
- export type IPostUniqueKeysBulkSs = (body: string, headers?: Record<string, string>) => Promise<IResponse>
49
-
50
46
  export type IPostTestImpressionsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>
51
47
 
52
48
  export type IPostTestImpressionsCount = (body: string, headers?: Record<string, string>) => Promise<IResponse>
53
49
 
54
- export type IPostMetricsConfig = (body: string) => Promise<IResponse>
50
+ export type IPostMetricsConfig = (body: string, headers?: Record<string, string>) => Promise<IResponse>
55
51
 
56
- export type IPostMetricsUsage = (body: string) => Promise<IResponse>
52
+ export type IPostMetricsUsage = (body: string, headers?: Record<string, string>) => Promise<IResponse>
57
53
 
58
54
  export interface ISplitApi {
59
55
  getSdkAPIHealthCheck: IHealthCheckAPI
@@ -63,8 +59,6 @@ export interface ISplitApi {
63
59
  fetchSegmentChanges: IFetchSegmentChanges
64
60
  fetchMySegments: IFetchMySegments
65
61
  postEventsBulk: IPostEventsBulk
66
- postUniqueKeysBulkCs: IPostUniqueKeysBulkCs
67
- postUniqueKeysBulkSs: IPostUniqueKeysBulkSs
68
62
  postTestImpressionsBulk: IPostTestImpressionsBulk
69
63
  postTestImpressionsCount: IPostTestImpressionsCount
70
64
  postMetricsConfig: IPostMetricsConfig
@@ -1,8 +1,9 @@
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';
4
5
 
5
- const methodNames: Record<Method, string> = {
6
+ const METHOD_NAMES: Record<Method, string> = {
6
7
  t: 'treatment',
7
8
  ts: 'treatments',
8
9
  tc: 'treatmentWithConfig',
@@ -12,11 +13,17 @@ const methodNames: Record<Method, string> = {
12
13
 
13
14
  export class KeyBuilderSS extends KeyBuilder {
14
15
 
15
- protected readonly metadata: IMetadata;
16
+ latencyPrefix: string;
17
+ exceptionPrefix: string;
18
+ initPrefix: string;
19
+ private versionablePrefix: string;
16
20
 
17
21
  constructor(prefix: string, metadata: IMetadata) {
18
22
  super(prefix);
19
- this.metadata = metadata;
23
+ this.latencyPrefix = `${this.prefix}.telemetry.latencies`;
24
+ this.exceptionPrefix = `${this.prefix}.telemetry.exceptions`;
25
+ this.initPrefix = `${this.prefix}.telemetry.init`;
26
+ this.versionablePrefix = `${metadata.s}/${metadata.n}/${metadata.i}`;
20
27
  }
21
28
 
22
29
  buildRegisteredSegmentsKey() {
@@ -27,14 +34,6 @@ export class KeyBuilderSS extends KeyBuilder {
27
34
  return `${this.prefix}.impressions`;
28
35
  }
29
36
 
30
- buildImpressionsCountKey() {
31
- return `${this.prefix}.impressions.count`;
32
- }
33
-
34
- buildUniqueKeysKey() {
35
- return `${this.prefix}.uniquekeys`;
36
- }
37
-
38
37
  buildEventsKey() {
39
38
  return `${this.prefix}.events`;
40
39
  }
@@ -46,19 +45,56 @@ export class KeyBuilderSS extends KeyBuilder {
46
45
  /* Telemetry keys */
47
46
 
48
47
  buildLatencyKey(method: Method, bucket: number) {
49
- return `${this.prefix}.telemetry.latencies::${this.buildVersionablePrefix()}/${methodNames[method]}/${bucket}`;
48
+ return `${this.latencyPrefix}::${this.versionablePrefix}/${METHOD_NAMES[method]}/${bucket}`;
50
49
  }
51
50
 
52
51
  buildExceptionKey(method: Method) {
53
- return `${this.prefix}.telemetry.exceptions::${this.buildVersionablePrefix()}/${methodNames[method]}`;
52
+ return `${this.exceptionPrefix}::${this.versionablePrefix}/${METHOD_NAMES[method]}`;
54
53
  }
55
54
 
56
55
  buildInitKey() {
57
- return `${this.prefix}.telemetry.init::${this.buildVersionablePrefix()}`;
56
+ return `${this.initPrefix}::${this.versionablePrefix}`;
58
57
  }
59
58
 
60
- private buildVersionablePrefix() {
61
- return `${this.metadata.s}/${this.metadata.n}/${this.metadata.i}`;
62
- }
59
+ }
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}`;
63
98
 
99
+ return [JSON.stringify({ s, n, i }), method, bucket];
64
100
  }
@@ -12,9 +12,8 @@ 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 { LOCALHOST_MODE, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
15
+ import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
16
16
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
17
- import { UniqueKeysCacheInMemoryCS } from '../inMemory/uniqueKeysCacheInMemoryCS';
18
17
 
19
18
  export interface InLocalStorageOptions {
20
19
  prefix?: string
@@ -45,8 +44,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
45
44
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
46
45
  impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
47
46
  events: new EventsCacheInMemory(params.eventsQueueSize),
48
- telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
49
- uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
47
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
50
48
 
51
49
  destroy() {
52
50
  this.splits = new SplitsCacheInMemory();
@@ -54,7 +52,6 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
54
52
  this.impressions.clear();
55
53
  this.impressionCounts && this.impressionCounts.clear();
56
54
  this.events.clear();
57
- this.uniqueKeys?.clear();
58
55
  },
59
56
 
60
57
  // When using shared instanciation with MEMORY we reuse everything but segments (they are customer per key).
@@ -1,16 +1,8 @@
1
1
  import { truncateTimeFrame } from '../../utils/time';
2
- import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
3
2
  import { IImpressionCountsCacheSync } from '../types';
4
3
 
5
4
  export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync {
6
- protected cache: Record<string, number> = {};
7
- private readonly maxStorage: number;
8
- protected onFullQueue?: () => void;
9
- private cacheSize = 0;
10
-
11
- constructor(impressionCountsCacheSize: number = DEFAULT_CACHE_SIZE) {
12
- this.maxStorage = impressionCountsCacheSize;
13
- }
5
+ private cache: Record<string, number> = {};
14
6
 
15
7
  /**
16
8
  * Builds key to be stored in the cache with the featureName and the timeFrame truncated.
@@ -26,13 +18,6 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync
26
18
  const key = this._makeKey(featureName, timeFrame);
27
19
  const currentAmount = this.cache[key];
28
20
  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
- }
36
21
  }
37
22
 
38
23
 
@@ -4,9 +4,8 @@ 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 { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
- import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
- import { UniqueKeysCacheInMemory } from './uniqueKeysCacheInMemory';
7
+ import { STORAGE_MEMORY } from '../../utils/constants';
8
+ import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
10
9
 
11
10
  /**
12
11
  * InMemory storage factory for standalone server-side SplitFactory
@@ -19,10 +18,9 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
19
18
  splits: new SplitsCacheInMemory(),
20
19
  segments: new SegmentsCacheInMemory(),
21
20
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
22
- impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
21
+ impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
23
22
  events: new EventsCacheInMemory(params.eventsQueueSize),
24
- telemetry: params.mode !== LOCALHOST_MODE ? new TelemetryCacheInMemory() : undefined, // Always track telemetry in standalone mode on server-side
25
- uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemory(params.uniqueKeysCacheSize) : undefined,
23
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
26
24
 
27
25
  // When using MEMORY we should clean all the caches to leave them empty
28
26
  destroy() {
@@ -31,7 +29,6 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
31
29
  this.impressions.clear();
32
30
  this.impressionCounts && this.impressionCounts.clear();
33
31
  this.events.clear();
34
- this.uniqueKeys?.clear();
35
32
  }
36
33
  };
37
34
  }
@@ -4,9 +4,8 @@ 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 { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
7
+ import { STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
- import { UniqueKeysCacheInMemoryCS } from './uniqueKeysCacheInMemoryCS';
10
9
 
11
10
  /**
12
11
  * InMemory storage factory for standalone client-side SplitFactory
@@ -19,11 +18,9 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
19
18
  splits: new SplitsCacheInMemory(),
20
19
  segments: new MySegmentsCacheInMemory(),
21
20
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
22
- impressionCounts: params.impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
21
+ impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
23
22
  events: new EventsCacheInMemory(params.eventsQueueSize),
24
- telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
25
- uniqueKeys: params.impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS(params.uniqueKeysCacheSize) : undefined,
26
-
23
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory() : undefined,
27
24
 
28
25
  // When using MEMORY we should clean all the caches to leave them empty
29
26
  destroy() {
@@ -32,7 +29,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
32
29
  this.impressions.clear();
33
30
  this.impressionCounts && this.impressionCounts.clear();
34
31
  this.events.clear();
35
- this.uniqueKeys?.clear();
36
32
  },
37
33
 
38
34
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
@@ -1,22 +1,24 @@
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
- return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
10
+ export function newBuckets() {
11
+ return new Array(MAX_LATENCY_BUCKET_COUNT).fill(0);
11
12
  }
12
13
 
13
14
  const ACCEPTANCE_RANGE = 0.001;
14
15
 
15
16
  /**
16
- * Used on client-side. 0.1% of instances will track telemetry
17
+ * Record telemetry if mode is not localhost.
18
+ * All factory instances track telemetry on server-side, and 0.1% on client-side.
17
19
  */
18
- export function shouldRecordTelemetry() {
19
- return Math.random() <= ACCEPTANCE_RANGE;
20
+ export function shouldRecordTelemetry(params: IStorageFactoryParams) {
21
+ return params.mode !== LOCALHOST_MODE && (params.matchingKey === undefined || Math.random() <= ACCEPTANCE_RANGE);
20
22
  }
21
23
 
22
24
  export class TelemetryCacheInMemory implements ITelemetryCacheSync {
@@ -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.
@@ -1,11 +1,14 @@
1
1
  import { ILogger } from '../../logger/types';
2
- import { Method } from '../../sync/submitters/types';
3
- import { KeyBuilderSS } from '../KeyBuilderSS';
2
+ import { Method, MultiConfigs, MultiMethodExceptions, MultiMethodLatencies } from '../../sync/submitters/types';
3
+ import { KeyBuilderSS, parseExceptionField, parseLatencyField, parseMetadata } from '../KeyBuilderSS';
4
4
  import { ITelemetryCacheAsync } from '../types';
5
5
  import { findLatencyIndex } from '../findLatencyIndex';
6
6
  import { Redis } from 'ioredis';
7
7
  import { getTelemetryConfigStats } from '../../sync/submitters/telemetrySubmitter';
8
8
  import { CONSUMER_MODE, STORAGE_REDIS } from '../../utils/constants';
9
+ import { isNaNNumber, isString } from '../../utils/lang';
10
+ import { _Map } from '../../utils/lang/maps';
11
+ import { MAX_LATENCY_BUCKET_COUNT, newBuckets } from '../inMemory/TelemetryCacheInMemory';
9
12
 
10
13
  export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
11
14
 
@@ -22,6 +25,7 @@ export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
22
25
  return this.redis.hincrby(key, field, 1)
23
26
  .catch(() => { /* Handle rejections for telemetry */ });
24
27
  }
28
+
25
29
  recordException(method: Method) {
26
30
  const [key, field] = this.keys.buildExceptionKey(method).split('::');
27
31
  return this.redis.hincrby(key, field, 1)
@@ -33,4 +37,120 @@ export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
33
37
  const value = JSON.stringify(getTelemetryConfigStats(CONSUMER_MODE, STORAGE_REDIS));
34
38
  return this.redis.hset(key, field, value).catch(() => { /* Handle rejections for telemetry */ });
35
39
  }
40
+
41
+ /**
42
+ * Pop telemetry latencies.
43
+ * The returned promise rejects if redis operations fail.
44
+ */
45
+ popLatencies(): Promise<MultiMethodLatencies> {
46
+ return this.redis.hgetall(this.keys.latencyPrefix).then(latencies => {
47
+
48
+ const result: MultiMethodLatencies = new _Map();
49
+
50
+ Object.keys(latencies).forEach(field => {
51
+
52
+ const parsedField = parseLatencyField(field);
53
+ if (isString(parsedField)) {
54
+ this.log.error(`Ignoring invalid latency field: ${field}: ${parsedField}`);
55
+ return;
56
+ }
57
+
58
+ const count = parseInt(latencies[field]);
59
+ if (isNaNNumber(count)) {
60
+ this.log.error(`Ignoring latency with invalid count: ${latencies[field]}`);
61
+ return;
62
+ }
63
+
64
+ const [metadata, method, bucket] = parsedField;
65
+
66
+ if (bucket >= MAX_LATENCY_BUCKET_COUNT) {
67
+ this.log.error(`Ignoring latency with invalid bucket: ${bucket}`);
68
+ return;
69
+ }
70
+
71
+ if (!result.has(metadata)) result.set(metadata, {
72
+ t: newBuckets(),
73
+ ts: newBuckets(),
74
+ tc: newBuckets(),
75
+ tcs: newBuckets(),
76
+ tr: newBuckets(),
77
+ });
78
+
79
+ result.get(metadata)![method][bucket] = count;
80
+ });
81
+
82
+ return this.redis.del(this.keys.latencyPrefix).then(() => result);
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Pop telemetry exceptions.
88
+ * The returned promise rejects if redis operations fail.
89
+ */
90
+ popExceptions(): Promise<MultiMethodExceptions> {
91
+ return this.redis.hgetall(this.keys.exceptionPrefix).then(exceptions => {
92
+
93
+ const result: MultiMethodExceptions = new _Map();
94
+
95
+ Object.keys(exceptions).forEach(field => {
96
+
97
+ const parsedField = parseExceptionField(field);
98
+ if (isString(parsedField)) {
99
+ this.log.error(`Ignoring invalid exception field: ${field}: ${parsedField}`);
100
+ return;
101
+ }
102
+
103
+ const count = parseInt(exceptions[field]);
104
+ if (isNaNNumber(count)) {
105
+ this.log.error(`Ignoring exception with invalid count: ${exceptions[field]}`);
106
+ return;
107
+ }
108
+
109
+ const [metadata, method] = parsedField;
110
+
111
+ if (!result.has(metadata)) result.set(metadata, {
112
+ t: 0,
113
+ ts: 0,
114
+ tc: 0,
115
+ tcs: 0,
116
+ tr: 0,
117
+ });
118
+
119
+ result.get(metadata)![method] = count;
120
+ });
121
+
122
+ return this.redis.del(this.keys.exceptionPrefix).then(() => result);
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Pop telemetry configs.
128
+ * The returned promise rejects if redis operations fail.
129
+ */
130
+ popConfigs(): Promise<MultiConfigs> {
131
+ return this.redis.hgetall(this.keys.initPrefix).then(configs => {
132
+
133
+ const result: MultiConfigs = new _Map();
134
+
135
+ Object.keys(configs).forEach(field => {
136
+
137
+ const parsedField = parseMetadata(field);
138
+ if (isString(parsedField)) {
139
+ this.log.error(`Ignoring invalid config field: ${field}: ${parsedField}`);
140
+ return;
141
+ }
142
+
143
+ const [metadata] = parsedField;
144
+
145
+ try {
146
+ const config = JSON.parse(configs[field]);
147
+ result.set(metadata, config);
148
+ } catch (e) {
149
+ this.log.error(`Ignoring invalid config: ${configs[field]}`);
150
+ }
151
+ });
152
+
153
+ return this.redis.del(this.keys.initPrefix).then(() => result);
154
+ });
155
+ }
36
156
  }
@@ -1,4 +1 @@
1
1
  export const LOG_PREFIX = 'storage:redis: ';
2
- export const DEFAULT_CACHE_SIZE = 30000;
3
- export const REFRESH_RATE = 300000; // 300.000 ms = start after 5 mins
4
- export const TTL_REFRESH = 3600; // 1hr