exframe-cache-manager 2.2.0 → 2.2.2
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/index.js +3 -270
- package/index.mjs +3 -0
- package/package.json +11 -3
- package/src/cacheManager/index.js +272 -0
- package/test/cacheManager.test.js +5 -5
package/index.js
CHANGED
|
@@ -1,272 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
const cacheManagerModule = require('./src/cacheManager/index');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import { setTimeout as waitTimeout } from 'node:timers/promises';
|
|
5
|
-
import CacheManager from 'cache-manager';
|
|
6
|
-
import RedisStore from 'cache-manager-redis-store';
|
|
7
|
-
import { lazyInstrument } from 'exframe-metrics';
|
|
8
|
-
import health from 'exframe-health';
|
|
9
|
-
import service from 'exframe-service';
|
|
10
|
-
import { generateShortId } from 'exframe-utilities';
|
|
3
|
+
const { cachemanager } = cacheManagerModule;
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const db = 0;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Cache manager for storing stuff.
|
|
18
|
-
* Options is required to pass the redis location
|
|
19
|
-
* @param {Options} options
|
|
20
|
-
*/
|
|
21
|
-
function cachemanager(options) {
|
|
22
|
-
if (options.url) {
|
|
23
|
-
options.host = undefined;
|
|
24
|
-
options.port = undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const redisCache = CacheManager.caching({
|
|
28
|
-
store: RedisStore,
|
|
29
|
-
ttl: DEFAULT_TTL,
|
|
30
|
-
db,
|
|
31
|
-
...options
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// @ts-ignore
|
|
35
|
-
const redisStore = redisCache.store;
|
|
36
|
-
const redisClient = redisStore.getClient();
|
|
37
|
-
redisClient.setMaxListeners(0);
|
|
38
|
-
|
|
39
|
-
redisClient.on('error', (e) => {
|
|
40
|
-
console.log('Unhandled Redis Error', { errorDetails: e }); // eslint-disable-line
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
process.on('beforeExit', () => {
|
|
44
|
-
if (redisClient.connected) {
|
|
45
|
-
redisClient.removeAllListeners();
|
|
46
|
-
redisClient.end(true);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const instance = {
|
|
51
|
-
id: generateShortId(),
|
|
52
|
-
get cacheStorePool() {
|
|
53
|
-
return redisClient;
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
*
|
|
58
|
-
* @param {string} key
|
|
59
|
-
* @param {object} context
|
|
60
|
-
* @param {Options} options
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
async getItem(context, key, { timeout } = {}) {
|
|
64
|
-
if (arguments.length === 1) {
|
|
65
|
-
// context = key, param order was switched in function def to preserve backwards compatability
|
|
66
|
-
return redisCache.get(context);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const controller = new AbortController();
|
|
70
|
-
if (timeout) {
|
|
71
|
-
const result = await Promise.race([
|
|
72
|
-
redisCache.get(key),
|
|
73
|
-
waitTimeout(timeout, false, { signal: controller.signal, ref: false })
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
if (!result) {
|
|
77
|
-
context.log.info('CACHE TIMEOUT ERROR');
|
|
78
|
-
return Promise.reject(new Error('Timed out attempting to retrieve item from cache.'));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
controller.abort();
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
return redisCache.get(key);
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
*
|
|
89
|
-
* @template T
|
|
90
|
-
* @param {string} key
|
|
91
|
-
* @param {T} value
|
|
92
|
-
* @param {number} [ttl]
|
|
93
|
-
* @returns {Promise<T>}
|
|
94
|
-
*/
|
|
95
|
-
async setItem(key, value, ttl) {
|
|
96
|
-
await redisCache.set(key, value, {
|
|
97
|
-
ttl: ttl || Number(process.env.REDIS_CACHE_TTL)
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return value;
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Set the item if it does not exist yet. If the item already exists, do not set and return null.
|
|
105
|
-
* @template T
|
|
106
|
-
* @param {String} key
|
|
107
|
-
* @param {T} value
|
|
108
|
-
* @param {Number} ttl
|
|
109
|
-
* @returns {Promise<T|null>}
|
|
110
|
-
*/
|
|
111
|
-
async setItemIfNotExists(key, value, ttl) {
|
|
112
|
-
const ttlToSet = ttl ?? (process.env.REDIS_CACHE_TTL
|
|
113
|
-
? Number(process.env.REDIS_CACHE_TTL)
|
|
114
|
-
: DEFAULT_TTL);
|
|
115
|
-
|
|
116
|
-
return new Promise((resolve, reject) => {
|
|
117
|
-
redisClient.set(key, value, 'NX', 'EX', ttlToSet, (error, result) => {
|
|
118
|
-
if (error) {
|
|
119
|
-
return reject(error);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
resolve(result === 'OK' ? value : null);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
*
|
|
129
|
-
* @param {string} key
|
|
130
|
-
* @param {any} value
|
|
131
|
-
*/
|
|
132
|
-
addSetItem(key, value) {
|
|
133
|
-
return this.processRedisCommands(client => client.sadd(key, value));
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
*
|
|
138
|
-
* @param {string} key
|
|
139
|
-
* @param {any} value
|
|
140
|
-
*/
|
|
141
|
-
removeSetItem(key, value) {
|
|
142
|
-
return this.processRedisCommands(client => client.srem(key, value));
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
*
|
|
147
|
-
* @param {string} key
|
|
148
|
-
* @param {any} value
|
|
149
|
-
* @returns {Promise<Boolean>}
|
|
150
|
-
*/
|
|
151
|
-
isSetMember(key, value) {
|
|
152
|
-
return this.processRedisCommands(async client => new Promise((res, rej) => {
|
|
153
|
-
client.sismember(key, value, (ex, result) => {
|
|
154
|
-
if (ex) return rej(ex);
|
|
155
|
-
res(result === 1);
|
|
156
|
-
});
|
|
157
|
-
}));
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* @returns {Promise<true>}
|
|
162
|
-
*/
|
|
163
|
-
async healthCheck() {
|
|
164
|
-
return this.processRedisCommands(client => client && client.server_info.loading === '0');
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
close() {
|
|
168
|
-
return new Promise(res => {
|
|
169
|
-
redisClient.removeAllListeners();
|
|
170
|
-
redisClient.end(true);
|
|
171
|
-
res();
|
|
172
|
-
});
|
|
173
|
-
},
|
|
174
|
-
|
|
175
|
-
getClient() {
|
|
176
|
-
return new Promise((resolve, reject) => {
|
|
177
|
-
if (redisClient.connected) {
|
|
178
|
-
return resolve(redisClient);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
redisClient.on('ready', () => resolve(redisClient));
|
|
182
|
-
redisClient.on('error', (e) => reject(e));
|
|
183
|
-
});
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
*
|
|
188
|
-
* @param {(client: any) => any} fn
|
|
189
|
-
*/
|
|
190
|
-
async processRedisCommands(fn) {
|
|
191
|
-
const client = await this.getClient();
|
|
192
|
-
const result = await fn(client);
|
|
193
|
-
|
|
194
|
-
return result;
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
*
|
|
199
|
-
* @param {Context} context
|
|
200
|
-
* @param {string | RegExp} pattern
|
|
201
|
-
*/
|
|
202
|
-
flushCache(context, pattern) {
|
|
203
|
-
return this.processRedisCommands(async (client) => {
|
|
204
|
-
const scanAsync = util.promisify(client.scan).bind(client);
|
|
205
|
-
const delAsync = util.promisify(client.del).bind(client);
|
|
206
|
-
let cursor = '0';
|
|
207
|
-
do {
|
|
208
|
-
const result = await scanAsync(cursor, 'MATCH', pattern);
|
|
209
|
-
([cursor] = result);
|
|
210
|
-
const keys = result[1];
|
|
211
|
-
context.log.info('matched keys', {
|
|
212
|
-
keys: result[1]
|
|
213
|
-
});
|
|
214
|
-
if (keys.length > 0) {
|
|
215
|
-
await delAsync(...keys);
|
|
216
|
-
}
|
|
217
|
-
} while (cursor !== '0');
|
|
218
|
-
});
|
|
219
|
-
},
|
|
220
|
-
redisClient,
|
|
221
|
-
redisCache
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
health.add(`redis-${instance.id}`, async () => {
|
|
225
|
-
try {
|
|
226
|
-
const controller = new AbortController();
|
|
227
|
-
|
|
228
|
-
const result = await Promise.race([
|
|
229
|
-
instance.healthCheck(),
|
|
230
|
-
waitTimeout(3000, false, { signal: controller.signal, ref: false })
|
|
231
|
-
]);
|
|
232
|
-
|
|
233
|
-
if (!result) {
|
|
234
|
-
throw new Error('Timed out waiting to verify health status');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
controller.abort();
|
|
238
|
-
return { status: 200, message: 'OK' };
|
|
239
|
-
} catch (ex) { // eslint-disable-line no-unused-vars
|
|
240
|
-
return { status: 503, message: 'Error connecting to Redis' };
|
|
241
|
-
}
|
|
242
|
-
}, { promotionTimeout: 60000 });
|
|
243
|
-
|
|
244
|
-
service.registerResource(`exframe-cache-manager-${instance.id}`, { onSignal: () => instance.close(), order: 'last' });
|
|
245
|
-
|
|
246
|
-
Object.keys(instance).forEach((key) => {
|
|
247
|
-
if (typeof instance[key] === 'function') {
|
|
248
|
-
instance[key] = lazyInstrument(instance[key].bind(instance), { metricPrefix: 'cache' });
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
return instance;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// eslint-disable-next-line import/prefer-default-export
|
|
256
|
-
export { cachemanager as create };
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* @typedef {RedisCacheManagerOptions & { store?: any, ttl?: number, db?: number }} Options
|
|
260
|
-
*/
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* @typedef {{ db?: number, url?: string, host?: string, port?: string }} RedisCacheManagerOptions
|
|
264
|
-
*/
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* @typedef {{ [key: string]: any, log: { [key: string]: LogLevel, info: LogLevel, error: LogLevel } }} Context
|
|
268
|
-
*/
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* @typedef {(message: string, meta: { [key: string]: any }) => void} LogLevel
|
|
272
|
-
*/
|
|
5
|
+
module.exports = { create: cachemanager };
|
package/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exframe-cache-manager",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Managing the cache",
|
|
5
|
-
"main": "index.js",
|
|
6
5
|
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.mjs",
|
|
11
|
+
"require": "./index.js",
|
|
12
|
+
"default": "./index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
7
15
|
"config": {
|
|
8
16
|
"reporter": "mocha-exzeo-reporter"
|
|
9
17
|
},
|
|
@@ -47,5 +55,5 @@
|
|
|
47
55
|
"url": "https://bitbucket.org/exzeo-usa/exframe",
|
|
48
56
|
"directory": "packages/exframe-cache-manager"
|
|
49
57
|
},
|
|
50
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "3e1aa0feed298e624741cedcf3195c8725a23e34"
|
|
51
59
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import util from 'node:util';
|
|
4
|
+
import { setTimeout as waitTimeout } from 'node:timers/promises';
|
|
5
|
+
import CacheManager from 'cache-manager';
|
|
6
|
+
import RedisStore from 'cache-manager-redis-store';
|
|
7
|
+
import { lazyInstrument } from 'exframe-metrics';
|
|
8
|
+
import health from 'exframe-health';
|
|
9
|
+
import service from 'exframe-service';
|
|
10
|
+
import { generateShortId } from 'exframe-utilities';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_TTL = 600;
|
|
13
|
+
|
|
14
|
+
const db = 0;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cache manager for storing stuff.
|
|
18
|
+
* Options is required to pass the redis location
|
|
19
|
+
* @param {Options} options
|
|
20
|
+
*/
|
|
21
|
+
function cachemanager(options) {
|
|
22
|
+
if (options.url) {
|
|
23
|
+
options.host = undefined;
|
|
24
|
+
options.port = undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const redisCache = CacheManager.caching({
|
|
28
|
+
store: RedisStore,
|
|
29
|
+
ttl: DEFAULT_TTL,
|
|
30
|
+
db,
|
|
31
|
+
...options
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
const redisStore = redisCache.store;
|
|
36
|
+
const redisClient = redisStore.getClient();
|
|
37
|
+
redisClient.setMaxListeners(0);
|
|
38
|
+
|
|
39
|
+
redisClient.on('error', (e) => {
|
|
40
|
+
console.log('Unhandled Redis Error', { errorDetails: e }); // eslint-disable-line
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
process.on('beforeExit', () => {
|
|
44
|
+
if (redisClient.connected) {
|
|
45
|
+
redisClient.removeAllListeners();
|
|
46
|
+
redisClient.end(true);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const instance = {
|
|
51
|
+
id: generateShortId(),
|
|
52
|
+
get cacheStorePool() {
|
|
53
|
+
return redisClient;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {string} key
|
|
59
|
+
* @param {object} context
|
|
60
|
+
* @param {Options} options
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
async getItem(context, key, { timeout } = {}) {
|
|
64
|
+
if (arguments.length === 1) {
|
|
65
|
+
// context = key, param order was switched in function def to preserve backwards compatability
|
|
66
|
+
return redisCache.get(context);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
if (timeout) {
|
|
71
|
+
const result = await Promise.race([
|
|
72
|
+
redisCache.get(key),
|
|
73
|
+
waitTimeout(timeout, false, { signal: controller.signal, ref: false })
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
if (!result) {
|
|
77
|
+
context.log.info('CACHE TIMEOUT ERROR');
|
|
78
|
+
return Promise.reject(new Error('Timed out attempting to retrieve item from cache.'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
controller.abort();
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
return redisCache.get(key);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* @template T
|
|
90
|
+
* @param {string} key
|
|
91
|
+
* @param {T} value
|
|
92
|
+
* @param {number} [ttl]
|
|
93
|
+
* @returns {Promise<T>}
|
|
94
|
+
*/
|
|
95
|
+
async setItem(key, value, ttl) {
|
|
96
|
+
await redisCache.set(key, value, {
|
|
97
|
+
ttl: ttl || Number(process.env.REDIS_CACHE_TTL)
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return value;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set the item if it does not exist yet. If the item already exists, do not set and return null.
|
|
105
|
+
* @template T
|
|
106
|
+
* @param {String} key
|
|
107
|
+
* @param {T} value
|
|
108
|
+
* @param {Number} ttl
|
|
109
|
+
* @returns {Promise<T|null>}
|
|
110
|
+
*/
|
|
111
|
+
async setItemIfNotExists(key, value, ttl) {
|
|
112
|
+
const ttlToSet = ttl ?? (process.env.REDIS_CACHE_TTL
|
|
113
|
+
? Number(process.env.REDIS_CACHE_TTL)
|
|
114
|
+
: DEFAULT_TTL);
|
|
115
|
+
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
redisClient.set(key, value, 'NX', 'EX', ttlToSet, (error, result) => {
|
|
118
|
+
if (error) {
|
|
119
|
+
return reject(error);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
resolve(result === 'OK' ? value : null);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
*
|
|
129
|
+
* @param {string} key
|
|
130
|
+
* @param {any} value
|
|
131
|
+
*/
|
|
132
|
+
addSetItem(key, value) {
|
|
133
|
+
return this.processRedisCommands(client => client.sadd(key, value));
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
* @param {string} key
|
|
139
|
+
* @param {any} value
|
|
140
|
+
*/
|
|
141
|
+
removeSetItem(key, value) {
|
|
142
|
+
return this.processRedisCommands(client => client.srem(key, value));
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
*
|
|
147
|
+
* @param {string} key
|
|
148
|
+
* @param {any} value
|
|
149
|
+
* @returns {Promise<Boolean>}
|
|
150
|
+
*/
|
|
151
|
+
isSetMember(key, value) {
|
|
152
|
+
return this.processRedisCommands(async client => new Promise((res, rej) => {
|
|
153
|
+
client.sismember(key, value, (ex, result) => {
|
|
154
|
+
if (ex) return rej(ex);
|
|
155
|
+
res(result === 1);
|
|
156
|
+
});
|
|
157
|
+
}));
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @returns {Promise<true>}
|
|
162
|
+
*/
|
|
163
|
+
async healthCheck() {
|
|
164
|
+
return this.processRedisCommands(client => client && client.server_info.loading === '0');
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
close() {
|
|
168
|
+
return new Promise(res => {
|
|
169
|
+
redisClient.removeAllListeners();
|
|
170
|
+
redisClient.end(true);
|
|
171
|
+
res();
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
getClient() {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
if (redisClient.connected) {
|
|
178
|
+
return resolve(redisClient);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
redisClient.on('ready', () => resolve(redisClient));
|
|
182
|
+
redisClient.on('error', (e) => reject(e));
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
*
|
|
188
|
+
* @param {(client: any) => any} fn
|
|
189
|
+
*/
|
|
190
|
+
async processRedisCommands(fn) {
|
|
191
|
+
const client = await this.getClient();
|
|
192
|
+
const result = await fn(client);
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
*
|
|
199
|
+
* @param {Context} context
|
|
200
|
+
* @param {string | RegExp} pattern
|
|
201
|
+
*/
|
|
202
|
+
flushCache(context, pattern) {
|
|
203
|
+
return this.processRedisCommands(async (client) => {
|
|
204
|
+
const scanAsync = util.promisify(client.scan).bind(client);
|
|
205
|
+
const delAsync = util.promisify(client.del).bind(client);
|
|
206
|
+
let cursor = '0';
|
|
207
|
+
do {
|
|
208
|
+
const result = await scanAsync(cursor, 'MATCH', pattern);
|
|
209
|
+
([cursor] = result);
|
|
210
|
+
const keys = result[1];
|
|
211
|
+
context.log.info('matched keys', {
|
|
212
|
+
keys: result[1]
|
|
213
|
+
});
|
|
214
|
+
if (keys.length > 0) {
|
|
215
|
+
await delAsync(...keys);
|
|
216
|
+
}
|
|
217
|
+
} while (cursor !== '0');
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
redisClient,
|
|
221
|
+
redisCache
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
health.add(`redis-${instance.id}`, async () => {
|
|
225
|
+
try {
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
|
|
228
|
+
const result = await Promise.race([
|
|
229
|
+
instance.healthCheck(),
|
|
230
|
+
waitTimeout(3000, false, { signal: controller.signal, ref: false })
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
if (!result) {
|
|
234
|
+
throw new Error('Timed out waiting to verify health status');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
controller.abort();
|
|
238
|
+
return { status: 200, message: 'OK' };
|
|
239
|
+
} catch (ex) { // eslint-disable-line no-unused-vars
|
|
240
|
+
return { status: 503, message: 'Error connecting to Redis' };
|
|
241
|
+
}
|
|
242
|
+
}, { promotionTimeout: 60000 });
|
|
243
|
+
|
|
244
|
+
service.registerResource(`exframe-cache-manager-${instance.id}`, { onSignal: () => instance.close(), order: 'last' });
|
|
245
|
+
|
|
246
|
+
Object.keys(instance).forEach((key) => {
|
|
247
|
+
if (typeof instance[key] === 'function') {
|
|
248
|
+
instance[key] = lazyInstrument(instance[key].bind(instance), { metricPrefix: 'cache' });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return instance;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
256
|
+
export { cachemanager };
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @typedef {RedisCacheManagerOptions & { store?: any, ttl?: number, db?: number }} Options
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @typedef {{ db?: number, url?: string, host?: string, port?: string }} RedisCacheManagerOptions
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @typedef {{ [key: string]: any, log: { [key: string]: LogLevel, info: LogLevel, error: LogLevel } }} Context
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @typedef {(message: string, meta: { [key: string]: any }) => void} LogLevel
|
|
272
|
+
*/
|
|
@@ -6,7 +6,7 @@ import service from 'exframe-service';
|
|
|
6
6
|
import health from 'exframe-health';
|
|
7
7
|
import { setTimeout as waitTimeout } from 'node:timers/promises';
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import cacheManager from '../index.mjs';
|
|
10
10
|
|
|
11
11
|
const { prometheusClient } = service;
|
|
12
12
|
const userId = 'auth0|1234567890';
|
|
@@ -36,7 +36,7 @@ context('Test User Profile Middleware', () => {
|
|
|
36
36
|
it('adds a health check with the instance ID in the name, check function, and liveness timeout of 60 seconds', () => {
|
|
37
37
|
const healthAddSpy = sinonInstance.spy(health, 'add');
|
|
38
38
|
|
|
39
|
-
const instance = create(options);
|
|
39
|
+
const instance = cacheManager.create(options);
|
|
40
40
|
expect(healthAddSpy.calledOnce).to.equal(true);
|
|
41
41
|
expect(healthAddSpy.calledWithMatch(`redis-${instance.id}`, match.func, { promotionTimeout: 60000 })).to.be.true;
|
|
42
42
|
});
|
|
@@ -44,7 +44,7 @@ context('Test User Profile Middleware', () => {
|
|
|
44
44
|
it('registers a service resource with the instance ID in the name, onSignal function, and order of "last"', () => {
|
|
45
45
|
const registerSpy = sinonInstance.spy(service, 'registerResource');
|
|
46
46
|
|
|
47
|
-
const instance = create(options);
|
|
47
|
+
const instance = cacheManager.create(options);
|
|
48
48
|
expect(registerSpy.calledOnce).to.equal(true);
|
|
49
49
|
expect(registerSpy.calledWithMatch(`exframe-cache-manager-${instance.id}`, match({
|
|
50
50
|
onSignal: match.func,
|
|
@@ -54,7 +54,7 @@ context('Test User Profile Middleware', () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
describe('instance', () => {
|
|
57
|
-
const app = create(options);
|
|
57
|
+
const app = cacheManager.create(options);
|
|
58
58
|
|
|
59
59
|
beforeEach(() => {
|
|
60
60
|
prometheusClient.register.clear();
|
|
@@ -178,7 +178,7 @@ context('Test User Profile Middleware', () => {
|
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
it('close drains the redis pool', async () => {
|
|
181
|
-
const app1 = create(options);
|
|
181
|
+
const app1 = cacheManager.create(options);
|
|
182
182
|
await app1.setItem(cacheKey, 'someValue');
|
|
183
183
|
const value = await app1.getItem(cacheKey);
|
|
184
184
|
expect(value).to.eql('someValue');
|