descript-redis-cache 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
package/.editorconfig ADDED
@@ -0,0 +1,19 @@
1
+ # top-most EditorConfig file
2
+ root = true
3
+
4
+ # Unix-style newlines with a newline ending every file
5
+ [*]
6
+ charset = utf-8
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+ indent_style = space
11
+ indent_size = 4
12
+
13
+ # Data files
14
+ [*.{json, yml}]
15
+ indent_size = 2
16
+
17
+ # Makefiles
18
+ [*{M,m}akefile]
19
+ indent_style = tab
package/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ .git/
2
+ .npm/
3
+ node_modules/
package/.eslintrc.js ADDED
@@ -0,0 +1,23 @@
1
+ module.exports = {
2
+ 'env': {
3
+ 'es6': true,
4
+ 'node': true,
5
+ },
6
+ 'extends': [
7
+ 'eslint:recommended',
8
+ ],
9
+ 'rules': {
10
+ 'array-bracket-spacing': [ 'error', 'always' ],
11
+ 'comma-dangle': [ 'error', 'always-multiline' ],
12
+ 'eol-last': 'error',
13
+ 'indent': [ 'error', 4 ],
14
+ 'linebreak-style': [ 'error', 'unix' ],
15
+ 'no-empty': [ 'error', { 'allowEmptyCatch': true } ],
16
+ 'no-multiple-empty-lines': [ 'error', { 'max': 1, 'maxBOF': 0 } ],
17
+ 'no-var': [ 'error' ],
18
+ 'object-curly-spacing': [ 'error', 'always' ],
19
+ 'quotes': [ 'error', 'single' ],
20
+ 'semi': [ 'error', 'always' ],
21
+ 'space-infix-ops': 'error',
22
+ },
23
+ };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Boris Zhidkov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # descript-redis-cache
2
+ Plugin to use Redis as a cache for responses in Descript
3
+
4
+ ## Usage
5
+
6
+ ```js
7
+ const de = require('descript');
8
+ const deRedisCache = require('descript-redis-cache');
9
+
10
+ const context = new de.Context(req, res, {
11
+ cache: new deMemcached(myCacheConfig, logger)
12
+ });
13
+ ```
14
+
15
+ ## Config
16
+
17
+ ```js
18
+ {
19
+ defaultKeyTTL: 60 * 60 * 24, // key ttl in seconds
20
+ generation: 1, // increment generation to invalidate all key across breaking changes releases
21
+ readTimeout: 100, // read timeout in milliseconds,
22
+ redisOptions: {}, // @see https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options
23
+ }
24
+ ```
25
+
26
+ ## Logger
27
+
28
+ Optionally you can pass logger object in the constructor. It should implement standard `Console` methods.
package/index.js ADDED
@@ -0,0 +1,280 @@
1
+ 'use strict';
2
+
3
+ const contimer = require('contimer');
4
+ const crypto = require('crypto');
5
+ const de = require('descript');
6
+ const Redis = require('ioredis');
7
+
8
+ class DescriptRedisCache {
9
+
10
+ constructor(options, logger) {
11
+ this._options = Object.assign({
12
+ defaultKeyTTL: 60 * 60 * 24, // key ttl in seconds
13
+ generation: 1, // increment generation to invalidate all key across breaking changes releases
14
+ readTimeout: 100, // read timeout in milliseconds,
15
+ redisOptions: {},
16
+ }, options);
17
+
18
+ this._logger = logger;
19
+
20
+ this._client = new Redis(options.redisOptions);
21
+
22
+ this._log({
23
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_INITIALIZED,
24
+ options: options,
25
+ });
26
+ }
27
+
28
+ getClient() {
29
+ return this._client;
30
+ }
31
+
32
+ get({ key, context }) {
33
+ const normalizedKey = this.normalizeKey(key);
34
+
35
+ return new Promise((resolve, reject) => {
36
+ this._log({
37
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_READ_START,
38
+ key,
39
+ normalizedKey,
40
+ }, context);
41
+
42
+ const networkTimerStop = contimer.start({}, 'descript-redis-cache.get.network');
43
+ const totalTimerStop = contimer.start({}, 'descript-redis-cache.get.total');
44
+
45
+ let isTimeout = false;
46
+
47
+ const timer = setTimeout(() => {
48
+ isTimeout = true;
49
+
50
+ const networkTimer = networkTimerStop();
51
+ const totalTimer = totalTimerStop();
52
+
53
+ this._log({
54
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_READ_TIMEOUT,
55
+ key,
56
+ normalizedKey,
57
+ timers: {
58
+ network: networkTimer,
59
+ total: totalTimer,
60
+ },
61
+ }, context);
62
+
63
+ reject(de.error({
64
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_READ_TIMEOUT,
65
+ }));
66
+ }, this._options.readTimeout);
67
+
68
+ this._client.get(normalizedKey, (error, data) => {
69
+ if (isTimeout) {
70
+ return;
71
+ }
72
+
73
+ const networkTimer = networkTimerStop();
74
+ clearTimeout(timer);
75
+
76
+ if (error) {
77
+ const totalTimer = totalTimerStop();
78
+ this._log({
79
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_READ_ERROR,
80
+ error,
81
+ key,
82
+ normalizedKey,
83
+ timers: {
84
+ network: networkTimer,
85
+ total: totalTimer,
86
+ },
87
+ }, context);
88
+
89
+ reject(de.error({
90
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_READ_ERROR,
91
+ }));
92
+ } else if (!data) {
93
+ const totalTimer = totalTimerStop();
94
+ this._log({
95
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_READ_KEY_NOT_FOUND,
96
+ key,
97
+ normalizedKey,
98
+ timers: {
99
+ network: networkTimer,
100
+ total: totalTimer,
101
+ },
102
+ }, context);
103
+
104
+ reject(de.error({
105
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_READ_KEY_NOT_FOUND,
106
+ }));
107
+ } else {
108
+ let parsedValue;
109
+ try {
110
+ parsedValue = JSON.parse(data);
111
+ } catch (error) {
112
+ const totalTimer = totalTimerStop();
113
+ this._log({
114
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_JSON_PARSING_FAILED,
115
+ data,
116
+ error,
117
+ key,
118
+ normalizedKey,
119
+ timers: {
120
+ network: networkTimer,
121
+ total: totalTimer,
122
+ },
123
+ }, context);
124
+
125
+ reject(de.error({
126
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_JSON_PARSING_FAILED,
127
+ }));
128
+ return;
129
+ }
130
+
131
+ const totalTimer = totalTimerStop();
132
+ this._log({
133
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_READ_DONE,
134
+ data,
135
+ key,
136
+ normalizedKey,
137
+ timers: {
138
+ network: networkTimer,
139
+ total: totalTimer,
140
+ },
141
+ }, context);
142
+
143
+ resolve(parsedValue);
144
+ }
145
+ });
146
+ });
147
+ }
148
+
149
+ set({ key, value, maxage = this._options.defaultKeyTTL, context } ) {
150
+ if (typeof value === 'undefined') {
151
+ return;
152
+ }
153
+
154
+ const totalTimerStop = contimer.start({}, 'descript-redis-cache.set.total');
155
+ const normalizedKey = this.normalizeKey(key);
156
+
157
+ return new Promise((resolve, reject) => {
158
+ this._log({
159
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_WRITE_START,
160
+ key,
161
+ normalizedKey,
162
+ }, context);
163
+
164
+ // save to cache only serializable data
165
+ const safeSerializableValue = {
166
+ status_code: value.status_code,
167
+ headers: value.headers,
168
+ result: value.result,
169
+ };
170
+
171
+ let json;
172
+ try {
173
+ json = JSON.stringify(safeSerializableValue);
174
+ } catch (error) {
175
+ const totalTimer = totalTimerStop();
176
+ this._log({
177
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_JSON_STRINGIFY_FAILED,
178
+ data: value,
179
+ error,
180
+ key,
181
+ normalizedKey,
182
+ timers: {
183
+ network: {},
184
+ total: totalTimer,
185
+ },
186
+ }, context);
187
+ reject(de.error({
188
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_JSON_STRINGIFY_FAILED,
189
+ }));
190
+ return;
191
+ }
192
+
193
+ const networkTimerStop = contimer.start({}, 'descript-redis-cache.set.network');
194
+ // maxage - seconds
195
+ this._client.set(normalizedKey, json, 'EX', maxage, (error, done) => {
196
+ const networkTimer = networkTimerStop();
197
+ const totalTimer = totalTimerStop();
198
+ if (error) {
199
+ this._log({
200
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_WRITE_ERROR,
201
+ error,
202
+ key,
203
+ normalizedKey,
204
+ timers: {
205
+ network: networkTimer,
206
+ total: totalTimer,
207
+ },
208
+ }, context);
209
+ reject(de.error({
210
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_WRITE_ERROR,
211
+ }));
212
+ } else if (!done) {
213
+ this._log({
214
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_WRITE_FAILED,
215
+ key,
216
+ normalizedKey,
217
+ timers: {
218
+ network: networkTimer,
219
+ total: totalTimer,
220
+ },
221
+ }, context);
222
+ reject(de.error({
223
+ id: DescriptRedisCache.EVENT.REDIS_CACHE_WRITE_FAILED,
224
+ }));
225
+ } else {
226
+ this._log({
227
+ type: DescriptRedisCache.EVENT.REDIS_CACHE_WRITE_DONE,
228
+ data: json,
229
+ key,
230
+ normalizedKey,
231
+ timers: {
232
+ network: networkTimer,
233
+ total: totalTimer,
234
+ },
235
+ }, context);
236
+ resolve();
237
+ }
238
+ });
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Generates normalized SHA-512 key with generation
244
+ * @param {string} key
245
+ * @returns {string}
246
+ */
247
+ normalizeKey(key) {
248
+ const value = `g${ this._options.generation }:${ key }`;
249
+ return crypto
250
+ .createHash('sha512')
251
+ .update(value, 'utf8')
252
+ .digest('hex');
253
+ }
254
+
255
+ _log(event, context) {
256
+ if (this._logger) {
257
+ this._logger.log(event, context);
258
+ }
259
+ }
260
+ }
261
+
262
+ DescriptRedisCache.EVENT = {
263
+ REDIS_CACHE_INITIALIZED: 'REDIS_CACHE_INITIALIZED',
264
+
265
+ REDIS_CACHE_JSON_PARSING_FAILED: 'REDIS_CACHE_JSON_PARSING_FAILED',
266
+ REDIS_CACHE_JSON_STRINGIFY_FAILED: 'REDIS_CACHE_JSON_STRINGIFY_FAILED',
267
+
268
+ REDIS_CACHE_READ_DONE: 'REDIS_CACHE_READ_DONE',
269
+ REDIS_CACHE_READ_ERROR: 'REDIS_CACHE_READ_ERROR',
270
+ REDIS_CACHE_READ_KEY_NOT_FOUND: 'REDIS_CACHE_READ_KEY_NOT_FOUND',
271
+ REDIS_CACHE_READ_START: 'REDIS_CACHE_READ_START',
272
+ REDIS_CACHE_READ_TIMEOUT: 'REDIS_CACHE_READ_TIMEOUT',
273
+
274
+ REDIS_CACHE_WRITE_DONE: 'REDIS_CACHE_WRITE_DONE',
275
+ REDIS_CACHE_WRITE_ERROR: 'REDIS_CACHE_WRITE_ERROR',
276
+ REDIS_CACHE_WRITE_FAILED: 'REDIS_CACHE_WRITE_FAILED',
277
+ REDIS_CACHE_WRITE_START: 'REDIS_CACHE_READ_START',
278
+ };
279
+
280
+ module.exports = DescriptRedisCache;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "descript-redis-cache",
3
+ "version": "3.0.0",
4
+ "description": "plugin for descript to use redis as cache",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "eslint ."
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": ""
12
+ },
13
+ "keywords": [
14
+ "descript",
15
+ "redis"
16
+ ],
17
+ "author": "Boris Zhidkov <eljusto@yandex-team.ru>",
18
+ "license": "MIT",
19
+ "bugs": {
20
+ "url": ""
21
+ },
22
+ "homepage": "",
23
+ "engines": {
24
+ "node": ">= 10.0.0"
25
+ },
26
+ "dependencies": {
27
+ "contimer": "^1.0.0",
28
+ "ioredis": "^4.28.2"
29
+ },
30
+ "peerDependencies": {
31
+ "descript": ">=3"
32
+ },
33
+ "devDependencies": {
34
+ "eslint": "^7.1.0"
35
+ }
36
+ }