exframe-cache-manager 1.3.2 → 2.0.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/.eslintrc.json CHANGED
@@ -1,17 +1,3 @@
1
1
  {
2
- "env": {
3
- "es6": true,
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
@@ -7,12 +7,11 @@ RUN apk update && \
7
7
  addgroup -S docker && adduser -S -G docker docker
8
8
 
9
9
  COPY ./package.json /app/package.json
10
- WORKDIR /app
11
- RUN npm install && \
12
- npm run pretest
13
- npm cache clean --force
14
-
15
10
  COPY . /app
16
11
 
12
+ WORKDIR /app
13
+ RUN yarn install
14
+ RUN npm run peers
15
+
17
16
  # Override the command, to run the test instead of the application
18
17
  CMD ["npm", "run", "unit-test"]
package/index.js CHANGED
@@ -1,208 +1,196 @@
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
- // eslint-disable-next-line no-await-in-loop
165
- const result = await scanAsync(cursor, 'MATCH', pattern);
166
- ([cursor] = result);
167
- const keys = result[1];
168
- context.log.info('matched keys', {
169
- keys: result[1]
170
- });
171
- if (keys.length > 0) {
172
- // eslint-disable-next-line no-await-in-loop
173
- await delAsync(...keys);
174
- }
175
- } while (cursor !== '0');
176
- });
177
- },
178
- cacheStorePool
179
- };
180
-
181
- Object.keys(instance).forEach((key) => {
182
- if (typeof instance[key] === 'function') {
183
- instance[key] = lazyInstrument(instance[key].bind(instance), { metricPrefix: 'cache' });
184
- }
185
- });
186
-
187
- return instance;
188
- }
189
-
190
- module.exports = {
191
- create: cachemanager
192
- };
193
-
194
- /**
195
- * @typedef {RedisCacheManagerOptions & { store?: any, ttl?: number, db?: number }} Options
196
- */
197
-
198
- /**
199
- * @typedef {{ db?: number, url?: string, host?: string, port?: string }} RedisCacheManagerOptions
200
- */
201
-
202
- /**
203
- * @typedef {{ [key: string]: any, log: { [key: string]: LogLevel, info: LogLevel, error: LogLevel } }} Context
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-store');
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
+ const redisClient = redisStore.getClient();
31
+ redisClient.setMaxListeners(0);
32
+
33
+ redisClient.on('error', (e) => {
34
+ console.log('Unhandled Redis Error', { errorDetails: e }); // eslint-disable-line
35
+ });
36
+
37
+ process.on('beforeExit', () => {
38
+ if (redisClient.connected) {
39
+ redisClient.removeAllListeners();
40
+ redisClient.end(true);
41
+ }
42
+ });
43
+
44
+ const instance = {
45
+ get cacheStorePool() {
46
+ return redisClient;
47
+ },
48
+
49
+ /**
50
+ *
51
+ * @param {string} key
52
+ */
53
+ getItem(key) {
54
+ return redisCache.get(key);
55
+ },
56
+
57
+ /**
58
+ *
59
+ * @template T
60
+ * @param {string} key
61
+ * @param {T} value
62
+ * @param {number} [ttl]
63
+ * @returns {Promise<T>}
64
+ */
65
+ async setItem(key, value, ttl) {
66
+ await redisCache.set(key, value, {
67
+ ttl: ttl || Number(process.env.REDIS_CACHE_TTL)
68
+ });
69
+
70
+ return value;
71
+ },
72
+
73
+ /**
74
+ *
75
+ * @param {string} key
76
+ * @param {any} value
77
+ */
78
+ addSetItem(key, value) {
79
+ return this.processRedisCommands(client => client.sadd(key, value));
80
+ },
81
+
82
+ /**
83
+ *
84
+ * @param {string} key
85
+ * @param {any} value
86
+ */
87
+ removeSetItem(key, value) {
88
+ return this.processRedisCommands(client => client.srem(key, value));
89
+ },
90
+
91
+ /**
92
+ *
93
+ * @param {string} key
94
+ * @param {any} value
95
+ * @returns {Promise<Boolean>}
96
+ */
97
+ isSetMember(key, value) {
98
+ return this.processRedisCommands(async client => new Promise((res, rej) => {
99
+ client.sismember(key, value, (ex, result) => {
100
+ if (ex) return rej(ex);
101
+ res(result === 1);
102
+ });
103
+ }));
104
+ },
105
+
106
+ /**
107
+ * @returns {Promise<true>}
108
+ */
109
+ async healthCheck() {
110
+ return this.processRedisCommands(client => client && client.server_info.loading === '0');
111
+ },
112
+
113
+ close() {
114
+ return new Promise(res => {
115
+ redisClient.removeAllListeners();
116
+ redisClient.end(true);
117
+ res();
118
+ });
119
+ },
120
+
121
+ getClient() {
122
+ return new Promise((resolve, reject) => {
123
+ if (redisClient.connected) {
124
+ return resolve(redisClient);
125
+ }
126
+
127
+ redisClient.on('ready', () => resolve(redisClient));
128
+ redisClient.on('error', (e) => reject(e));
129
+ });
130
+ },
131
+
132
+ /**
133
+ *
134
+ * @param {(client: any) => any} fn
135
+ */
136
+ async processRedisCommands(fn) {
137
+ const client = await this.getClient();
138
+ const result = await fn(client);
139
+
140
+ return result;
141
+ },
142
+
143
+ /**
144
+ *
145
+ * @param {Context} context
146
+ * @param {string | RegExp} pattern
147
+ */
148
+ flushCache(context, pattern) {
149
+ return this.processRedisCommands(async (client) => {
150
+ const scanAsync = util.promisify(client.scan).bind(client);
151
+ const delAsync = util.promisify(client.del).bind(client);
152
+ let cursor = '0';
153
+ do {
154
+ const result = await scanAsync(cursor, 'MATCH', pattern);
155
+ ([cursor] = result);
156
+ const keys = result[1];
157
+ context.log.info('matched keys', {
158
+ keys: result[1]
159
+ });
160
+ if (keys.length > 0) {
161
+ await delAsync(...keys);
162
+ }
163
+ } while (cursor !== '0');
164
+ });
165
+ },
166
+ redisClient
167
+ };
168
+
169
+ Object.keys(instance).forEach((key) => {
170
+ if (typeof instance[key] === 'function') {
171
+ instance[key] = lazyInstrument(instance[key].bind(instance), { metricPrefix: 'cache' });
172
+ }
173
+ });
174
+
175
+ return instance;
176
+ }
177
+
178
+ module.exports = {
179
+ create: cachemanager
180
+ };
181
+
182
+ /**
183
+ * @typedef {RedisCacheManagerOptions & { store?: any, ttl?: number, db?: number }} Options
184
+ */
185
+
186
+ /**
187
+ * @typedef {{ db?: number, url?: string, host?: string, port?: string }} RedisCacheManagerOptions
188
+ */
189
+
190
+ /**
191
+ * @typedef {{ [key: string]: any, log: { [key: string]: LogLevel, info: LogLevel, error: LogLevel } }} Context
192
+ */
193
+
194
+ /**
195
+ * @typedef {(message: string, meta: { [key: string]: any }) => void} LogLevel
196
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exframe-cache-manager",
3
- "version": "1.3.2",
3
+ "version": "2.0.1",
4
4
  "description": "Managing the cache",
5
5
  "main": "index.js",
6
6
  "config": {
@@ -11,9 +11,10 @@
11
11
  },
12
12
  "scripts": {
13
13
  "pretest": "npm-install-peers && npm run lint",
14
- "lint": "./node_modules/.bin/eslint .",
14
+ "lint": "eslint ./",
15
+ "peers": "npm-install-peers",
15
16
  "test": "docker-compose -f docker-compose.yml up --abort-on-container-exit --exit-code-from exframe-cache-manager",
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
+ "unit-test": "./node_modules/.bin/nyc -r lcov --report-dir ./documentation/coverage -t ./documentation/coverage mocha --exit --reporter $npm_package_config_reporter \"./test/**/*.test.js\""
17
18
  },
18
19
  "author": "Exzeo",
19
20
  "license": "ISC",
@@ -24,26 +25,25 @@
24
25
  },
25
26
  "dependencies": {
26
27
  "@types/cache-manager": "^3.4.2",
27
- "@types/redis": "^2.8.32",
28
28
  "cache-manager": "^3.6.0",
29
- "cache-manager-redis": "^0.6.0",
30
- "exframe-metrics": "^1.1.0",
31
- "redis": "^3.1.2"
29
+ "cache-manager-redis-store": "^2.0.0",
30
+ "exframe-metrics": "^1.1.0"
32
31
  },
33
32
  "devDependencies": {
34
- "chai": "^4.3.4",
35
- "eslint": "^8.3.0",
36
- "eslint-config-airbnb-base": "^15.0.0",
37
- "eslint-plugin-import": "^2.25.3",
33
+ "chai": "*",
34
+ "eslint": "*",
35
+ "eslint-config-exzeo": "*",
36
+ "eslint-plugin-import": "*",
38
37
  "mocha": "*",
39
- "mocha-exzeo-reporter": "0.0.3",
38
+ "mocha-exzeo-reporter": "*",
40
39
  "npm-install-peers": "*",
41
- "nyc": "*"
40
+ "nyc": "*",
41
+ "sinon": "*"
42
42
  },
43
43
  "repository": {
44
44
  "type": "git",
45
45
  "url": "https://bitbucket.org/exzeo-usa/exframe",
46
46
  "directory": "packages/exframe-cache-manager"
47
47
  },
48
- "gitHead": "56ff8bc05dfaf32745361c9685fe276ddc5f8478"
48
+ "gitHead": "a4d35e7c49768aee0a7e487f0bf7ec0a88a3f33b"
49
49
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "exzeo/mocha"
3
+ }
@@ -105,7 +105,7 @@ context('Test User Profile Middleware', () => {
105
105
  const result = await app1.getItem(cacheKey);
106
106
  expect(result).to.not.be.ok();
107
107
  } catch (ex) {
108
- expect(ex.message).to.be.equal('pool is draining and cannot accept work');
108
+ expect(ex.message).to.be.equal('GET can\'t be processed. The connection is already closed.');
109
109
  const metrics = prometheusClient.register.getMetricsAsArray().filter((m) => m.name.includes('close'));
110
110
  expect(metrics).to.be.an('array').and.to.have.lengthOf.above(0);
111
111
  }
@@ -122,7 +122,6 @@ context('Test User Profile Middleware', () => {
122
122
  it('validate the cacheStorePool', () => {
123
123
  const catchepool = app.cacheStorePool;
124
124
  expect(app).to.not.eql(null);
125
- expect(catchepool.db).to.eql(0);
126
125
  expect(catchepool._maxListeners).to.eql(0);
127
126
  });
128
127