@autofleet/matmon 2.2.2-beta.2 → 2.3.1-beta-1
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/lib/cache.d.ts +2 -1
- package/lib/cache.js +29 -7
- package/lib/cache.test.d.ts +1 -0
- package/lib/cache.test.js +172 -0
- package/lib/redis/index.d.ts +5 -4
- package/lib/redis/index.js +34 -13
- package/lib/redis/index.test.d.ts +1 -0
- package/lib/redis/index.test.js +65 -0
- package/package.json +6 -7
package/lib/cache.d.ts
CHANGED
|
@@ -18,7 +18,8 @@ interface GetMultipleWithCacheOptions<V = unknown> {
|
|
|
18
18
|
setInCache: (key: string, value: V) => Promise<void>;
|
|
19
19
|
getter?: (query: any) => Promise<V>;
|
|
20
20
|
multiGetter?: (queries: any[]) => Promise<V[]>;
|
|
21
|
+
setMultiInCache?: (keyValues: Record<string, object>) => Promise<void>;
|
|
21
22
|
idField?: string;
|
|
22
23
|
}
|
|
23
|
-
export declare const getMultipleWithCache: <V = unknown>({ getFromCache, multiGetterFromCache, setInCache, getter, multiGetter, idField, }: GetMultipleWithCacheOptions<V>) => (queries: any[]) => Promise<V[]>;
|
|
24
|
+
export declare const getMultipleWithCache: <V = unknown>({ getFromCache, multiGetterFromCache, setInCache, setMultiInCache, getter, multiGetter, idField, }: GetMultipleWithCacheOptions<V>) => (queries: any[]) => Promise<V[]>;
|
|
24
25
|
export {};
|
package/lib/cache.js
CHANGED
|
@@ -30,6 +30,7 @@ exports.getMultipleWithCache = exports.getWithCacheSupport = exports.getNewLRU =
|
|
|
30
30
|
const lru_cache_1 = __importDefault(require("lru-cache"));
|
|
31
31
|
const locking = __importStar(require("./locking"));
|
|
32
32
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
33
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
33
34
|
dotenv_1.default.config();
|
|
34
35
|
const DEFAULT_CACHE_SIZE = 300;
|
|
35
36
|
const MAX_SIZE = 100000;
|
|
@@ -52,10 +53,20 @@ const getNewLRU = (lifeTimeInSec, size) => new lru_cache_1.default(getOptions({
|
|
|
52
53
|
size,
|
|
53
54
|
}));
|
|
54
55
|
exports.getNewLRU = getNewLRU;
|
|
56
|
+
const IN_LOCAL_TEST = process.env.IS_IN_MATMON_TESTING === 'true';
|
|
57
|
+
const IS_IN_SERVICE_TEST = process.env.NODE_ENV === 'test' && !IN_LOCAL_TEST;
|
|
58
|
+
const tryToSetInCache = async (callback) => {
|
|
59
|
+
try {
|
|
60
|
+
await callback();
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
logger_1.default.error('Failed to set in cache', e);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
55
66
|
const getWithCacheSupport = async ({ cacheKey, cacheGet, cacheSet, fetching, skipCache, }) => {
|
|
56
|
-
if (skipCache ||
|
|
67
|
+
if (skipCache || IS_IN_SERVICE_TEST) {
|
|
57
68
|
const res = await fetching();
|
|
58
|
-
await cacheSet(res);
|
|
69
|
+
await tryToSetInCache(() => cacheSet(res));
|
|
59
70
|
return res;
|
|
60
71
|
}
|
|
61
72
|
let valueToReturn = null;
|
|
@@ -64,7 +75,7 @@ const getWithCacheSupport = async ({ cacheKey, cacheGet, cacheSet, fetching, ski
|
|
|
64
75
|
valueToReturn = await cacheGet();
|
|
65
76
|
if (!valueToReturn) {
|
|
66
77
|
valueToReturn = await fetching();
|
|
67
|
-
await cacheSet(valueToReturn);
|
|
78
|
+
await tryToSetInCache(() => cacheSet(valueToReturn));
|
|
68
79
|
}
|
|
69
80
|
else {
|
|
70
81
|
// logger.info('get value from cache');
|
|
@@ -75,7 +86,7 @@ const getWithCacheSupport = async ({ cacheKey, cacheGet, cacheSet, fetching, ski
|
|
|
75
86
|
}
|
|
76
87
|
catch (e) {
|
|
77
88
|
valueToReturn = await fetching();
|
|
78
|
-
await cacheSet(valueToReturn);
|
|
89
|
+
await tryToSetInCache(() => cacheSet(valueToReturn));
|
|
79
90
|
deleteMutexByCacheKey(cacheKey);
|
|
80
91
|
}
|
|
81
92
|
return valueToReturn;
|
|
@@ -87,7 +98,7 @@ const getIdField = (query, idField) => {
|
|
|
87
98
|
}
|
|
88
99
|
return query[idField];
|
|
89
100
|
};
|
|
90
|
-
const getMultipleWithCache = ({ getFromCache, multiGetterFromCache, setInCache, getter, multiGetter, idField = 'id', }) => async (queries) => {
|
|
101
|
+
const getMultipleWithCache = ({ getFromCache, multiGetterFromCache, setInCache, setMultiInCache, getter, multiGetter, idField = 'id', }) => async (queries) => {
|
|
91
102
|
const queriesMap = new Map(queries.filter(Boolean).map(query => [getIdField(query, idField), query]));
|
|
92
103
|
const resultMap = new Map();
|
|
93
104
|
const valuesToPullFromCache = [...queriesMap.values()];
|
|
@@ -98,13 +109,24 @@ const getMultipleWithCache = ({ getFromCache, multiGetterFromCache, setInCache,
|
|
|
98
109
|
queriesMap.delete(getIdField(value, idField));
|
|
99
110
|
resultMap.set(getIdField(value, idField), value);
|
|
100
111
|
});
|
|
101
|
-
let valuesFromGetter = [];
|
|
102
112
|
if (queriesMap.size > 0) {
|
|
103
113
|
const valuesFromGetter = await (multiGetter?.([...queriesMap.values()]) || // Use multiGetter if it's provided
|
|
104
114
|
Promise.all([...queriesMap.values()].map(id => getter(id))) // Otherwise, iterate over the queries with getter
|
|
105
115
|
);
|
|
116
|
+
if (setMultiInCache) {
|
|
117
|
+
const setCacheObject = valuesFromGetter.reduce((acc, value) => {
|
|
118
|
+
acc[getIdField(value, idField)] = value;
|
|
119
|
+
return acc;
|
|
120
|
+
}, {});
|
|
121
|
+
await tryToSetInCache(() => setMultiInCache(setCacheObject));
|
|
122
|
+
}
|
|
106
123
|
valuesFromGetter.forEach((value) => {
|
|
107
|
-
|
|
124
|
+
if (!value) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!setMultiInCache) {
|
|
128
|
+
tryToSetInCache(() => setInCache(value[idField], value));
|
|
129
|
+
}
|
|
108
130
|
resultMap.set(getIdField(value, idField), value);
|
|
109
131
|
});
|
|
110
132
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const cache_1 = require("./cache");
|
|
4
|
+
const async_mutex_1 = require("async-mutex");
|
|
5
|
+
jest.mock('./locking', () => ({
|
|
6
|
+
getMutex: jest.fn(() => new async_mutex_1.Mutex()),
|
|
7
|
+
wrapWithMutex: jest.fn((mutex, fn) => fn()),
|
|
8
|
+
}));
|
|
9
|
+
describe('Cache', () => {
|
|
10
|
+
describe('getNewLRU', () => {
|
|
11
|
+
it('should create a new LRU cache with default size', () => {
|
|
12
|
+
const cache = (0, cache_1.getNewLRU)(60);
|
|
13
|
+
expect(cache.max).toBe(300);
|
|
14
|
+
});
|
|
15
|
+
it('should create a new LRU cache with specified size', () => {
|
|
16
|
+
const cache = (0, cache_1.getNewLRU)(60, 500);
|
|
17
|
+
expect(cache.max).toBe(500);
|
|
18
|
+
});
|
|
19
|
+
it('should not exceed the maximum size', () => {
|
|
20
|
+
const cache = (0, cache_1.getNewLRU)(60, 200000);
|
|
21
|
+
expect(cache.max).toBe(100000);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('getWithCacheSupport', () => {
|
|
25
|
+
const cacheKey = 'testKey';
|
|
26
|
+
const createMocks = () => {
|
|
27
|
+
const cacheGet = jest.fn();
|
|
28
|
+
const cacheSet = jest.fn();
|
|
29
|
+
const fetching = jest.fn();
|
|
30
|
+
const getFromCache = jest.fn();
|
|
31
|
+
const setInCache = jest.fn();
|
|
32
|
+
const multiGetterFromCache = jest.fn();
|
|
33
|
+
const setMultiInCache = jest.fn();
|
|
34
|
+
return {
|
|
35
|
+
cacheGet,
|
|
36
|
+
cacheSet,
|
|
37
|
+
fetching,
|
|
38
|
+
getFromCache,
|
|
39
|
+
setInCache,
|
|
40
|
+
multiGetterFromCache,
|
|
41
|
+
setMultiInCache
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
jest.clearAllMocks();
|
|
46
|
+
});
|
|
47
|
+
it('should fetch and set value if skipCache is true', async () => {
|
|
48
|
+
const { cacheGet, cacheSet, fetching } = createMocks();
|
|
49
|
+
fetching.mockResolvedValue('fetchedValue');
|
|
50
|
+
await (0, cache_1.getWithCacheSupport)({ cacheKey, cacheGet, cacheSet, fetching, skipCache: true });
|
|
51
|
+
expect(fetching).toHaveBeenCalled();
|
|
52
|
+
expect(cacheSet).toHaveBeenCalledWith('fetchedValue');
|
|
53
|
+
});
|
|
54
|
+
it('should fetch and set value if cache is empty', async () => {
|
|
55
|
+
const { cacheGet, cacheSet, fetching } = createMocks();
|
|
56
|
+
cacheGet.mockResolvedValue(null);
|
|
57
|
+
fetching.mockResolvedValue('fetchedValue');
|
|
58
|
+
await (0, cache_1.getWithCacheSupport)({ cacheKey, cacheGet, cacheSet, fetching });
|
|
59
|
+
expect(fetching).toHaveBeenCalled();
|
|
60
|
+
expect(cacheSet).toHaveBeenCalledWith('fetchedValue');
|
|
61
|
+
});
|
|
62
|
+
it('should return cached value if available', async () => {
|
|
63
|
+
const { cacheGet, cacheSet, fetching } = createMocks();
|
|
64
|
+
cacheGet.mockResolvedValue('cachedValue');
|
|
65
|
+
const result = await (0, cache_1.getWithCacheSupport)({ cacheKey, cacheGet, cacheSet, fetching });
|
|
66
|
+
expect(result).toBe('cachedValue');
|
|
67
|
+
expect(fetching).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
it('should retry fetching if an error occurs', async () => {
|
|
70
|
+
const { cacheGet, cacheSet, fetching } = createMocks();
|
|
71
|
+
cacheGet.mockRejectedValue(new Error('Cache error'));
|
|
72
|
+
fetching.mockResolvedValue('fetchedValue');
|
|
73
|
+
const result = await (0, cache_1.getWithCacheSupport)({ cacheKey, cacheGet, cacheSet, fetching });
|
|
74
|
+
expect(result).toBe('fetchedValue');
|
|
75
|
+
expect(fetching).toHaveBeenCalled();
|
|
76
|
+
expect(cacheSet).toHaveBeenCalledWith('fetchedValue');
|
|
77
|
+
});
|
|
78
|
+
it('should not throw an error if cacheSet fails', async () => {
|
|
79
|
+
const { cacheGet, cacheSet, fetching } = createMocks();
|
|
80
|
+
cacheGet.mockResolvedValue(null);
|
|
81
|
+
cacheSet.mockRejectedValue(new Error('Cache set error'));
|
|
82
|
+
fetching.mockResolvedValue('fetchedValue');
|
|
83
|
+
await expect((0, cache_1.getWithCacheSupport)({ cacheKey, cacheGet, cacheSet, fetching })).resolves.not.toThrow();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('getMultipleWithCache', () => {
|
|
87
|
+
const buildMocks = () => {
|
|
88
|
+
const getFromCache = jest.fn();
|
|
89
|
+
const multiGetterFromCache = jest.fn();
|
|
90
|
+
const setInCache = jest.fn();
|
|
91
|
+
const setMultiInCache = jest.fn();
|
|
92
|
+
const getter = jest.fn();
|
|
93
|
+
const multiGetter = jest.fn();
|
|
94
|
+
return {
|
|
95
|
+
getFromCache,
|
|
96
|
+
multiGetterFromCache,
|
|
97
|
+
setInCache,
|
|
98
|
+
setMultiInCache,
|
|
99
|
+
getter,
|
|
100
|
+
multiGetter,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
jest.clearAllMocks();
|
|
105
|
+
});
|
|
106
|
+
it('should return values from cache if available', async () => {
|
|
107
|
+
const { getFromCache, setInCache, getter } = buildMocks();
|
|
108
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
109
|
+
getFromCache.mockResolvedValueOnce({ id: 1, value: 'cachedValue1' }).mockResolvedValueOnce({ id: 2, value: 'cachedValue2' });
|
|
110
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ getFromCache, setInCache, getter });
|
|
111
|
+
const result = await getMultiple(queries);
|
|
112
|
+
expect(result.map(({ value }) => value)).toEqual(['cachedValue1', 'cachedValue2']);
|
|
113
|
+
});
|
|
114
|
+
it('should fetch and cache values if not in cache', async () => {
|
|
115
|
+
const { getFromCache, setInCache, getter } = buildMocks();
|
|
116
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
117
|
+
getFromCache.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
118
|
+
getter.mockResolvedValueOnce({ id: 1, value: 'fetchedValue1' }).mockResolvedValueOnce({ id: 2, value: 'fetchedValue2' });
|
|
119
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ getFromCache, setInCache, getter });
|
|
120
|
+
const result = await getMultiple(queries);
|
|
121
|
+
expect(result.map(({ value }) => value)).toEqual(['fetchedValue1', 'fetchedValue2']);
|
|
122
|
+
expect(setInCache).toHaveBeenCalledWith(1, { id: 1, value: 'fetchedValue1' });
|
|
123
|
+
expect(setInCache).toHaveBeenCalledWith(2, { id: 2, value: 'fetchedValue2' });
|
|
124
|
+
});
|
|
125
|
+
it('should use multiGetterFromCache if provided', async () => {
|
|
126
|
+
const { multiGetterFromCache, setInCache, getter } = buildMocks();
|
|
127
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
128
|
+
multiGetterFromCache.mockResolvedValue([{ id: 1, value: 'cachedValue1' }, { id: 2, value: 'cachedValue2' }]);
|
|
129
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ multiGetterFromCache, setInCache, getter });
|
|
130
|
+
const result = await getMultiple(queries);
|
|
131
|
+
expect(result.map(({ value }) => value)).toEqual(['cachedValue1', 'cachedValue2']);
|
|
132
|
+
});
|
|
133
|
+
it('should use multiGetter if provided', async () => {
|
|
134
|
+
const { getFromCache, setInCache, multiGetter } = buildMocks();
|
|
135
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
136
|
+
getFromCache.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
137
|
+
multiGetter.mockResolvedValue([{ id: 1, value: 'fetchedValue1' }, { id: 2, value: 'fetchedValue2' }]);
|
|
138
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ getFromCache, setInCache, multiGetter });
|
|
139
|
+
const result = await getMultiple(queries);
|
|
140
|
+
expect(result.map(({ value }) => value)).toEqual(['fetchedValue1', 'fetchedValue2']);
|
|
141
|
+
expect(setInCache).toHaveBeenCalledWith(1, { id: 1, value: 'fetchedValue1' });
|
|
142
|
+
expect(setInCache).toHaveBeenCalledWith(2, { id: 2, value: 'fetchedValue2' });
|
|
143
|
+
});
|
|
144
|
+
it('should use setMultiInCache if provided', async () => {
|
|
145
|
+
const { getFromCache, setInCache, multiGetter, setMultiInCache } = buildMocks();
|
|
146
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
147
|
+
getFromCache.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
148
|
+
multiGetter.mockResolvedValue([{ id: 1, value: 'fetchedValue1' }, { id: 2, value: 'fetchedValue2' }]);
|
|
149
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ getFromCache, setInCache, multiGetter, setMultiInCache });
|
|
150
|
+
await getMultiple(queries);
|
|
151
|
+
expect(setMultiInCache).toHaveBeenCalledWith({ 1: { id: 1, value: 'fetchedValue1' }, 2: { id: 2, value: 'fetchedValue2' } });
|
|
152
|
+
});
|
|
153
|
+
it('Should not throw and error if setMultiInCache throws an error', async () => {
|
|
154
|
+
const { getFromCache, setInCache, multiGetter, setMultiInCache } = buildMocks();
|
|
155
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
156
|
+
getFromCache.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
157
|
+
multiGetter.mockResolvedValue([{ id: 1, value: 'fetchedValue1' }, { id: 2, value: 'fetchedValue2' }]);
|
|
158
|
+
setMultiInCache.mockRejectedValue(new Error('Error setting cache'));
|
|
159
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ getFromCache, setInCache, multiGetter, setMultiInCache });
|
|
160
|
+
await expect(getMultiple(queries)).resolves.not.toThrow();
|
|
161
|
+
});
|
|
162
|
+
it('should not throw an error if setInCache fails', async () => {
|
|
163
|
+
const { getFromCache, setInCache, getter } = buildMocks();
|
|
164
|
+
const queries = [{ id: 1 }, { id: 2 }];
|
|
165
|
+
getFromCache.mockResolvedValueOnce(null).mockResolvedValueOnce(null);
|
|
166
|
+
getter.mockResolvedValueOnce({ id: 1, value: 'fetchedValue1' }).mockResolvedValueOnce({ id: 2, value: 'fetchedValue2' });
|
|
167
|
+
setInCache.mockRejectedValue(new Error('Error setting cache'));
|
|
168
|
+
const getMultiple = (0, cache_1.getMultipleWithCache)({ getFromCache, setInCache, getter });
|
|
169
|
+
await expect(getMultiple(queries)).resolves.not.toThrow();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
package/lib/redis/index.d.ts
CHANGED
|
@@ -3,17 +3,18 @@ declare class RedisCache {
|
|
|
3
3
|
private locker;
|
|
4
4
|
private lockTimeout;
|
|
5
5
|
private lockDuration;
|
|
6
|
-
|
|
6
|
+
locks: any;
|
|
7
7
|
private baseTTL;
|
|
8
8
|
private lockRetries;
|
|
9
|
-
|
|
9
|
+
keyPrefix: string;
|
|
10
|
+
useLock: boolean;
|
|
10
11
|
constructor(options: any);
|
|
11
12
|
get(key: any): Promise<any>;
|
|
12
|
-
getMultiple(keys: any
|
|
13
|
+
getMultiple(keys: any): Promise<any>;
|
|
13
14
|
set(key: any, value: any): Promise<void>;
|
|
15
|
+
setMultiple(keyValues: Record<string, any>): Promise<any>;
|
|
14
16
|
remove(key: any): Promise<void>;
|
|
15
17
|
removeMultiple(keys: any): Promise<void>;
|
|
16
|
-
flushAll(): Promise<void>;
|
|
17
18
|
getClient(): any;
|
|
18
19
|
private lock;
|
|
19
20
|
}
|
package/lib/redis/index.js
CHANGED
|
@@ -5,11 +5,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const redis_1 = __importDefault(require("redis"));
|
|
7
7
|
const redis_lock_1 = __importDefault(require("redis-lock"));
|
|
8
|
-
const bluebird_1 = __importDefault(require("bluebird"));
|
|
9
8
|
const util_1 = require("util");
|
|
10
9
|
const errors_1 = require("./errors");
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
11
|
+
const promisifyAll = (obj) => {
|
|
12
|
+
Object.keys(obj).forEach((key) => {
|
|
13
|
+
if (typeof obj[key] === 'function') {
|
|
14
|
+
obj[`${key}Async`] = (0, util_1.promisify)(obj[key]);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
promisifyAll(redis_1.default.RedisClient.prototype);
|
|
19
|
+
promisifyAll(redis_1.default.Multi.prototype);
|
|
13
20
|
const { env } = process;
|
|
14
21
|
const HOST = env.REDIS_HOST_NAME || '127.0.0.1';
|
|
15
22
|
const PORT = env.REDIS_HOST_PORT || 6379;
|
|
@@ -33,6 +40,7 @@ class RedisCache {
|
|
|
33
40
|
this.baseTTL = options.ttl ?? DEFAULT_BASE_TTL;
|
|
34
41
|
this.locks = {};
|
|
35
42
|
this.useLock = !!options.useLock;
|
|
43
|
+
this.keyPrefix = KEY_PREFIX;
|
|
36
44
|
}
|
|
37
45
|
async get(key) {
|
|
38
46
|
const keyWithPrefix = KEY_PREFIX + key;
|
|
@@ -58,8 +66,8 @@ class RedisCache {
|
|
|
58
66
|
}
|
|
59
67
|
return JSON.parse(value);
|
|
60
68
|
}
|
|
61
|
-
async getMultiple(keys
|
|
62
|
-
const keysWithPrefix =
|
|
69
|
+
async getMultiple(keys) {
|
|
70
|
+
const keysWithPrefix = keys.map(key => KEY_PREFIX + key);
|
|
63
71
|
let values;
|
|
64
72
|
try {
|
|
65
73
|
if (keysWithPrefix.length === 0) {
|
|
@@ -87,6 +95,27 @@ class RedisCache {
|
|
|
87
95
|
throw new errors_1.RedisCacheError('Failed to set a key-value pair', err);
|
|
88
96
|
}
|
|
89
97
|
}
|
|
98
|
+
async setMultiple(keyValues) {
|
|
99
|
+
if (typeof keyValues !== 'object') {
|
|
100
|
+
const error = new errors_1.RedisCacheError('keyValues must be an object', new Error('keyValues must be an object'));
|
|
101
|
+
logger_1.default.error('keyValues must be an object', { error });
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
const keyValuesWithPrefix = Object.entries(keyValues).map(([key, value]) => [KEY_PREFIX + key, JSON.stringify(value)]);
|
|
105
|
+
const ttl = parseInt(String(this.baseTTL * (Math.random() + 1)), 10);
|
|
106
|
+
try {
|
|
107
|
+
const multi = this.client.multi();
|
|
108
|
+
const setPromise = multi.msetAsync(...keyValuesWithPrefix.flat());
|
|
109
|
+
keyValuesWithPrefix.map(([key]) => {
|
|
110
|
+
return multi.expireAsync(key, ttl);
|
|
111
|
+
});
|
|
112
|
+
await multi.exec();
|
|
113
|
+
return setPromise;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
throw new errors_1.RedisCacheError('Failed to set multiple key-value pairs', err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
90
119
|
async remove(key) {
|
|
91
120
|
const keyWithPrefix = KEY_PREFIX + key;
|
|
92
121
|
try {
|
|
@@ -109,14 +138,6 @@ class RedisCache {
|
|
|
109
138
|
throw new errors_1.RedisCacheError(`Failed to delete multiple keys ${keysWithPrefix.join('|')}`, err);
|
|
110
139
|
}
|
|
111
140
|
}
|
|
112
|
-
async flushAll() {
|
|
113
|
-
try {
|
|
114
|
-
await this.client.flushAll();
|
|
115
|
-
}
|
|
116
|
-
catch (err) {
|
|
117
|
-
throw new errors_1.RedisCacheError(`Failed to flush all keys`, err);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
141
|
getClient() {
|
|
121
142
|
return this.client;
|
|
122
143
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const redis_1 = __importDefault(require("redis"));
|
|
7
|
+
const index_1 = __importDefault(require("./index"));
|
|
8
|
+
const util_1 = require("util");
|
|
9
|
+
const promisifyAll = (obj) => {
|
|
10
|
+
Object.keys(obj).forEach((key) => {
|
|
11
|
+
if (typeof obj[key] === 'function') {
|
|
12
|
+
obj[`${key}Async`] = (0, util_1.promisify)(obj[key]);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
promisifyAll(redis_1.default.RedisClient.prototype);
|
|
17
|
+
describe('RedisCache', () => {
|
|
18
|
+
let redisCache;
|
|
19
|
+
const testClient = redis_1.default.createClient();
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
redisCache = new index_1.default({});
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
describe('setMultiple', () => {
|
|
27
|
+
it('should set multiple key-value pairs in redis', async () => {
|
|
28
|
+
const keyValues = { testKey1: 'testValue1', testKey2: 'testValue2' };
|
|
29
|
+
const keyValuesArray = Object.entries(keyValues);
|
|
30
|
+
await redisCache.setMultiple(keyValues);
|
|
31
|
+
const expectedKeyValues = keyValuesArray.map(([key, value]) => [`${redisCache.keyPrefix}${key}`, JSON.stringify(value)]);
|
|
32
|
+
await Promise.all(expectedKeyValues.map(async ([key, value]) => {
|
|
33
|
+
const valueInRedis = await testClient.getAsync(key);
|
|
34
|
+
expect(valueInRedis).toBe(value);
|
|
35
|
+
}));
|
|
36
|
+
// Check that the keys expire after a random amount of time.
|
|
37
|
+
const currentTTL = await testClient.ttlAsync(expectedKeyValues[0][0]);
|
|
38
|
+
expect(currentTTL).toBeGreaterThanOrEqual(0);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('set', () => {
|
|
42
|
+
it('should set a key-value pair in redis', async () => {
|
|
43
|
+
const key = 'testKey';
|
|
44
|
+
const value = 'testValue';
|
|
45
|
+
await redisCache.set(key, value);
|
|
46
|
+
const keyWithPrefix = `${redisCache.keyPrefix}${key}`;
|
|
47
|
+
const valueInRedis = await testClient.getAsync(keyWithPrefix);
|
|
48
|
+
expect(valueInRedis).toBe(JSON.stringify(value));
|
|
49
|
+
// Check that the key expires after a random amount of time.
|
|
50
|
+
const currentTTL = await testClient.ttlAsync(keyWithPrefix);
|
|
51
|
+
expect(currentTTL).toBeGreaterThanOrEqual(0);
|
|
52
|
+
});
|
|
53
|
+
it('should release the lock after setting a key-value pair', async () => {
|
|
54
|
+
const key = 'testKeyWithLock';
|
|
55
|
+
const value = 'testValueWithLock';
|
|
56
|
+
redisCache.useLock = true;
|
|
57
|
+
await redisCache.set(key, value);
|
|
58
|
+
const keyWithPrefix = `${redisCache.keyPrefix}${key}`;
|
|
59
|
+
const valueInRedis = await testClient.getAsync(keyWithPrefix);
|
|
60
|
+
expect(valueInRedis).toBe(JSON.stringify(value));
|
|
61
|
+
// Check that the lock is released
|
|
62
|
+
expect(redisCache.locks[keyWithPrefix]).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/matmon",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1-beta-1",
|
|
4
4
|
"description": "manage cache",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -12,10 +12,6 @@
|
|
|
12
12
|
"test-auto": "jest --watch --runInBand",
|
|
13
13
|
"linter": "./node_modules/.bin/eslint ."
|
|
14
14
|
},
|
|
15
|
-
"jest": {
|
|
16
|
-
"setupTestFrameworkScriptFile": "jest-extended",
|
|
17
|
-
"testURL": "http://localhost:8085/"
|
|
18
|
-
},
|
|
19
15
|
"repository": {
|
|
20
16
|
"type": "git",
|
|
21
17
|
"url": "git+ssh://git@gitlab.com/AutoFleet/matmon.git"
|
|
@@ -27,10 +23,9 @@
|
|
|
27
23
|
},
|
|
28
24
|
"homepage": "https://github.com/Autofleet/matmon",
|
|
29
25
|
"dependencies": {
|
|
30
|
-
"@autofleet/logger": "4
|
|
26
|
+
"@autofleet/logger": "^4",
|
|
31
27
|
"@types/node": "^14.14.20",
|
|
32
28
|
"async-mutex": "^0.2.6",
|
|
33
|
-
"bluebird": "^3.7.2",
|
|
34
29
|
"dotenv": "^8.2.0",
|
|
35
30
|
"lru-cache": "^6.0.0",
|
|
36
31
|
"redis": "^3.0.2",
|
|
@@ -40,8 +35,12 @@
|
|
|
40
35
|
},
|
|
41
36
|
"devDependencies": {
|
|
42
37
|
"@autofleet/logger": "^4.0.3",
|
|
38
|
+
"@types/jest": "^29.5.14",
|
|
43
39
|
"@types/lru-cache": "^5.1.1",
|
|
44
40
|
"@types/node": "^14.14.20",
|
|
41
|
+
"@types/redis": "^4.0.11",
|
|
42
|
+
"jest": "^29.7.0",
|
|
43
|
+
"ts-jest": "^29.2.5",
|
|
45
44
|
"typescript": "^5.5.2"
|
|
46
45
|
},
|
|
47
46
|
"peerDependencies": {
|