@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.
- package/cjs/listeners/browser.js +9 -11
- package/cjs/sdkFactory/index.js +7 -24
- package/cjs/storages/KeyBuilderSS.js +4 -43
- package/cjs/storages/inLocalStorage/index.js +14 -10
- package/cjs/storages/inMemory/InMemoryStorage.js +10 -7
- package/cjs/storages/inMemory/InMemoryStorageCS.js +10 -7
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +57 -34
- package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
- package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
- package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
- package/cjs/storages/inRedis/index.js +4 -2
- package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
- package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
- package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
- package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
- package/cjs/storages/pluggable/index.js +4 -2
- package/cjs/storages/utils.js +73 -0
- package/cjs/sync/submitters/submitterManager.js +1 -1
- package/cjs/sync/submitters/telemetrySubmitter.js +4 -40
- package/cjs/trackers/impressionObserver/utils.js +1 -17
- package/cjs/trackers/uniqueKeysTracker.js +1 -1
- package/cjs/utils/settingsValidation/index.js +7 -1
- package/esm/listeners/browser.js +9 -11
- package/esm/sdkFactory/index.js +7 -24
- package/esm/storages/KeyBuilderSS.js +1 -37
- package/esm/storages/inLocalStorage/index.js +15 -11
- package/esm/storages/inMemory/InMemoryStorage.js +10 -7
- package/esm/storages/inMemory/InMemoryStorageCS.js +10 -7
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +58 -35
- package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +3 -3
- package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
- package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
- package/esm/storages/inRedis/index.js +4 -2
- package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +3 -3
- package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
- package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
- package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
- package/esm/storages/pluggable/index.js +4 -2
- package/esm/storages/utils.js +65 -0
- package/esm/sync/submitters/submitterManager.js +1 -1
- package/esm/sync/submitters/telemetrySubmitter.js +4 -39
- package/esm/trackers/impressionObserver/utils.js +1 -15
- package/esm/trackers/uniqueKeysTracker.js +1 -1
- package/esm/utils/settingsValidation/index.js +7 -1
- package/package.json +2 -1
- package/src/listeners/browser.ts +9 -13
- package/src/sdkClient/sdkClient.ts +1 -1
- package/src/sdkFactory/index.ts +7 -29
- package/src/sdkFactory/types.ts +2 -2
- package/src/services/splitApi.ts +2 -2
- package/src/storages/KeyBuilderSS.ts +2 -44
- package/src/storages/inLocalStorage/index.ts +16 -11
- package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
- package/src/storages/inMemory/InMemoryStorage.ts +11 -7
- package/src/storages/inMemory/InMemoryStorageCS.ts +11 -7
- package/src/storages/inMemory/TelemetryCacheInMemory.ts +66 -33
- package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
- package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
- package/src/storages/inRedis/index.ts +4 -1
- package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +3 -3
- package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
- package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
- package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
- package/src/storages/pluggable/index.ts +4 -2
- package/src/storages/types.ts +45 -64
- package/src/storages/utils.ts +78 -0
- package/src/sync/submitters/submitter.ts +2 -2
- package/src/sync/submitters/submitterManager.ts +1 -1
- package/src/sync/submitters/telemetrySubmitter.ts +5 -41
- package/src/sync/submitters/types.ts +7 -5
- package/src/trackers/impressionObserver/utils.ts +1 -16
- package/src/trackers/impressionsTracker.ts +2 -2
- package/src/trackers/strategy/strategyDebug.ts +4 -4
- package/src/trackers/strategy/strategyNone.ts +9 -9
- package/src/trackers/strategy/strategyOptimized.ts +9 -9
- package/src/trackers/uniqueKeysTracker.ts +6 -6
- package/src/utils/redis/RedisMock.ts +5 -5
- package/src/utils/settingsValidation/index.ts +5 -1
- package/types/storages/KeyBuilderSS.d.ts +1 -3
- package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +19 -8
- package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
- package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
- package/types/storages/types.d.ts +35 -45
- package/types/storages/utils.d.ts +8 -0
- package/types/sync/submitters/submitter.d.ts +2 -2
- package/types/sync/submitters/telemetrySubmitter.d.ts +2 -10
- package/types/sync/submitters/types.d.ts +8 -6
- package/types/trackers/impressionObserver/utils.d.ts +0 -8
- package/types/trackers/strategy/strategyNone.d.ts +2 -2
- package/types/trackers/strategy/strategyOptimized.d.ts +2 -2
- package/types/trackers/uniqueKeysTracker.d.ts +1 -1
- package/cjs/storages/metadataBuilder.js +0 -12
- package/esm/storages/metadataBuilder.js +0 -8
- 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.
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
81
|
+
pf.push({
|
|
82
82
|
f: keyFeatureNameAndTime[1],
|
|
83
83
|
m: timeFrame,
|
|
84
84
|
rc: rawCount,
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
return
|
|
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.
|
|
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
|
|
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]) +
|
|
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] = '
|
|
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]) -
|
|
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] = '-
|
|
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 {
|
|
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
|
-
|
|
87
|
+
settings.core.key === undefined ? new UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS() :
|
|
86
88
|
new UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
|
|
87
89
|
undefined;
|
|
88
90
|
|
package/src/storages/types.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { MaybeThenable,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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,
|
|
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
|
-
|
|
331
|
+
/* Named `ImpressionsCounter` in spec */
|
|
332
|
+
export interface IImpressionCountsCacheSync extends IImpressionCountsCacheBase, IRecorderCacheSync<Record<string, number>> { }
|
|
324
333
|
|
|
325
|
-
|
|
326
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
474
|
+
IUniqueKeysCacheSync
|
|
485
475
|
> { }
|
|
486
476
|
|
|
487
477
|
export interface IStorageAsync extends IStorageBase<
|
|
488
478
|
ISplitsCacheAsync,
|
|
489
479
|
ISegmentsCacheAsync,
|
|
490
480
|
IImpressionsCacheAsync | IImpressionsCacheSync,
|
|
491
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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 {
|
|
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:
|
|
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.
|
|
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 {
|
|
1
|
+
import { ITelemetryCacheSync } from '../../storages/types';
|
|
2
2
|
import { submitterFactory, firstPushWindowDecorator } from './submitter';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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: {
|
|
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
|
-
|
|
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
|