exframe-cache-manager 1.3.2 → 1.3.3
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/.eslintrc.json +2 -16
- package/Dockerfile +0 -1
- package/index.js +206 -208
- package/package.json +4 -4
- package/test/.eslintrc.json +3 -0
package/.eslintrc.json
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
"node": true
|
|
5
|
-
},
|
|
6
|
-
"extends": "airbnb-base",
|
|
7
|
-
"rules": {
|
|
8
|
-
"comma-dangle": ["error", "never"],
|
|
9
|
-
"strict": ["off", "global"] ,
|
|
10
|
-
"consistent-return": "off",
|
|
11
|
-
"linebreak-style": ["error", "windows"],
|
|
12
|
-
"max-len": ["error", 200],
|
|
13
|
-
"no-param-reassign": ["error", { "props": false }],
|
|
14
|
-
"no-underscore-dangle": "off",
|
|
15
|
-
"arrow-parens": "off"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
2
|
+
"extends": "exzeo/base"
|
|
3
|
+
}
|
package/Dockerfile
CHANGED
package/index.js
CHANGED
|
@@ -1,208 +1,206 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const CacheManager = require('cache-manager');
|
|
4
|
-
const RedisStore = require('cache-manager-redis');
|
|
5
|
-
const util = require('util');
|
|
6
|
-
const { lazyInstrument } = require('exframe-metrics');
|
|
7
|
-
|
|
8
|
-
const db = 0;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Cache manager for storing stuff.
|
|
12
|
-
* Options is required to pass the redis location
|
|
13
|
-
* @param {Options} options
|
|
14
|
-
*/
|
|
15
|
-
function cachemanager(options) {
|
|
16
|
-
if (options.url) {
|
|
17
|
-
options.host = undefined;
|
|
18
|
-
options.port = undefined;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const redisCache = CacheManager.caching({
|
|
22
|
-
store: RedisStore,
|
|
23
|
-
ttl: 600,
|
|
24
|
-
db,
|
|
25
|
-
...options
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
const redisStore = redisCache.store;
|
|
30
|
-
|
|
31
|
-
// need to expose the store's connection pool after it's created by the cache manager as it does not seem to have a way to pass it in
|
|
32
|
-
// if we use something besides redis, we'll have to tackle this differently
|
|
33
|
-
const cacheStorePool = Object.assign(redisStore._pool, { db });
|
|
34
|
-
cacheStorePool.setMaxListeners(0);
|
|
35
|
-
|
|
36
|
-
const instance = {
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
* @param {string} key
|
|
40
|
-
*/
|
|
41
|
-
getItem(key) {
|
|
42
|
-
return redisCache.get(key);
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
*
|
|
47
|
-
* @template T
|
|
48
|
-
* @param {string} key
|
|
49
|
-
* @param {T} value
|
|
50
|
-
* @param {number} [ttl]
|
|
51
|
-
* @returns {Promise<T>}
|
|
52
|
-
*/
|
|
53
|
-
async setItem(key, value, ttl) {
|
|
54
|
-
await redisCache.set(key, value, {
|
|
55
|
-
ttl: ttl || Number(process.env.REDIS_CACHE_TTL)
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return value;
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
*
|
|
63
|
-
* @param {string} key
|
|
64
|
-
* @param {any} value
|
|
65
|
-
*/
|
|
66
|
-
addSetItem(key, value) {
|
|
67
|
-
return this.processRedisCommands(client => client.sadd(key, value));
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
* @param {string} key
|
|
73
|
-
* @param {any} value
|
|
74
|
-
*/
|
|
75
|
-
removeSetItem(key, value) {
|
|
76
|
-
return this.processRedisCommands(client => client.srem(key, value));
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
*
|
|
81
|
-
* @param {string} key
|
|
82
|
-
* @param {any} value
|
|
83
|
-
* @returns {Promise<Boolean>}
|
|
84
|
-
*/
|
|
85
|
-
isSetMember(key, value) {
|
|
86
|
-
return this.processRedisCommands(async client => new Promise((res, rej) => {
|
|
87
|
-
client.sismember(key, value, (ex, result) => {
|
|
88
|
-
if (ex) return rej(ex);
|
|
89
|
-
res(result === 1);
|
|
90
|
-
});
|
|
91
|
-
}));
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* @returns {Promise<true>}
|
|
96
|
-
*/
|
|
97
|
-
async healthCheck() {
|
|
98
|
-
return this.processRedisCommands(client => client && client.server_info.loading === '0');
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
close() {
|
|
102
|
-
return new Promise(res => {
|
|
103
|
-
cacheStorePool.drain(res);
|
|
104
|
-
});
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
getClient() {
|
|
108
|
-
return new Promise((resolve, reject) => {
|
|
109
|
-
cacheStorePool.acquireDb(async (connError, client) => {
|
|
110
|
-
let timeout;
|
|
111
|
-
|
|
112
|
-
if (connError) {
|
|
113
|
-
return reject(connError);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (client.connected) {
|
|
117
|
-
return resolve(client);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
client.once('ready', () => {
|
|
121
|
-
clearTimeout(timeout);
|
|
122
|
-
resolve(client);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
timeout = setTimeout(() => {
|
|
126
|
-
cacheStorePool.release(client);
|
|
127
|
-
reject(new Error('Timeout waiting for redis client connection'));
|
|
128
|
-
}, 10000);
|
|
129
|
-
}, db);
|
|
130
|
-
});
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
*
|
|
135
|
-
* @param {(client: any) => any} fn
|
|
136
|
-
*/
|
|
137
|
-
async processRedisCommands(fn) {
|
|
138
|
-
let client;
|
|
139
|
-
let result;
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
client = await this.getClient();
|
|
143
|
-
result = await fn(client);
|
|
144
|
-
} finally {
|
|
145
|
-
if (client) {
|
|
146
|
-
cacheStorePool.release(client);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return result;
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
*
|
|
155
|
-
* @param {Context} context
|
|
156
|
-
* @param {string | RegExp} pattern
|
|
157
|
-
*/
|
|
158
|
-
flushCache(context, pattern) {
|
|
159
|
-
return this.processRedisCommands(async (client) => {
|
|
160
|
-
const scanAsync = util.promisify(client.scan).bind(client);
|
|
161
|
-
const delAsync = util.promisify(client.del).bind(client);
|
|
162
|
-
let cursor = '0';
|
|
163
|
-
do {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* @typedef {(message: string, meta: { [key: string]: any }) => void} LogLevel
|
|
208
|
-
*/
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CacheManager = require('cache-manager');
|
|
4
|
+
const RedisStore = require('cache-manager-redis');
|
|
5
|
+
const util = require('util');
|
|
6
|
+
const { lazyInstrument } = require('exframe-metrics');
|
|
7
|
+
|
|
8
|
+
const db = 0;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cache manager for storing stuff.
|
|
12
|
+
* Options is required to pass the redis location
|
|
13
|
+
* @param {Options} options
|
|
14
|
+
*/
|
|
15
|
+
function cachemanager(options) {
|
|
16
|
+
if (options.url) {
|
|
17
|
+
options.host = undefined;
|
|
18
|
+
options.port = undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const redisCache = CacheManager.caching({
|
|
22
|
+
store: RedisStore,
|
|
23
|
+
ttl: 600,
|
|
24
|
+
db,
|
|
25
|
+
...options
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
const redisStore = redisCache.store;
|
|
30
|
+
|
|
31
|
+
// need to expose the store's connection pool after it's created by the cache manager as it does not seem to have a way to pass it in
|
|
32
|
+
// if we use something besides redis, we'll have to tackle this differently
|
|
33
|
+
const cacheStorePool = Object.assign(redisStore._pool, { db });
|
|
34
|
+
cacheStorePool.setMaxListeners(0);
|
|
35
|
+
|
|
36
|
+
const instance = {
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param {string} key
|
|
40
|
+
*/
|
|
41
|
+
getItem(key) {
|
|
42
|
+
return redisCache.get(key);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @template T
|
|
48
|
+
* @param {string} key
|
|
49
|
+
* @param {T} value
|
|
50
|
+
* @param {number} [ttl]
|
|
51
|
+
* @returns {Promise<T>}
|
|
52
|
+
*/
|
|
53
|
+
async setItem(key, value, ttl) {
|
|
54
|
+
await redisCache.set(key, value, {
|
|
55
|
+
ttl: ttl || Number(process.env.REDIS_CACHE_TTL)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return value;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @param {string} key
|
|
64
|
+
* @param {any} value
|
|
65
|
+
*/
|
|
66
|
+
addSetItem(key, value) {
|
|
67
|
+
return this.processRedisCommands(client => client.sadd(key, value));
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param {string} key
|
|
73
|
+
* @param {any} value
|
|
74
|
+
*/
|
|
75
|
+
removeSetItem(key, value) {
|
|
76
|
+
return this.processRedisCommands(client => client.srem(key, value));
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
* @param {string} key
|
|
82
|
+
* @param {any} value
|
|
83
|
+
* @returns {Promise<Boolean>}
|
|
84
|
+
*/
|
|
85
|
+
isSetMember(key, value) {
|
|
86
|
+
return this.processRedisCommands(async client => new Promise((res, rej) => {
|
|
87
|
+
client.sismember(key, value, (ex, result) => {
|
|
88
|
+
if (ex) return rej(ex);
|
|
89
|
+
res(result === 1);
|
|
90
|
+
});
|
|
91
|
+
}));
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @returns {Promise<true>}
|
|
96
|
+
*/
|
|
97
|
+
async healthCheck() {
|
|
98
|
+
return this.processRedisCommands(client => client && client.server_info.loading === '0');
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
close() {
|
|
102
|
+
return new Promise(res => {
|
|
103
|
+
cacheStorePool.drain(res);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
getClient() {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
cacheStorePool.acquireDb(async (connError, client) => {
|
|
110
|
+
let timeout;
|
|
111
|
+
|
|
112
|
+
if (connError) {
|
|
113
|
+
return reject(connError);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (client.connected) {
|
|
117
|
+
return resolve(client);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
client.once('ready', () => {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
resolve(client);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
timeout = setTimeout(() => {
|
|
126
|
+
cacheStorePool.release(client);
|
|
127
|
+
reject(new Error('Timeout waiting for redis client connection'));
|
|
128
|
+
}, 10000);
|
|
129
|
+
}, db);
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
*
|
|
135
|
+
* @param {(client: any) => any} fn
|
|
136
|
+
*/
|
|
137
|
+
async processRedisCommands(fn) {
|
|
138
|
+
let client;
|
|
139
|
+
let result;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
client = await this.getClient();
|
|
143
|
+
result = await fn(client);
|
|
144
|
+
} finally {
|
|
145
|
+
if (client) {
|
|
146
|
+
cacheStorePool.release(client);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
*
|
|
155
|
+
* @param {Context} context
|
|
156
|
+
* @param {string | RegExp} pattern
|
|
157
|
+
*/
|
|
158
|
+
flushCache(context, pattern) {
|
|
159
|
+
return this.processRedisCommands(async (client) => {
|
|
160
|
+
const scanAsync = util.promisify(client.scan).bind(client);
|
|
161
|
+
const delAsync = util.promisify(client.del).bind(client);
|
|
162
|
+
let cursor = '0';
|
|
163
|
+
do {
|
|
164
|
+
const result = await scanAsync(cursor, 'MATCH', pattern);
|
|
165
|
+
([cursor] = result);
|
|
166
|
+
const keys = result[1];
|
|
167
|
+
context.log.info('matched keys', {
|
|
168
|
+
keys: result[1]
|
|
169
|
+
});
|
|
170
|
+
if (keys.length > 0) {
|
|
171
|
+
await delAsync(...keys);
|
|
172
|
+
}
|
|
173
|
+
} while (cursor !== '0');
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
cacheStorePool
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
Object.keys(instance).forEach((key) => {
|
|
180
|
+
if (typeof instance[key] === 'function') {
|
|
181
|
+
instance[key] = lazyInstrument(instance[key].bind(instance), { metricPrefix: 'cache' });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return instance;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
create: cachemanager
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @typedef {RedisCacheManagerOptions & { store?: any, ttl?: number, db?: number }} Options
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @typedef {{ db?: number, url?: string, host?: string, port?: string }} RedisCacheManagerOptions
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @typedef {{ [key: string]: any, log: { [key: string]: LogLevel, info: LogLevel, error: LogLevel } }} Context
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @typedef {(message: string, meta: { [key: string]: any }) => void} LogLevel
|
|
206
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exframe-cache-manager",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Managing the cache",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"config": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"pretest": "npm-install-peers && npm run lint",
|
|
14
|
-
"lint": "
|
|
14
|
+
"lint": "eslint ./",
|
|
15
15
|
"test": "docker-compose -f docker-compose.yml up --abort-on-container-exit --exit-code-from exframe-cache-manager",
|
|
16
16
|
"unit-test": "./node_modules/.bin/nyc -r lcov --report-dir ./documentation/coverage -t ./documentation/coverage mocha --reporter $npm_package_config_reporter \"./test/**/*.test.js\""
|
|
17
17
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"chai": "^4.3.4",
|
|
35
35
|
"eslint": "^8.3.0",
|
|
36
|
-
"eslint-config-
|
|
36
|
+
"eslint-config-exzeo": "*",
|
|
37
37
|
"eslint-plugin-import": "^2.25.3",
|
|
38
38
|
"mocha": "*",
|
|
39
39
|
"mocha-exzeo-reporter": "0.0.3",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"url": "https://bitbucket.org/exzeo-usa/exframe",
|
|
46
46
|
"directory": "packages/exframe-cache-manager"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "ef87c842aa08c54cbcb03ee075db8463ebc8fd43"
|
|
49
49
|
}
|