@splitsoftware/splitio-commons 1.6.2-rc.13 → 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 (50) hide show
  1. package/cjs/storages/KeyBuilderSS.js +4 -43
  2. package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
  3. package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  4. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
  5. package/cjs/storages/inRedis/index.js +2 -2
  6. package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
  7. package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  8. package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
  9. package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
  10. package/cjs/storages/pluggable/index.js +2 -2
  11. package/cjs/storages/utils.js +73 -0
  12. package/esm/storages/KeyBuilderSS.js +1 -37
  13. package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
  14. package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
  15. package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
  16. package/esm/storages/inRedis/index.js +1 -1
  17. package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
  18. package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
  19. package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
  20. package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
  21. package/esm/storages/pluggable/index.js +1 -1
  22. package/esm/storages/utils.js +65 -0
  23. package/package.json +1 -1
  24. package/src/services/splitApi.ts +2 -2
  25. package/src/storages/KeyBuilderSS.ts +2 -44
  26. package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
  27. package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +3 -3
  28. package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
  29. package/src/storages/inRedis/TelemetryCacheInRedis.ts +2 -1
  30. package/src/storages/inRedis/index.ts +1 -1
  31. package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +3 -3
  32. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
  33. package/src/storages/pluggable/TelemetryCachePluggable.ts +2 -1
  34. package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
  35. package/src/storages/pluggable/index.ts +1 -1
  36. package/src/storages/types.ts +4 -4
  37. package/src/storages/utils.ts +78 -0
  38. package/src/sync/submitters/types.ts +2 -0
  39. package/src/trackers/impressionsTracker.ts +2 -2
  40. package/src/trackers/strategy/strategyDebug.ts +4 -4
  41. package/src/utils/redis/RedisMock.ts +5 -5
  42. package/types/storages/KeyBuilderSS.d.ts +1 -3
  43. package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
  44. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
  45. package/types/storages/types.d.ts +4 -4
  46. package/types/storages/utils.d.ts +8 -0
  47. package/types/sync/submitters/types.d.ts +2 -0
  48. package/cjs/storages/metadataBuilder.js +0 -12
  49. package/esm/storages/metadataBuilder.js +0 -8
  50. package/src/storages/metadataBuilder.ts +0 -11
@@ -34,29 +34,31 @@ export function inMemoryWrapperFactory(connDelay) {
34
34
  getKeysByPrefix: function (prefix) {
35
35
  return Promise.resolve(Object.keys(_cache).filter(function (key) { return startsWith(key, prefix); }));
36
36
  },
37
- incr: function (key) {
37
+ incr: function (key, increment) {
38
+ if (increment === void 0) { increment = 1; }
38
39
  if (key in _cache) {
39
- var count = toNumber(_cache[key]) + 1;
40
+ var count = toNumber(_cache[key]) + increment;
40
41
  if (isNaN(count))
41
42
  return Promise.reject('Given key is not a number');
42
43
  _cache[key] = count + '';
43
44
  return Promise.resolve(count);
44
45
  }
45
46
  else {
46
- _cache[key] = '1';
47
+ _cache[key] = '' + increment;
47
48
  return Promise.resolve(1);
48
49
  }
49
50
  },
50
- decr: function (key) {
51
+ decr: function (key, decrement) {
52
+ if (decrement === void 0) { decrement = 1; }
51
53
  if (key in _cache) {
52
- var count = toNumber(_cache[key]) - 1;
54
+ var count = toNumber(_cache[key]) - decrement;
53
55
  if (isNaN(count))
54
56
  return Promise.reject('Given key is not a number');
55
57
  _cache[key] = count + '';
56
58
  return Promise.resolve(count);
57
59
  }
58
60
  else {
59
- _cache[key] = '-1';
61
+ _cache[key] = '-' + decrement;
60
62
  return Promise.resolve(-1);
61
63
  }
62
64
  },
@@ -17,7 +17,7 @@ import { ImpressionCountsCachePluggable } from './ImpressionCountsCachePluggable
17
17
  import { UniqueKeysCachePluggable } from './UniqueKeysCachePluggable';
18
18
  import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
19
19
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
20
- import { metadataBuilder } from '../metadataBuilder';
20
+ import { metadataBuilder } from '../utils';
21
21
  var NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
22
22
  var NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
23
23
  /**
@@ -0,0 +1,65 @@
1
+ // Shared utils for Redis and Pluggable storage
2
+ import { UNKNOWN } from '../utils/constants';
3
+ import { MAX_LATENCY_BUCKET_COUNT } from './inMemory/TelemetryCacheInMemory';
4
+ import { METHOD_NAMES } from './KeyBuilderSS';
5
+ export function metadataBuilder(settings) {
6
+ return {
7
+ s: settings.version,
8
+ i: settings.runtime.ip || UNKNOWN,
9
+ n: settings.runtime.hostname || UNKNOWN,
10
+ };
11
+ }
12
+ // Converts impressions to be stored in Redis or pluggable storage.
13
+ export function impressionsToJSON(impressions, metadata) {
14
+ return impressions.map(function (impression) {
15
+ var impressionWithMetadata = {
16
+ m: metadata,
17
+ i: {
18
+ k: impression.keyName,
19
+ b: impression.bucketingKey,
20
+ f: impression.feature,
21
+ t: impression.treatment,
22
+ r: impression.label,
23
+ c: impression.changeNumber,
24
+ m: impression.time,
25
+ pt: impression.pt,
26
+ }
27
+ };
28
+ return JSON.stringify(impressionWithMetadata);
29
+ });
30
+ }
31
+ // Utilities used by TelemetryCacheInRedis and TelemetryCachePluggable
32
+ var REVERSE_METHOD_NAMES = Object.keys(METHOD_NAMES).reduce(function (acc, key) {
33
+ acc[METHOD_NAMES[key]] = key;
34
+ return acc;
35
+ }, {});
36
+ export function parseMetadata(field) {
37
+ var parts = field.split('/');
38
+ if (parts.length !== 3)
39
+ return "invalid subsection count. Expected 3, got: " + parts.length;
40
+ var s = parts[0] /* metadata.s */, n = parts[1] /* metadata.n */, i = parts[2] /* metadata.i */;
41
+ return [JSON.stringify({ s: s, n: n, i: i })];
42
+ }
43
+ export function parseExceptionField(field) {
44
+ var parts = field.split('/');
45
+ if (parts.length !== 4)
46
+ return "invalid subsection count. Expected 4, got: " + parts.length;
47
+ var s = parts[0] /* metadata.s */, n = parts[1] /* metadata.n */, i = parts[2] /* metadata.i */, m = parts[3];
48
+ var method = REVERSE_METHOD_NAMES[m];
49
+ if (!method)
50
+ return "unknown method '" + m + "'";
51
+ return [JSON.stringify({ s: s, n: n, i: i }), method];
52
+ }
53
+ export function parseLatencyField(field) {
54
+ var parts = field.split('/');
55
+ if (parts.length !== 5)
56
+ return "invalid subsection count. Expected 5, got: " + parts.length;
57
+ var s = parts[0] /* metadata.s */, n = parts[1] /* metadata.n */, i = parts[2] /* metadata.i */, m = parts[3], b = parts[4];
58
+ var method = REVERSE_METHOD_NAMES[m];
59
+ if (!method)
60
+ return "unknown method '" + m + "'";
61
+ var bucket = parseInt(b);
62
+ if (isNaN(bucket) || bucket >= MAX_LATENCY_BUCKET_COUNT)
63
+ return "invalid bucket. Expected a number between 0 and " + (MAX_LATENCY_BUCKET_COUNT - 1) + ", got: " + b;
64
+ return [JSON.stringify({ s: s, n: n, i: i }), method, bucket];
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.6.2-rc.13",
3
+ "version": "1.6.2-rc.14",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -106,7 +106,7 @@ 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
-
109
+
110
110
  /**
111
111
  * Post unique keys for client side.
112
112
  *
@@ -117,7 +117,7 @@ export function splitApiFactory(
117
117
  const url = `${urls.telemetry}/v1/keys/cs`;
118
118
  return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY));
119
119
  },
120
-
120
+
121
121
  /**
122
122
  * Post unique keys for server side.
123
123
  *
@@ -1,9 +1,8 @@
1
1
  import { KeyBuilder } from './KeyBuilder';
2
2
  import { IMetadata } from '../dtos/types';
3
3
  import { Method } from '../sync/submitters/types';
4
- import { MAX_LATENCY_BUCKET_COUNT } from './inMemory/TelemetryCacheInMemory';
5
4
 
6
- const METHOD_NAMES: Record<Method, string> = {
5
+ export const METHOD_NAMES: Record<Method, string> = {
7
6
  t: 'treatment',
8
7
  ts: 'treatments',
9
8
  tc: 'treatmentWithConfig',
@@ -37,7 +36,7 @@ export class KeyBuilderSS extends KeyBuilder {
37
36
  buildImpressionsCountKey() {
38
37
  return `${this.prefix}.impressions.count`;
39
38
  }
40
-
39
+
41
40
  buildUniqueKeysKey() {
42
41
  return `${this.prefix}.uniquekeys`;
43
42
  }
@@ -65,44 +64,3 @@ export class KeyBuilderSS extends KeyBuilder {
65
64
  }
66
65
 
67
66
  }
68
-
69
- // Used by consumer methods of TelemetryCacheInRedis and TelemetryCachePluggable
70
-
71
- const REVERSE_METHOD_NAMES = Object.keys(METHOD_NAMES).reduce((acc, key) => {
72
- acc[METHOD_NAMES[key as Method]] = key as Method;
73
- return acc;
74
- }, {} as Record<string, Method>);
75
-
76
-
77
- export function parseMetadata(field: string): [metadata: string] | string {
78
- const parts = field.split('/');
79
- if (parts.length !== 3) return `invalid subsection count. Expected 3, got: ${parts.length}`;
80
-
81
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */] = parts;
82
- return [JSON.stringify({ s, n, i })];
83
- }
84
-
85
- export function parseExceptionField(field: string): [metadata: string, method: Method] | string {
86
- const parts = field.split('/');
87
- if (parts.length !== 4) return `invalid subsection count. Expected 4, got: ${parts.length}`;
88
-
89
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m] = parts;
90
- const method = REVERSE_METHOD_NAMES[m];
91
- if (!method) return `unknown method '${m}'`;
92
-
93
- return [JSON.stringify({ s, n, i }), method];
94
- }
95
-
96
- export function parseLatencyField(field: string): [metadata: string, method: Method, bucket: number] | string {
97
- const parts = field.split('/');
98
- if (parts.length !== 5) return `invalid subsection count. Expected 5, got: ${parts.length}`;
99
-
100
- const [s /* metadata.s */, n /* metadata.n */, i /* metadata.i */, m, b] = parts;
101
- const method = REVERSE_METHOD_NAMES[m];
102
- if (!method) return `unknown method '${m}'`;
103
-
104
- const bucket = parseInt(b);
105
- if (isNaN(bucket) || bucket >= MAX_LATENCY_BUCKET_COUNT) return `invalid bucket. Expected a number between 0 and ${MAX_LATENCY_BUCKET_COUNT - 1}, got: ${b}`;
106
-
107
- return [JSON.stringify({ s, n, i }), method, bucket];
108
- }
@@ -7,10 +7,10 @@ export class AttributesCacheInMemory {
7
7
 
8
8
  /**
9
9
  * Create or update the value for the given attribute
10
- *
10
+ *
11
11
  * @param {string} attributeName attribute name
12
12
  * @param {Object} attributeValue attribute value
13
- * @returns {boolean} the attribute was stored
13
+ * @returns {boolean} the attribute was stored
14
14
  */
15
15
  setAttribute(attributeName: string, attributeValue: Object): boolean {
16
16
  this.attributesCache[attributeName] = attributeValue;
@@ -19,7 +19,7 @@ export class AttributesCacheInMemory {
19
19
 
20
20
  /**
21
21
  * Retrieves the value of a given attribute
22
- *
22
+ *
23
23
  * @param {string} attributeName attribute name
24
24
  * @returns {Object?} stored attribute value
25
25
  */
@@ -29,7 +29,7 @@ export class AttributesCacheInMemory {
29
29
 
30
30
  /**
31
31
  * Create or update all the given attributes
32
- *
32
+ *
33
33
  * @param {[string, Object]} attributes attributes to create or update
34
34
  * @returns {boolean} attributes were stored
35
35
  */
@@ -40,7 +40,7 @@ export class AttributesCacheInMemory {
40
40
 
41
41
  /**
42
42
  * Retrieve the full attributes map
43
- *
43
+ *
44
44
  * @returns {Map<string, Object>} stored attributes
45
45
  */
46
46
  getAll(): Record<string, Object> {
@@ -49,7 +49,7 @@ export class AttributesCacheInMemory {
49
49
 
50
50
  /**
51
51
  * Removes a given attribute from the map
52
- *
52
+ *
53
53
  * @param {string} attributeName attribute to remove
54
54
  * @returns {boolean} attribute removed
55
55
  */
@@ -63,7 +63,7 @@ export class AttributesCacheInMemory {
63
63
 
64
64
  /**
65
65
  * Clears all attributes stored in the SDK
66
- *
66
+ *
67
67
  */
68
68
  clear() {
69
69
  this.attributesCache = {};
@@ -61,7 +61,7 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
61
61
 
62
62
  this.redis.del(this.key).catch(() => { /* no-op */ });
63
63
 
64
- const impressionsCount: ImpressionCountsPayload = { pf: [] };
64
+ const pf: ImpressionCountsPayload['pf'] = [];
65
65
 
66
66
  forOwn(counts, (count, key) => {
67
67
  const nameAndTime = key.split('::');
@@ -82,14 +82,14 @@ export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory
82
82
  return;
83
83
  }
84
84
 
85
- impressionsCount.pf.push({
85
+ pf.push({
86
86
  f: nameAndTime[0],
87
87
  m: timeFrame,
88
88
  rc: rawCount,
89
89
  });
90
90
  });
91
91
 
92
- return impressionsCount;
92
+ return { pf };
93
93
  });
94
94
  }
95
95
  }
@@ -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
 
@@ -10,7 +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 '../metadataBuilder';
13
+ import { metadataBuilder } from '../utils';
14
14
 
15
15
  export interface InRedisStorageOptions {
16
16
  prefix?: string
@@ -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
 
@@ -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,7 +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 '../metadataBuilder';
21
+ import { metadataBuilder } from '../utils';
22
22
 
23
23
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
24
24
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -69,21 +69,21 @@ export interface IPluggableStorageWrapper {
69
69
  /** Integer operations */
70
70
 
71
71
  /**
72
- * 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.
73
73
  *
74
74
  * @function incr
75
75
  * @param {string} key Key to increment
76
- * @param {number} increment Value to increment by
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
80
  incr: (key: string, increment?: number) => Promise<number>
81
81
  /**
82
- * 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.
83
83
  *
84
84
  * @function decr
85
85
  * @param {string} key Key to decrement
86
- * @param {number} decrement Value to decrement by
86
+ * @param {number} decrement Value to decrement by. Defaults to 1.
87
87
  * @returns {Promise<number>} A promise that resolves with the value of key after the decrement. The promise rejects if the operation fails,
88
88
  * for example, if there is a connection error or the key contains a string that can not be represented as integer.
89
89
  */
@@ -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
+ }
@@ -76,6 +76,8 @@ export type StoredImpressionWithMetadata = {
76
76
  c: number,
77
77
  /** time */
78
78
  m: number
79
+ /** previous time */
80
+ pt?: number
79
81
  }
80
82
  }
81
83
 
@@ -31,9 +31,9 @@ export function impressionsTrackerFactory(
31
31
 
32
32
  const impressionsCount = impressions.length;
33
33
  const { impressionsToStore, impressionsToListener, deduped } = strategy.process(impressions);
34
-
34
+
35
35
  const impressionsToListenerCount = impressionsToListener.length;
36
-
36
+
37
37
  if ( impressionsToStore.length>0 ){
38
38
  const res = impressionsCache.track(impressionsToStore);
39
39
 
@@ -4,14 +4,14 @@ import { IStrategy } from '../types';
4
4
 
5
5
  /**
6
6
  * Debug strategy for impressions tracker. Wraps impressions to store and adds previousTime if it corresponds
7
- *
7
+ *
8
8
  * @param impressionsObserver impression observer. Previous time (pt property) is included in impression instances
9
9
  * @returns IStrategyResult
10
10
  */
11
11
  export function strategyDebugFactory(
12
12
  impressionsObserver: IImpressionObserver
13
13
  ): IStrategy {
14
-
14
+
15
15
  return {
16
16
  process(impressions: ImpressionDTO[]) {
17
17
  impressions.forEach((impression) => {
@@ -19,8 +19,8 @@ export function strategyDebugFactory(
19
19
  impression.pt = impressionsObserver.testAndSet(impression);
20
20
  });
21
21
  return {
22
- impressionsToStore: impressions,
23
- impressionsToListener: impressions,
22
+ impressionsToStore: impressions,
23
+ impressionsToListener: impressions,
24
24
  deduped: 0
25
25
  };
26
26
  }