@splitsoftware/splitio-commons 1.6.2-rc.9 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -1
- package/cjs/consent/sdkUserConsent.js +2 -2
- package/cjs/listeners/browser.js +11 -12
- package/cjs/logger/constants.js +2 -1
- package/cjs/sdkClient/sdkClient.js +3 -1
- package/cjs/sdkFactory/index.js +26 -26
- package/cjs/services/splitApi.js +20 -0
- package/cjs/storages/AbstractSplitsCacheAsync.js +1 -1
- package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
- package/cjs/storages/KeyBuilderSS.js +10 -43
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
- package/cjs/storages/inLocalStorage/index.js +17 -9
- package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
- package/cjs/storages/inMemory/InMemoryStorage.js +13 -6
- package/cjs/storages/inMemory/InMemoryStorageCS.js +13 -6
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +60 -35
- package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +72 -0
- package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +76 -0
- package/cjs/storages/inRedis/ImpressionCountsCacheInRedis.js +85 -0
- package/cjs/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
- package/cjs/storages/inRedis/TelemetryCacheInRedis.js +4 -4
- package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +71 -0
- package/cjs/storages/inRedis/constants.js +4 -1
- package/cjs/storages/inRedis/index.js +20 -3
- package/cjs/storages/pluggable/ImpressionCountsCachePluggable.js +81 -0
- package/cjs/storages/pluggable/ImpressionsCachePluggable.js +2 -19
- package/cjs/storages/pluggable/TelemetryCachePluggable.js +4 -4
- package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +61 -0
- package/cjs/storages/pluggable/inMemoryWrapper.js +8 -6
- package/cjs/storages/pluggable/index.js +38 -9
- package/cjs/storages/utils.js +73 -0
- package/cjs/sync/submitters/submitterManager.js +3 -0
- package/cjs/sync/submitters/telemetrySubmitter.js +5 -40
- package/cjs/sync/submitters/uniqueKeysSubmitter.js +27 -0
- package/cjs/trackers/impressionObserver/utils.js +1 -17
- package/cjs/trackers/impressionsTracker.js +22 -41
- package/cjs/trackers/strategy/strategyDebug.js +25 -0
- package/cjs/trackers/strategy/strategyNone.js +29 -0
- package/cjs/trackers/strategy/strategyOptimized.js +35 -0
- package/cjs/trackers/uniqueKeysTracker.js +38 -0
- package/cjs/utils/constants/index.js +4 -2
- package/cjs/utils/redis/RedisMock.js +31 -0
- package/cjs/utils/settingsValidation/impressionsMode.js +2 -2
- package/cjs/utils/settingsValidation/index.js +7 -1
- package/esm/consent/sdkUserConsent.js +2 -2
- package/esm/listeners/browser.js +12 -13
- package/esm/logger/constants.js +1 -0
- package/esm/sdkClient/sdkClient.js +3 -1
- package/esm/sdkFactory/index.js +26 -26
- package/esm/services/splitApi.js +20 -0
- package/esm/storages/AbstractSplitsCacheAsync.js +1 -1
- package/esm/storages/AbstractSplitsCacheSync.js +1 -1
- package/esm/storages/KeyBuilderSS.js +7 -37
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -1
- package/esm/storages/inLocalStorage/index.js +18 -10
- package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +12 -1
- package/esm/storages/inMemory/InMemoryStorage.js +14 -7
- package/esm/storages/inMemory/InMemoryStorageCS.js +14 -7
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +61 -36
- package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +68 -0
- package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +73 -0
- package/esm/storages/inRedis/ImpressionCountsCacheInRedis.js +82 -0
- package/esm/storages/inRedis/ImpressionsCacheInRedis.js +2 -19
- package/esm/storages/inRedis/TelemetryCacheInRedis.js +1 -1
- package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +68 -0
- package/esm/storages/inRedis/constants.js +3 -0
- package/esm/storages/inRedis/index.js +21 -4
- package/esm/storages/pluggable/ImpressionCountsCachePluggable.js +78 -0
- package/esm/storages/pluggable/ImpressionsCachePluggable.js +2 -19
- package/esm/storages/pluggable/TelemetryCachePluggable.js +1 -1
- package/esm/storages/pluggable/UniqueKeysCachePluggable.js +58 -0
- package/esm/storages/pluggable/inMemoryWrapper.js +8 -6
- package/esm/storages/pluggable/index.js +39 -10
- package/esm/storages/utils.js +65 -0
- package/esm/sync/submitters/submitterManager.js +3 -0
- package/esm/sync/submitters/telemetrySubmitter.js +5 -39
- package/esm/sync/submitters/uniqueKeysSubmitter.js +23 -0
- package/esm/trackers/impressionObserver/utils.js +1 -15
- package/esm/trackers/impressionsTracker.js +22 -41
- package/esm/trackers/strategy/strategyDebug.js +21 -0
- package/esm/trackers/strategy/strategyNone.js +25 -0
- package/esm/trackers/strategy/strategyOptimized.js +31 -0
- package/esm/trackers/uniqueKeysTracker.js +34 -0
- package/esm/utils/constants/index.js +2 -0
- package/esm/utils/redis/RedisMock.js +28 -0
- package/esm/utils/settingsValidation/impressionsMode.js +3 -3
- package/esm/utils/settingsValidation/index.js +7 -1
- package/package.json +1 -2
- package/src/consent/sdkUserConsent.ts +2 -2
- package/src/listeners/browser.ts +12 -15
- package/src/logger/constants.ts +1 -0
- package/src/sdkClient/sdkClient.ts +3 -1
- package/src/sdkFactory/index.ts +29 -31
- package/src/sdkFactory/types.ts +7 -4
- package/src/services/splitApi.ts +22 -0
- package/src/services/types.ts +6 -0
- package/src/storages/AbstractSplitsCacheAsync.ts +1 -1
- package/src/storages/AbstractSplitsCacheSync.ts +1 -1
- package/src/storages/KeyBuilderSS.ts +9 -43
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -1
- package/src/storages/inLocalStorage/index.ts +18 -10
- package/src/storages/inMemory/AttributesCacheInMemory.ts +7 -7
- package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +16 -1
- package/src/storages/inMemory/InMemoryStorage.ts +14 -7
- package/src/storages/inMemory/InMemoryStorageCS.ts +14 -7
- package/src/storages/inMemory/TelemetryCacheInMemory.ts +69 -34
- package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +80 -0
- package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +86 -0
- package/src/storages/inRedis/ImpressionCountsCacheInRedis.ts +95 -0
- package/src/storages/inRedis/ImpressionsCacheInRedis.ts +2 -22
- package/src/storages/inRedis/TelemetryCacheInRedis.ts +3 -2
- package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +77 -0
- package/src/storages/inRedis/constants.ts +3 -0
- package/src/storages/inRedis/index.ts +18 -5
- package/src/storages/pluggable/ImpressionCountsCachePluggable.ts +92 -0
- package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -23
- package/src/storages/pluggable/TelemetryCachePluggable.ts +3 -2
- package/src/storages/pluggable/UniqueKeysCachePluggable.ts +67 -0
- package/src/storages/pluggable/inMemoryWrapper.ts +6 -6
- package/src/storages/pluggable/index.ts +41 -9
- package/src/storages/types.ts +56 -55
- package/src/storages/utils.ts +78 -0
- package/src/sync/submitters/submitter.ts +2 -2
- package/src/sync/submitters/submitterManager.ts +2 -0
- package/src/sync/submitters/telemetrySubmitter.ts +8 -43
- package/src/sync/submitters/types.ts +29 -27
- package/src/sync/submitters/uniqueKeysSubmitter.ts +36 -0
- package/src/trackers/impressionObserver/utils.ts +1 -16
- package/src/trackers/impressionsTracker.ts +25 -46
- package/src/trackers/strategy/strategyDebug.ts +28 -0
- package/src/trackers/strategy/strategyNone.ts +34 -0
- package/src/trackers/strategy/strategyOptimized.ts +42 -0
- package/src/trackers/types.ts +28 -0
- package/src/trackers/uniqueKeysTracker.ts +48 -0
- package/src/types.ts +1 -1
- package/src/utils/constants/index.ts +2 -0
- package/src/utils/redis/RedisMock.ts +33 -0
- package/src/utils/settingsValidation/impressionsMode.ts +3 -3
- package/src/utils/settingsValidation/index.ts +5 -1
- package/types/logger/constants.d.ts +1 -0
- package/types/sdkFactory/types.d.ts +4 -2
- package/types/services/types.d.ts +4 -0
- package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
- package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
- package/types/storages/KeyBuilderSS.d.ts +3 -3
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -1
- package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +5 -1
- package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +20 -9
- package/types/storages/inMemory/uniqueKeysCacheInMemory.d.ts +35 -0
- package/types/storages/inMemory/uniqueKeysCacheInMemoryCS.d.ts +35 -0
- package/types/storages/inRedis/ImpressionCountsCacheInRedis.d.ts +16 -0
- package/types/storages/inRedis/ImpressionsCacheInRedis.d.ts +0 -1
- package/types/storages/inRedis/constants.d.ts +3 -0
- package/types/storages/inRedis/uniqueKeysCacheInRedis.d.ts +21 -0
- package/types/storages/pluggable/ImpressionCountsCachePluggable.d.ts +16 -0
- package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +1 -2
- package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +20 -0
- package/types/storages/types.d.ts +39 -38
- 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 +27 -7
- package/types/sync/submitters/uniqueKeysSubmitter.d.ts +5 -0
- package/types/trackers/impressionObserver/utils.d.ts +0 -8
- package/types/trackers/impressionsTracker.d.ts +4 -6
- package/types/trackers/strategy/strategyDebug.d.ts +9 -0
- package/types/trackers/strategy/strategyNone.d.ts +10 -0
- package/types/trackers/strategy/strategyOptimized.d.ts +11 -0
- package/types/trackers/types.d.ts +23 -0
- package/types/trackers/uniqueKeysTracker.d.ts +13 -0
- package/types/types.d.ts +1 -1
- package/types/utils/constants/index.d.ts +2 -0
- package/types/utils/redis/RedisMock.d.ts +4 -0
- package/cjs/storages/metadataBuilder.js +0 -12
- package/esm/storages/metadataBuilder.js +0 -8
- package/src/storages/metadataBuilder.ts +0 -11
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { IUniqueKeysCacheBase } from '../types';
|
|
2
|
+
import { ISet, setToArray, _Set } from '../../utils/lang/sets';
|
|
3
|
+
import { UniqueKeysPayloadCs } from '../../sync/submitters/types';
|
|
4
|
+
import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
|
|
5
|
+
|
|
6
|
+
export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
7
|
+
|
|
8
|
+
private onFullQueue?: () => void;
|
|
9
|
+
private readonly maxStorage: number;
|
|
10
|
+
private uniqueTrackerSize = 0;
|
|
11
|
+
private uniqueKeysTracker: { [userKey: string]: ISet<string> };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
|
|
16
|
+
* Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
|
|
17
|
+
*/
|
|
18
|
+
constructor(uniqueKeysQueueSize = DEFAULT_CACHE_SIZE) {
|
|
19
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
20
|
+
this.uniqueKeysTracker = {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setOnFullQueueCb(cb: () => void) {
|
|
24
|
+
this.onFullQueue = cb;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Store unique keys per feature.
|
|
29
|
+
*/
|
|
30
|
+
track(userKey: string, featureName: string) {
|
|
31
|
+
|
|
32
|
+
if (!this.uniqueKeysTracker[userKey]) this.uniqueKeysTracker[userKey] = new _Set();
|
|
33
|
+
const tracker = this.uniqueKeysTracker[userKey];
|
|
34
|
+
if (!tracker.has(featureName)) {
|
|
35
|
+
tracker.add(featureName);
|
|
36
|
+
this.uniqueTrackerSize++;
|
|
37
|
+
}
|
|
38
|
+
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
39
|
+
this.uniqueTrackerSize = 0;
|
|
40
|
+
this.onFullQueue();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Clear the data stored on the cache.
|
|
46
|
+
*/
|
|
47
|
+
clear() {
|
|
48
|
+
this.uniqueKeysTracker = {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Pop the collected data, used as payload for posting.
|
|
53
|
+
*/
|
|
54
|
+
pop() {
|
|
55
|
+
const data = this.uniqueKeysTracker;
|
|
56
|
+
this.uniqueKeysTracker = {};
|
|
57
|
+
return this.fromUniqueKeysCollector(data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if the cache is empty.
|
|
62
|
+
*/
|
|
63
|
+
isEmpty() {
|
|
64
|
+
return Object.keys(this.uniqueKeysTracker).length === 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Converts `uniqueKeys` data from cache into request payload.
|
|
69
|
+
*/
|
|
70
|
+
private fromUniqueKeysCollector(uniqueKeys: { [userKey: string]: ISet<string> }): UniqueKeysPayloadCs {
|
|
71
|
+
const payload = [];
|
|
72
|
+
const userKeys = Object.keys(uniqueKeys);
|
|
73
|
+
for (let k = 0; k < userKeys.length; k++) {
|
|
74
|
+
const userKey = userKeys[k];
|
|
75
|
+
const featureNames = setToArray(uniqueKeys[userKey]);
|
|
76
|
+
const uniqueKeysPayload = {
|
|
77
|
+
k: userKey,
|
|
78
|
+
fs: featureNames
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
payload.push(uniqueKeysPayload);
|
|
82
|
+
}
|
|
83
|
+
return { keys: payload };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { ILogger } from '../../logger/types';
|
|
3
|
+
import { ImpressionCountsPayload } from '../../sync/submitters/types';
|
|
4
|
+
import { forOwn } from '../../utils/lang';
|
|
5
|
+
import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
|
|
6
|
+
import { LOG_PREFIX, REFRESH_RATE, TTL_REFRESH } from './constants';
|
|
7
|
+
|
|
8
|
+
export class ImpressionCountsCacheInRedis extends ImpressionCountsCacheInMemory {
|
|
9
|
+
|
|
10
|
+
private readonly log: ILogger;
|
|
11
|
+
private readonly key: string;
|
|
12
|
+
private readonly redis: Redis;
|
|
13
|
+
private readonly refreshRate: number;
|
|
14
|
+
private intervalId: any;
|
|
15
|
+
|
|
16
|
+
constructor(log: ILogger, key: string, redis: Redis, impressionCountsCacheSize?: number, refreshRate = REFRESH_RATE) {
|
|
17
|
+
super(impressionCountsCacheSize);
|
|
18
|
+
this.log = log;
|
|
19
|
+
this.key = key;
|
|
20
|
+
this.redis = redis;
|
|
21
|
+
this.refreshRate = refreshRate;
|
|
22
|
+
this.onFullQueue = () => { this.postImpressionCountsInRedis(); };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private postImpressionCountsInRedis() {
|
|
26
|
+
const counts = this.pop();
|
|
27
|
+
const keys = Object.keys(counts);
|
|
28
|
+
if (!keys.length) return Promise.resolve(false);
|
|
29
|
+
|
|
30
|
+
const pipeline = this.redis.pipeline();
|
|
31
|
+
keys.forEach(key => {
|
|
32
|
+
pipeline.hincrby(this.key, key, counts[key]);
|
|
33
|
+
});
|
|
34
|
+
return pipeline.exec()
|
|
35
|
+
.then(data => {
|
|
36
|
+
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
37
|
+
if (data.length && data.length === keys.length) {
|
|
38
|
+
return this.redis.expire(this.key, TTL_REFRESH);
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
.catch(err => {
|
|
42
|
+
this.log.error(`${LOG_PREFIX}Error in impression counts pipeline: ${err}.`);
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
start() {
|
|
48
|
+
this.intervalId = setInterval(this.postImpressionCountsInRedis.bind(this), this.refreshRate);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
stop() {
|
|
52
|
+
clearInterval(this.intervalId);
|
|
53
|
+
return this.postImpressionCountsInRedis();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Async consumer API, used by synchronizer
|
|
57
|
+
getImpressionsCount(): Promise<ImpressionCountsPayload | undefined> {
|
|
58
|
+
return this.redis.hgetall(this.key)
|
|
59
|
+
.then(counts => {
|
|
60
|
+
if (!Object.keys(counts).length) return undefined;
|
|
61
|
+
|
|
62
|
+
this.redis.del(this.key).catch(() => { /* no-op */ });
|
|
63
|
+
|
|
64
|
+
const pf: ImpressionCountsPayload['pf'] = [];
|
|
65
|
+
|
|
66
|
+
forOwn(counts, (count, key) => {
|
|
67
|
+
const nameAndTime = key.split('::');
|
|
68
|
+
if (nameAndTime.length !== 2) {
|
|
69
|
+
this.log.error(`${LOG_PREFIX}Error spliting key ${key}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const timeFrame = parseInt(nameAndTime[1]);
|
|
74
|
+
if (isNaN(timeFrame)) {
|
|
75
|
+
this.log.error(`${LOG_PREFIX}Error parsing time frame ${nameAndTime[1]}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rawCount = parseInt(count);
|
|
80
|
+
if (isNaN(rawCount)) {
|
|
81
|
+
this.log.error(`${LOG_PREFIX}Error parsing raw count ${count}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pf.push({
|
|
86
|
+
f: nameAndTime[0],
|
|
87
|
+
m: timeFrame,
|
|
88
|
+
rc: rawCount,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return { pf };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
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.
|
|
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);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { IUniqueKeysCacheBase } from '../types';
|
|
2
|
+
import { Redis } from 'ioredis';
|
|
3
|
+
import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
|
|
4
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
5
|
+
import { DEFAULT_CACHE_SIZE, REFRESH_RATE, TTL_REFRESH } from './constants';
|
|
6
|
+
import { LOG_PREFIX } from './constants';
|
|
7
|
+
import { ILogger } from '../../logger/types';
|
|
8
|
+
import { UniqueKeysItemSs } from '../../sync/submitters/types';
|
|
9
|
+
|
|
10
|
+
export class UniqueKeysCacheInRedis extends UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
11
|
+
|
|
12
|
+
private readonly log: ILogger;
|
|
13
|
+
private readonly key: string;
|
|
14
|
+
private readonly redis: Redis;
|
|
15
|
+
private readonly refreshRate: number;
|
|
16
|
+
private intervalId: any;
|
|
17
|
+
|
|
18
|
+
constructor(log: ILogger, key: string, redis: Redis, uniqueKeysQueueSize = DEFAULT_CACHE_SIZE, refreshRate = REFRESH_RATE) {
|
|
19
|
+
super(uniqueKeysQueueSize);
|
|
20
|
+
this.log = log;
|
|
21
|
+
this.key = key;
|
|
22
|
+
this.redis = redis;
|
|
23
|
+
this.refreshRate = refreshRate;
|
|
24
|
+
this.onFullQueue = () => { this.postUniqueKeysInRedis(); };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private postUniqueKeysInRedis() {
|
|
28
|
+
const featureNames = Object.keys(this.uniqueKeysTracker);
|
|
29
|
+
if (!featureNames.length) return Promise.resolve(false);
|
|
30
|
+
|
|
31
|
+
const pipeline = this.redis.pipeline();
|
|
32
|
+
for (let i = 0; i < featureNames.length; i++) {
|
|
33
|
+
const featureName = featureNames[i];
|
|
34
|
+
const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
|
|
35
|
+
const uniqueKeysPayload = {
|
|
36
|
+
f: featureName,
|
|
37
|
+
ks: featureKeys
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
pipeline.rpush(this.key, JSON.stringify(uniqueKeysPayload));
|
|
41
|
+
}
|
|
42
|
+
this.clear();
|
|
43
|
+
return pipeline.exec()
|
|
44
|
+
.then(data => {
|
|
45
|
+
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
46
|
+
if (data.length && data.length === featureNames.length) {
|
|
47
|
+
return this.redis.expire(this.key, TTL_REFRESH);
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
.catch(err => {
|
|
51
|
+
this.log.error(`${LOG_PREFIX}Error in uniqueKeys pipeline: ${err}.`);
|
|
52
|
+
return false;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
start() {
|
|
58
|
+
this.intervalId = setInterval(this.postUniqueKeysInRedis.bind(this), this.refreshRate);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
stop() {
|
|
62
|
+
clearInterval(this.intervalId);
|
|
63
|
+
return this.postUniqueKeysInRedis();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Async consumer API, used by synchronizer.
|
|
68
|
+
* @param count number of items to pop from the queue. If not provided or equal 0, all items will be popped.
|
|
69
|
+
*/
|
|
70
|
+
popNRaw(count = 0): Promise<UniqueKeysItemSs[]> {
|
|
71
|
+
return this.redis.lrange(this.key, 0, count - 1).then(uniqueKeyItems => {
|
|
72
|
+
return this.redis.ltrim(this.key, uniqueKeyItems.length, -1)
|
|
73
|
+
.then(() => uniqueKeyItems.map(uniqueKeyItem => JSON.parse(uniqueKeyItem)));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -6,8 +6,11 @@ import { SplitsCacheInRedis } from './SplitsCacheInRedis';
|
|
|
6
6
|
import { SegmentsCacheInRedis } from './SegmentsCacheInRedis';
|
|
7
7
|
import { ImpressionsCacheInRedis } from './ImpressionsCacheInRedis';
|
|
8
8
|
import { EventsCacheInRedis } from './EventsCacheInRedis';
|
|
9
|
-
import { STORAGE_REDIS } from '../../utils/constants';
|
|
9
|
+
import { DEBUG, NONE, STORAGE_REDIS } from '../../utils/constants';
|
|
10
10
|
import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
|
|
11
|
+
import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
|
|
12
|
+
import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
|
|
13
|
+
import { metadataBuilder } from '../utils';
|
|
11
14
|
|
|
12
15
|
export interface InRedisStorageOptions {
|
|
13
16
|
prefix?: string
|
|
@@ -22,15 +25,20 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
|
|
|
22
25
|
|
|
23
26
|
const prefix = validatePrefix(options.prefix);
|
|
24
27
|
|
|
25
|
-
function InRedisStorageFactory(
|
|
26
|
-
|
|
28
|
+
function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync {
|
|
29
|
+
const { onReadyCb, settings, settings: { log, sync: { impressionsMode } } } = params;
|
|
30
|
+
const metadata = metadataBuilder(settings);
|
|
27
31
|
const keys = new KeyBuilderSS(prefix, metadata);
|
|
28
32
|
const redisClient = new RedisAdapter(log, options.options || {});
|
|
29
33
|
const telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
|
|
34
|
+
const impressionCountsCache = impressionsMode !== DEBUG ? new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient) : undefined;
|
|
35
|
+
const uniqueKeysCache = impressionsMode === NONE ? new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient) : undefined;
|
|
30
36
|
|
|
31
37
|
// subscription to Redis connect event in order to emit SDK_READY event on consumer mode
|
|
32
38
|
redisClient.on('connect', () => {
|
|
33
39
|
onReadyCb();
|
|
40
|
+
if (impressionCountsCache) impressionCountsCache.start();
|
|
41
|
+
if (uniqueKeysCache) uniqueKeysCache.start();
|
|
34
42
|
|
|
35
43
|
// Synchronize config
|
|
36
44
|
telemetry.recordConfig();
|
|
@@ -40,13 +48,18 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
|
|
|
40
48
|
splits: new SplitsCacheInRedis(log, keys, redisClient),
|
|
41
49
|
segments: new SegmentsCacheInRedis(log, keys, redisClient),
|
|
42
50
|
impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
|
|
51
|
+
impressionCounts: impressionCountsCache,
|
|
43
52
|
events: new EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
|
|
44
53
|
telemetry,
|
|
54
|
+
uniqueKeys: uniqueKeysCache,
|
|
45
55
|
|
|
46
56
|
// When using REDIS we should:
|
|
47
57
|
// 1- Disconnect from the storage
|
|
48
|
-
destroy() {
|
|
49
|
-
|
|
58
|
+
destroy(): Promise<void> {
|
|
59
|
+
let promises = [];
|
|
60
|
+
if (impressionCountsCache) promises.push(impressionCountsCache.stop());
|
|
61
|
+
if (uniqueKeysCache) promises.push(uniqueKeysCache.stop());
|
|
62
|
+
return Promise.all(promises).then(() => { redisClient.disconnect(); });
|
|
50
63
|
// @TODO check that caches works as expected when redisClient is disconnected
|
|
51
64
|
}
|
|
52
65
|
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ILogger } from '../../logger/types';
|
|
2
|
+
import { ImpressionCountsPayload } from '../../sync/submitters/types';
|
|
3
|
+
import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
|
|
4
|
+
import { REFRESH_RATE } from '../inRedis/constants';
|
|
5
|
+
import { IPluggableStorageWrapper } from '../types';
|
|
6
|
+
import { LOG_PREFIX } from './constants';
|
|
7
|
+
|
|
8
|
+
export class ImpressionCountsCachePluggable extends ImpressionCountsCacheInMemory {
|
|
9
|
+
|
|
10
|
+
private readonly log: ILogger;
|
|
11
|
+
private readonly key: string;
|
|
12
|
+
private readonly wrapper: IPluggableStorageWrapper;
|
|
13
|
+
private readonly refreshRate: number;
|
|
14
|
+
private intervalId: any;
|
|
15
|
+
|
|
16
|
+
constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, impressionCountsCacheSize?: number, refreshRate = REFRESH_RATE) {
|
|
17
|
+
super(impressionCountsCacheSize);
|
|
18
|
+
this.log = log;
|
|
19
|
+
this.key = key;
|
|
20
|
+
this.wrapper = wrapper;
|
|
21
|
+
this.refreshRate = refreshRate;
|
|
22
|
+
this.onFullQueue = () => { this.storeImpressionCounts(); };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private storeImpressionCounts() {
|
|
26
|
+
const counts = this.pop();
|
|
27
|
+
const keys = Object.keys(counts);
|
|
28
|
+
if (!keys.length) return Promise.resolve(false);
|
|
29
|
+
|
|
30
|
+
const pipeline = keys.reduce<Promise<any>>((pipeline, key) => {
|
|
31
|
+
return pipeline.then(() => this.wrapper.incr(`${this.key}::${key}`, counts[key]));
|
|
32
|
+
}, Promise.resolve());
|
|
33
|
+
|
|
34
|
+
return pipeline.catch(err => {
|
|
35
|
+
this.log.error(`${LOG_PREFIX}Error in impression counts pipeline: ${err}.`);
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
start() {
|
|
41
|
+
this.intervalId = setInterval(this.storeImpressionCounts.bind(this), this.refreshRate);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
stop() {
|
|
45
|
+
clearInterval(this.intervalId);
|
|
46
|
+
return this.storeImpressionCounts();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Async consumer API, used by synchronizer
|
|
50
|
+
getImpressionsCount(): Promise<ImpressionCountsPayload | undefined> {
|
|
51
|
+
return this.wrapper.getKeysByPrefix(this.key)
|
|
52
|
+
.then(keys => {
|
|
53
|
+
return keys.length ? Promise.all(keys.map(key => this.wrapper.get(key)))
|
|
54
|
+
.then(counts => {
|
|
55
|
+
keys.forEach(key => this.wrapper.del(key).catch(() => { /* noop */ }));
|
|
56
|
+
|
|
57
|
+
const pf = [];
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < keys.length; i++) {
|
|
60
|
+
const key = keys[i];
|
|
61
|
+
const count = counts[i];
|
|
62
|
+
|
|
63
|
+
const keyFeatureNameAndTime = key.split('::');
|
|
64
|
+
if (keyFeatureNameAndTime.length !== 3) {
|
|
65
|
+
this.log.error(`${LOG_PREFIX}Error spliting key ${key}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const timeFrame = parseInt(keyFeatureNameAndTime[2]);
|
|
70
|
+
if (isNaN(timeFrame)) {
|
|
71
|
+
this.log.error(`${LOG_PREFIX}Error parsing time frame ${keyFeatureNameAndTime[2]}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// @ts-ignore
|
|
75
|
+
const rawCount = parseInt(count);
|
|
76
|
+
if (isNaN(rawCount)) {
|
|
77
|
+
this.log.error(`${LOG_PREFIX}Error parsing raw count ${count}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pf.push({
|
|
82
|
+
f: keyFeatureNameAndTime[1],
|
|
83
|
+
m: timeFrame,
|
|
84
|
+
rc: rawCount,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { pf };
|
|
89
|
+
}) : undefined;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -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);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { IPluggableStorageWrapper, IUniqueKeysCacheBase } from '../types';
|
|
2
|
+
import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
|
|
3
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
4
|
+
import { DEFAULT_CACHE_SIZE, REFRESH_RATE } from '../inRedis/constants';
|
|
5
|
+
import { LOG_PREFIX } from './constants';
|
|
6
|
+
import { ILogger } from '../../logger/types';
|
|
7
|
+
import { UniqueKeysItemSs } from '../../sync/submitters/types';
|
|
8
|
+
|
|
9
|
+
export class UniqueKeysCachePluggable extends UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
10
|
+
|
|
11
|
+
private readonly log: ILogger;
|
|
12
|
+
private readonly key: string;
|
|
13
|
+
private readonly wrapper: IPluggableStorageWrapper;
|
|
14
|
+
private readonly refreshRate: number;
|
|
15
|
+
private intervalId: any;
|
|
16
|
+
|
|
17
|
+
constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, uniqueKeysQueueSize = DEFAULT_CACHE_SIZE, refreshRate = REFRESH_RATE) {
|
|
18
|
+
super(uniqueKeysQueueSize);
|
|
19
|
+
this.log = log;
|
|
20
|
+
this.key = key;
|
|
21
|
+
this.wrapper = wrapper;
|
|
22
|
+
this.refreshRate = refreshRate;
|
|
23
|
+
this.onFullQueue = () => { this.storeUniqueKeys(); };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
storeUniqueKeys() {
|
|
27
|
+
const featureNames = Object.keys(this.uniqueKeysTracker);
|
|
28
|
+
if (!featureNames.length) return Promise.resolve(false);
|
|
29
|
+
|
|
30
|
+
const pipeline = featureNames.reduce<Promise<any>>((pipeline, featureName) => {
|
|
31
|
+
const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
|
|
32
|
+
const uniqueKeysPayload = {
|
|
33
|
+
f: featureName,
|
|
34
|
+
ks: featureKeys
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return pipeline.then(() => this.wrapper.pushItems(this.key, [JSON.stringify(uniqueKeysPayload)]));
|
|
38
|
+
}, Promise.resolve());
|
|
39
|
+
|
|
40
|
+
this.clear();
|
|
41
|
+
return pipeline.catch(err => {
|
|
42
|
+
this.log.error(`${LOG_PREFIX}Error in uniqueKeys pipeline: ${err}.`);
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
start() {
|
|
49
|
+
this.intervalId = setInterval(this.storeUniqueKeys.bind(this), this.refreshRate);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
stop() {
|
|
53
|
+
clearInterval(this.intervalId);
|
|
54
|
+
return this.storeUniqueKeys();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Async consumer API, used by synchronizer.
|
|
59
|
+
* @param count number of items to pop from the queue. If not provided or equal 0, all items will be popped.
|
|
60
|
+
*/
|
|
61
|
+
popNRaw(count = 0): Promise<UniqueKeysItemSs[]> {
|
|
62
|
+
return Promise.resolve(count || this.wrapper.getItemsCount(this.key))
|
|
63
|
+
.then(count => this.wrapper.popItems(this.key, count))
|
|
64
|
+
.then((uniqueKeyItems) => uniqueKeyItems.map(uniqueKeyItem => JSON.parse(uniqueKeyItem)));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
}
|
|
@@ -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
|
},
|