@splitsoftware/splitio-commons 1.6.2-rc.12 → 1.6.2-rc.14

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 (95) hide show
  1. package/cjs/listeners/browser.js +9 -11
  2. package/cjs/sdkFactory/index.js +7 -24
  3. package/cjs/storages/KeyBuilderSS.js +4 -43
  4. package/cjs/storages/inLocalStorage/index.js +14 -10
  5. package/cjs/storages/inMemory/InMemoryStorage.js +10 -7
  6. package/cjs/storages/inMemory/InMemoryStorageCS.js +10 -7
  7. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +57 -34
  8. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
  9. package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  10. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
  11. package/cjs/storages/inRedis/index.js +4 -2
  12. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
  13. package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  14. package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
  15. package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
  16. package/cjs/storages/pluggable/index.js +4 -2
  17. package/cjs/storages/utils.js +73 -0
  18. package/cjs/sync/submitters/submitterManager.js +1 -1
  19. package/cjs/sync/submitters/telemetrySubmitter.js +4 -40
  20. package/cjs/trackers/impressionObserver/utils.js +1 -17
  21. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  22. package/cjs/utils/settingsValidation/index.js +7 -1
  23. package/esm/listeners/browser.js +9 -11
  24. package/esm/sdkFactory/index.js +7 -24
  25. package/esm/storages/KeyBuilderSS.js +1 -37
  26. package/esm/storages/inLocalStorage/index.js +15 -11
  27. package/esm/storages/inMemory/InMemoryStorage.js +10 -7
  28. package/esm/storages/inMemory/InMemoryStorageCS.js +10 -7
  29. package/esm/storages/inMemory/TelemetryCacheInMemory.js +58 -35
  30. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
  31. package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  32. package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
  33. package/esm/storages/inRedis/index.js +4 -2
  34. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
  35. package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  36. package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
  37. package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
  38. package/esm/storages/pluggable/index.js +4 -2
  39. package/esm/storages/utils.js +65 -0
  40. package/esm/sync/submitters/submitterManager.js +1 -1
  41. package/esm/sync/submitters/telemetrySubmitter.js +4 -39
  42. package/esm/trackers/impressionObserver/utils.js +1 -15
  43. package/esm/trackers/uniqueKeysTracker.js +1 -1
  44. package/esm/utils/settingsValidation/index.js +7 -1
  45. package/package.json +2 -1
  46. package/src/listeners/browser.ts +9 -13
  47. package/src/sdkClient/sdkClient.ts +1 -1
  48. package/src/sdkFactory/index.ts +7 -29
  49. package/src/sdkFactory/types.ts +2 -2
  50. package/src/services/splitApi.ts +2 -2
  51. package/src/storages/KeyBuilderSS.ts +2 -44
  52. package/src/storages/inLocalStorage/index.ts +16 -11
  53. package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
  54. package/src/storages/inMemory/InMemoryStorage.ts +11 -7
  55. package/src/storages/inMemory/InMemoryStorageCS.ts +11 -7
  56. package/src/storages/inMemory/TelemetryCacheInMemory.ts +66 -33
  57. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +3 -3
  58. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
  59. package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
  60. package/src/storages/inRedis/index.ts +4 -1
  61. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +3 -3
  62. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
  63. package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
  64. package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
  65. package/src/storages/pluggable/index.ts +4 -2
  66. package/src/storages/types.ts +45 -64
  67. package/src/storages/utils.ts +78 -0
  68. package/src/sync/submitters/submitter.ts +2 -2
  69. package/src/sync/submitters/submitterManager.ts +1 -1
  70. package/src/sync/submitters/telemetrySubmitter.ts +5 -41
  71. package/src/sync/submitters/types.ts +7 -5
  72. package/src/trackers/impressionObserver/utils.ts +1 -16
  73. package/src/trackers/impressionsTracker.ts +2 -2
  74. package/src/trackers/strategy/strategyDebug.ts +4 -4
  75. package/src/trackers/strategy/strategyNone.ts +9 -9
  76. package/src/trackers/strategy/strategyOptimized.ts +9 -9
  77. package/src/trackers/uniqueKeysTracker.ts +6 -6
  78. package/src/utils/redis/RedisMock.ts +5 -5
  79. package/src/utils/settingsValidation/index.ts +5 -1
  80. package/types/storages/KeyBuilderSS.d.ts +1 -3
  81. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +19 -8
  82. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
  83. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
  84. package/types/storages/types.d.ts +35 -45
  85. package/types/storages/utils.d.ts +8 -0
  86. package/types/sync/submitters/submitter.d.ts +2 -2
  87. package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
  88. package/types/sync/submitters/types.d.ts +8 -6
  89. package/types/trackers/impressionObserver/utils.d.ts +0 -8
  90. package/types/trackers/strategy/strategyNone.d.ts +2 -2
  91. package/types/trackers/strategy/strategyOptimized.d.ts +2 -2
  92. package/types/trackers/uniqueKeysTracker.d.ts +1 -1
  93. package/cjs/storages/metadataBuilder.js +0 -12
  94. package/esm/storages/metadataBuilder.js +0 -8
  95. package/src/storages/metadataBuilder.ts +0 -11
@@ -4,6 +4,7 @@ import { ImpressionDTO } from '../../types';
4
4
  import { Redis } from 'ioredis';
5
5
  import { StoredImpressionWithMetadata } from '../../sync/submitters/types';
6
6
  import { ILogger } from '../../logger/types';
7
+ import { impressionsToJSON } from '../utils';
7
8
 
8
9
  const IMPRESSIONS_TTL_REFRESH = 3600; // 1 hr
9
10
 
@@ -24,7 +25,7 @@ export class ImpressionsCacheInRedis implements IImpressionsCacheAsync {
24
25
  track(impressions: ImpressionDTO[]): Promise<void> { // @ts-ignore
25
26
  return this.redis.rpush(
26
27
  this.key,
27
- this._toJSON(impressions)
28
+ impressionsToJSON(impressions, this.metadata),
28
29
  ).then(queuedCount => {
29
30
  // If this is the creation of the key on Redis, set the expiration for it in 1hr.
30
31
  if (queuedCount === impressions.length) {
@@ -33,27 +34,6 @@ export class ImpressionsCacheInRedis implements IImpressionsCacheAsync {
33
34
  });
34
35
  }
35
36
 
36
- private _toJSON(impressions: ImpressionDTO[]): string[] {
37
- return impressions.map(impression => {
38
- const {
39
- keyName, bucketingKey, feature, treatment, label, time, changeNumber
40
- } = impression;
41
-
42
- return JSON.stringify({
43
- m: this.metadata,
44
- i: {
45
- k: keyName,
46
- b: bucketingKey,
47
- f: feature,
48
- t: treatment,
49
- r: label,
50
- c: changeNumber,
51
- m: time
52
- }
53
- });
54
- });
55
- }
56
-
57
37
  count(): Promise<number> {
58
38
  return this.redis.llen(this.key).catch(() => 0);
59
39
  }
@@ -1,6 +1,6 @@
1
1
  import { ILogger } from '../../logger/types';
2
2
  import { Method, MultiConfigs, MultiMethodExceptions, MultiMethodLatencies } from '../../sync/submitters/types';
3
- import { KeyBuilderSS, parseExceptionField, parseLatencyField, parseMetadata } from '../KeyBuilderSS';
3
+ import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { ITelemetryCacheAsync } from '../types';
5
5
  import { findLatencyIndex } from '../findLatencyIndex';
6
6
  import { Redis } from 'ioredis';
@@ -9,6 +9,7 @@ import { CONSUMER_MODE, STORAGE_REDIS } from '../../utils/constants';
9
9
  import { isNaNNumber, isString } from '../../utils/lang';
10
10
  import { _Map } from '../../utils/lang/maps';
11
11
  import { MAX_LATENCY_BUCKET_COUNT, newBuckets } from '../inMemory/TelemetryCacheInMemory';
12
+ import { parseLatencyField, parseExceptionField, parseMetadata } from '../utils';
12
13
 
13
14
  export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
14
15
 
@@ -76,7 +77,7 @@ export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
76
77
  tr: newBuckets(),
77
78
  });
78
79
 
79
- result.get(metadata)![method][bucket] = count;
80
+ result.get(metadata)![method]![bucket] = count;
80
81
  });
81
82
 
82
83
  return this.redis.del(this.keys.latencyPrefix).then(() => result);
@@ -10,6 +10,7 @@ import { DEBUG, NONE, STORAGE_REDIS } from '../../utils/constants';
10
10
  import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
11
11
  import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
12
12
  import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
13
+ import { metadataBuilder } from '../utils';
13
14
 
14
15
  export interface InRedisStorageOptions {
15
16
  prefix?: string
@@ -24,7 +25,9 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
24
25
 
25
26
  const prefix = validatePrefix(options.prefix);
26
27
 
27
- function InRedisStorageFactory({ log, metadata, onReadyCb, impressionsMode }: IStorageFactoryParams): IStorageAsync {
28
+ function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync {
29
+ const { onReadyCb, settings, settings: { log, sync: { impressionsMode } } } = params;
30
+ const metadata = metadataBuilder(settings);
28
31
  const keys = new KeyBuilderSS(prefix, metadata);
29
32
  const redisClient = new RedisAdapter(log, options.options || {});
30
33
  const telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
@@ -54,7 +54,7 @@ export class ImpressionCountsCachePluggable extends ImpressionCountsCacheInMemor
54
54
  .then(counts => {
55
55
  keys.forEach(key => this.wrapper.del(key).catch(() => { /* noop */ }));
56
56
 
57
- const impressionsCount: ImpressionCountsPayload = { pf: [] };
57
+ const pf = [];
58
58
 
59
59
  for (let i = 0; i < keys.length; i++) {
60
60
  const key = keys[i];
@@ -78,14 +78,14 @@ export class ImpressionCountsCachePluggable extends ImpressionCountsCacheInMemor
78
78
  continue;
79
79
  }
80
80
 
81
- impressionsCount.pf.push({
81
+ pf.push({
82
82
  f: keyFeatureNameAndTime[1],
83
83
  m: timeFrame,
84
84
  rc: rawCount,
85
85
  });
86
86
  }
87
87
 
88
- return impressionsCount;
88
+ return { pf };
89
89
  }) : undefined;
90
90
  });
91
91
  }
@@ -1,8 +1,9 @@
1
1
  import { IPluggableStorageWrapper, IImpressionsCacheAsync } from '../types';
2
2
  import { IMetadata } from '../../dtos/types';
3
3
  import { ImpressionDTO } from '../../types';
4
- import { ILogger } from '../../logger/types';
5
4
  import { StoredImpressionWithMetadata } from '../../sync/submitters/types';
5
+ import { ILogger } from '../../logger/types';
6
+ import { impressionsToJSON } from '../utils';
6
7
 
7
8
  export class ImpressionsCachePluggable implements IImpressionsCacheAsync {
8
9
 
@@ -27,31 +28,10 @@ export class ImpressionsCachePluggable implements IImpressionsCacheAsync {
27
28
  track(impressions: ImpressionDTO[]): Promise<void> {
28
29
  return this.wrapper.pushItems(
29
30
  this.key,
30
- this._toJSON(impressions)
31
+ impressionsToJSON(impressions, this.metadata)
31
32
  );
32
33
  }
33
34
 
34
- private _toJSON(impressions: ImpressionDTO[]): string[] {
35
- return impressions.map(impression => {
36
- const {
37
- keyName, bucketingKey, feature, treatment, label, time, changeNumber
38
- } = impression;
39
-
40
- return JSON.stringify({
41
- m: this.metadata,
42
- i: {
43
- k: keyName,
44
- b: bucketingKey,
45
- f: feature,
46
- t: treatment,
47
- r: label,
48
- c: changeNumber,
49
- m: time
50
- }
51
- } as StoredImpressionWithMetadata);
52
- });
53
- }
54
-
55
35
  /**
56
36
  * Returns a promise that resolves with the count of stored impressions, or 0 if there was some error.
57
37
  * The promise will never be rejected.
@@ -1,6 +1,6 @@
1
1
  import { ILogger } from '../../logger/types';
2
2
  import { Method, MultiConfigs, MultiMethodExceptions, MultiMethodLatencies } from '../../sync/submitters/types';
3
- import { KeyBuilderSS, parseExceptionField, parseLatencyField, parseMetadata } from '../KeyBuilderSS';
3
+ import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { IPluggableStorageWrapper, ITelemetryCacheAsync } from '../types';
5
5
  import { findLatencyIndex } from '../findLatencyIndex';
6
6
  import { getTelemetryConfigStats } from '../../sync/submitters/telemetrySubmitter';
@@ -8,6 +8,7 @@ import { CONSUMER_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
8
8
  import { isString, isNaNNumber } from '../../utils/lang';
9
9
  import { _Map } from '../../utils/lang/maps';
10
10
  import { MAX_LATENCY_BUCKET_COUNT, newBuckets } from '../inMemory/TelemetryCacheInMemory';
11
+ import { parseLatencyField, parseExceptionField, parseMetadata } from '../utils';
11
12
 
12
13
  export class TelemetryCachePluggable implements ITelemetryCacheAsync {
13
14
 
@@ -75,7 +76,7 @@ export class TelemetryCachePluggable implements ITelemetryCacheAsync {
75
76
  tr: newBuckets(),
76
77
  });
77
78
 
78
- result.get(metadata)![method][bucket] = count;
79
+ result.get(metadata)![method]![bucket] = count;
79
80
  }
80
81
 
81
82
  return Promise.all(latencyKeys.map((latencyKey) => this.wrapper.del(latencyKey))).then(() => result);
@@ -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
  },
@@ -18,6 +18,7 @@ import { ImpressionCountsCachePluggable } from './ImpressionCountsCachePluggable
18
18
  import { UniqueKeysCachePluggable } from './UniqueKeysCachePluggable';
19
19
  import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
20
20
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
21
+ import { metadataBuilder } from '../utils';
21
22
 
22
23
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
23
24
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -61,7 +62,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
61
62
  const prefix = validatePrefix(options.prefix);
62
63
 
63
64
  function PluggableStorageFactory(params: IStorageFactoryParams): IStorageAsync {
64
- const { log, metadata, onReadyCb, mode, eventsQueueSize, impressionsQueueSize, impressionsMode, matchingKey } = params;
65
+ const { onReadyCb, settings, settings: { log, mode, sync: { impressionsMode }, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
66
+ const metadata = metadataBuilder(settings);
65
67
  const keys = new KeyBuilderSS(prefix, metadata);
66
68
  const wrapper = wrapperAdapter(log, options.wrapper);
67
69
 
@@ -82,7 +84,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
82
84
 
83
85
  const uniqueKeysCache = impressionsMode === NONE || isSyncronizer ?
84
86
  isPartialConsumer ?
85
- matchingKey === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
87
+ settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
86
88
  new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
87
89
  undefined;
88
90
 
@@ -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, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, 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,21 +69,21 @@ export interface IPluggableStorageWrapper {
70
69
  /** Integer operations */
71
70
 
72
71
  /**
73
- * Increments the number stored at `key` by `increment` (or 1 if `increment` is not provided), or set it to `increment` (or 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
77
- * @param {number} increment Value to increment by
76
+ * @param {number} increment Value to increment by. Defaults to 1.
78
77
  * @returns {Promise<number>} A promise that resolves with the value of key after the increment. The promise rejects if the operation fails,
79
78
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
80
79
  */
81
80
  incr: (key: string, increment?: number) => Promise<number>
82
81
  /**
83
- * Decrements the number stored at `key` by `decrement` (or 1 if `decrement` is not provided), or set it to minus `decrement` (or minus 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.
84
83
  *
85
84
  * @function decr
86
85
  * @param {string} key Key to decrement
87
- * @param {number} decrement Value to decrement by
86
+ * @param {number} decrement Value to decrement by. Defaults to 1.
88
87
  * @returns {Promise<number>} A promise that resolves with the value of key after the decrement. The promise rejects if the operation fails,
89
88
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
90
89
  */
@@ -285,19 +284,29 @@ export interface ISegmentsCacheAsync extends ISegmentsCacheBase {
285
284
  /** Recorder storages (impressions, events and telemetry) */
286
285
 
287
286
  export interface IImpressionsCacheBase {
288
- // 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.
289
288
  track(data: ImpressionDTO[]): MaybeThenable<void>
290
289
  }
291
290
 
292
291
  export interface IEventsCacheBase {
293
- // 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.
294
293
  track(data: SplitIO.EventData, size?: number): MaybeThenable<boolean>
295
294
  }
296
295
 
297
- /** 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
+ }
298
305
 
299
- // Producer API methods for sync recorder storages, used by submitters in standalone mode to pop data and post it to Split BE.
300
- 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> {
301
310
  // @TODO names are inconsistent with spec
302
311
  /* Checks if cache is empty. Returns true if the cache was just created or cleared */
303
312
  isEmpty(): boolean
@@ -307,23 +316,29 @@ export interface IRecorderCacheProducerSync<T> {
307
316
  pop(toMerge?: T): T
308
317
  }
309
318
 
310
-
311
- export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheProducerSync<ImpressionDTO[]> {
319
+ export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheSync<ImpressionDTO[]> {
312
320
  track(data: ImpressionDTO[]): void
313
321
  /* Registers callback for full queue */
314
322
  setOnFullQueueCb(cb: () => void): void
315
323
  }
316
324
 
317
- export interface IEventsCacheSync extends IEventsCacheBase, IRecorderCacheProducerSync<SplitIO.EventData[]> {
325
+ export interface IEventsCacheSync extends IEventsCacheBase, IRecorderCacheSync<SplitIO.EventData[]> {
318
326
  track(data: SplitIO.EventData, size?: number): boolean
319
327
  /* Registers callback for full queue */
320
328
  setOnFullQueueCb(cb: () => void): void
321
329
  }
322
330
 
323
- /** 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>> { }
324
333
 
325
- // Producer API methods for async recorder storages, used by submitters in producer mode to pop data and post it to Split BE.
326
- 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> {
327
342
  /* returns the number of stored items */
328
343
  count(): Promise<number>
329
344
  /* removes the given number of items from the store. If not provided, it deletes all items */
@@ -332,43 +347,18 @@ export interface IRecorderCacheProducerAsync<T> {
332
347
  popNWithMetadata(count: number): Promise<T>
333
348
  }
334
349
 
335
- export interface IImpressionsCacheAsync extends IImpressionsCacheBase, IRecorderCacheProducerAsync<StoredImpressionWithMetadata[]> {
350
+ export interface IImpressionsCacheAsync extends IImpressionsCacheBase, IRecorderCacheAsync<StoredImpressionWithMetadata[]> {
336
351
  // Consumer API method, used by impressions tracker (in standalone and consumer modes) to push data into.
337
352
  // The result promise can reject.
338
353
  track(data: ImpressionDTO[]): Promise<void>
339
354
  }
340
355
 
341
- export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheProducerAsync<StoredEventWithMetadata[]> {
356
+ export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheAsync<StoredEventWithMetadata[]> {
342
357
  // Consumer API method, used by events tracker (in standalone and consumer modes) to push data into.
343
358
  // The result promise cannot reject.
344
359
  track(data: SplitIO.EventData, size?: number): Promise<boolean>
345
360
  }
346
361
 
347
- /**
348
- * Impression counts cache for impressions dedup in standalone and producer mode.
349
- * Only in memory. Named `ImpressionsCounter` in spec.
350
- */
351
- export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<Record<string, number>> {
352
- // Used by impressions tracker
353
- track(featureName: string, timeFrame: number, amount: number): void
354
-
355
- // Used by impressions count submitter in standalone and producer mode
356
- isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
357
- pop(toMerge?: Record<string, number>): Record<string, number> // pop cache data
358
- }
359
-
360
- export interface IUniqueKeysCacheBase {
361
- // Used by unique Keys tracker
362
- track(key: string, value: string): void
363
-
364
- // Used by unique keys submitter in standalone and producer mode
365
- isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
366
- pop(): UniqueKeysPayloadSs | UniqueKeysPayloadCs // pop cache data
367
- /* Registers callback for full queue */
368
- setOnFullQueueCb(cb: () => void): void,
369
- clear(): void
370
- }
371
-
372
362
  /**
373
363
  * Telemetry storage interface for standalone and partial consumer modes.
374
364
  * Methods are sync because data is stored in memory.
@@ -428,7 +418,7 @@ export interface ITelemetryEvaluationProducerSync {
428
418
 
429
419
  export interface ITelemetryStorageProducerSync extends ITelemetryInitProducerSync, ITelemetryRuntimeProducerSync, ITelemetryEvaluationProducerSync { }
430
420
 
431
- export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITelemetryStorageProducerSync { }
421
+ export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITelemetryStorageProducerSync, IRecorderCacheSync<TelemetryUsageStatsPayload> { }
432
422
 
433
423
  /**
434
424
  * Telemetry storage interface for consumer mode.
@@ -458,7 +448,7 @@ export interface IStorageBase<
458
448
  TSplitsCache extends ISplitsCacheBase,
459
449
  TSegmentsCache extends ISegmentsCacheBase,
460
450
  TImpressionsCache extends IImpressionsCacheBase,
461
- TImpressionsCountCache extends IImpressionCountsCacheSync,
451
+ TImpressionsCountCache extends IImpressionCountsCacheBase,
462
452
  TEventsCache extends IEventsCacheBase,
463
453
  TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
464
454
  TUniqueKeysCache extends IUniqueKeysCacheBase
@@ -481,14 +471,14 @@ export interface IStorageSync extends IStorageBase<
481
471
  IImpressionCountsCacheSync,
482
472
  IEventsCacheSync,
483
473
  ITelemetryCacheSync,
484
- IUniqueKeysCacheBase
474
+ IUniqueKeysCacheSync
485
475
  > { }
486
476
 
487
477
  export interface IStorageAsync extends IStorageBase<
488
478
  ISplitsCacheAsync,
489
479
  ISegmentsCacheAsync,
490
480
  IImpressionsCacheAsync | IImpressionsCacheSync,
491
- IImpressionCountsCacheSync,
481
+ IImpressionCountsCacheBase,
492
482
  IEventsCacheAsync | IEventsCacheSync,
493
483
  ITelemetryCacheAsync | ITelemetryCacheSync,
494
484
  IUniqueKeysCacheBase
@@ -499,21 +489,12 @@ export interface IStorageAsync extends IStorageBase<
499
489
  export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
500
490
 
501
491
  export interface IStorageFactoryParams {
502
- log: ILogger,
503
- impressionsQueueSize?: number,
504
- eventsQueueSize?: number,
505
- optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
506
- mode: SDKMode,
507
- impressionsMode?: string,
508
- // ATM, only used by InLocalStorage
509
- matchingKey?: string, /* undefined on server-side SDKs */
510
- splitFiltersValidation?: ISplitFiltersValidation,
511
-
512
- // This callback is invoked when the storage is ready to be used. Error-first callback style: if an error is passed,
513
- // it means that the storge fail to connect and shouldn't be used.
514
- // 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
+ */
515
497
  onReadyCb: (error?: any) => void,
516
- metadata: IMetadata,
517
498
  }
518
499
 
519
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';
@@ -9,41 +9,6 @@ import { usedKeysMap } from '../../utils/inputValidation/apiKey';
9
9
  import { timer } from '../../utils/timeTracker/timer';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
11
  import { objectAssign } from '../../utils/lang/objectAssign';
12
- import { isStorageSync } from '../../trackers/impressionObserver/utils';
13
-
14
- /**
15
- * Converts data from telemetry cache into /metrics/usage request payload.
16
- */
17
- export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, splits?: ISplitsCacheSync, segments?: ISegmentsCacheSync) {
18
- return {
19
- isEmpty() { return false; }, // There is always data in telemetry cache
20
- clear() { }, // No-op
21
-
22
- // @TODO consider moving inside telemetry cache for code size reduction
23
- pop(): TelemetryUsageStatsPayload {
24
- return {
25
- lS: telemetry.getLastSynchronization(),
26
- mL: telemetry.popLatencies(),
27
- mE: telemetry.popExceptions(),
28
- hE: telemetry.popHttpErrors(),
29
- hL: telemetry.popHttpLatencies(),
30
- tR: telemetry.popTokenRefreshes(),
31
- aR: telemetry.popAuthRejections(),
32
- iQ: telemetry.getImpressionStats(QUEUED),
33
- iDe: telemetry.getImpressionStats(DEDUPED),
34
- iDr: telemetry.getImpressionStats(DROPPED),
35
- spC: splits && splits.getSplitNames().length,
36
- seC: segments && segments.getRegisteredSegments().length,
37
- skC: segments && segments.getKeysCount(),
38
- sL: telemetry.getSessionLength(),
39
- eQ: telemetry.getEventStats(QUEUED),
40
- eD: telemetry.getEventStats(DROPPED),
41
- sE: telemetry.popStreamingEvents(),
42
- t: telemetry.popTags(),
43
- };
44
- }
45
- };
46
- }
47
12
 
48
13
  const OPERATION_MODE_MAP = {
49
14
  [STANDALONE_MODE]: STANDALONE_ENUM,
@@ -131,7 +96,7 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
131
96
  * Submitter that periodically posts telemetry data
132
97
  */
133
98
  export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
134
- const { storage: { splits, segments, telemetry }, platform: { now } } = params;
99
+ const { storage: { telemetry }, platform: { now } } = params;
135
100
  if (!telemetry || !now) return; // No submitter created if telemetry cache is not defined
136
101
 
137
102
  const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, readiness, sdkReadinessManager } = params;
@@ -140,8 +105,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
140
105
  const submitter = firstPushWindowDecorator(
141
106
  submitterFactory(
142
107
  log, splitApi.postMetricsUsage,
143
- // @TODO cannot provide splits and segments cache if they are async, because `submitterFactory` expects a sync storage source
144
- isStorageSync(params.settings) ? telemetryCacheStatsAdapter(telemetry, splits, segments) : telemetryCacheStatsAdapter(telemetry),
108
+ telemetry,
145
109
  telemetryRefreshRate, 'telemetry stats', undefined, 0, true
146
110
  ),
147
111
  telemetryRefreshRate