Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 0ce0304

Browse files
author
Andreas Richter
committed
feat: add redis backend
1 parent 843cde8 commit 0ce0304

File tree

9 files changed

+228
-18
lines changed

9 files changed

+228
-18
lines changed

lib/backend.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
const Memcached = require('./backends/memcached');
3636
const Memory = require('./backends/memory');
37+
const Redis = require('./backends/redis');
3738

3839
/**
3940
* @typedef {Object} BackendImpl
@@ -70,6 +71,7 @@ function addType(type, Backend) {
7071
function register() {
7172
addType('memory', Memory);
7273
addType('memcached', Memcached);
74+
addType('redis', Redis);
7375
}
7476

7577
module.exports = {

lib/backends/memory.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ class MemoryBackend {
4242
this.type = 'memory';
4343
}
4444

45+
end() {
46+
this.cache = null;
47+
}
48+
4549
/**
4650
* @param {string} key
4751
* @param {any} value

lib/backends/redis.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright (c) 2014, Groupon, Inc.
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
*
9+
* Redistributions of source code must retain the above copyright notice,
10+
* this list of conditions and the following disclaimer.
11+
*
12+
* Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* Neither the name of GROUPON nor the names of its contributors may be
17+
* used to endorse or promote products derived from this software without
18+
* specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21+
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23+
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
26+
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
33+
'use strict';
34+
35+
const { promisify } = require('util');
36+
const redis = require('redis');
37+
38+
/**
39+
* @param {import('redis').ClientOpts} options
40+
* @returns {RedisClient}
41+
*/
42+
function createClient(options) {
43+
if (options.client instanceof redis.RedisClient) {
44+
return options.client;
45+
}
46+
options = {
47+
host: '127.0.0.1',
48+
port: '6379',
49+
...options,
50+
};
51+
return redis.createClient(options);
52+
}
53+
54+
/* Uses anything supporting the memcached protocol */
55+
class RedisBackend {
56+
/**
57+
* @param {import('redis').ClientOpts} options
58+
*/
59+
constructor(options) {
60+
this.type = 'redis';
61+
this.client = createClient(options);
62+
this._get = promisify(this.client.get.bind(this.client));
63+
this._set = promisify(this.client.set.bind(this.client));
64+
this._del = promisify(this.client.del.bind(this.client));
65+
this._flush = promisify(this.client.flushall.bind(this.client));
66+
}
67+
68+
/**
69+
* Closes all active memcached connections.
70+
*/
71+
end() {
72+
this.client.end(true);
73+
}
74+
75+
/**
76+
* Get the value for the given key.
77+
*
78+
* @param {string} key
79+
* @returns {Promise<any>}
80+
*/
81+
async get(key) {
82+
const res = await this._get(key);
83+
return JSON.parse(res);
84+
}
85+
86+
/**
87+
* Flushes the memcached server
88+
*
89+
* @returns {Promise<void>}
90+
*/
91+
async flush() {
92+
await this._flush();
93+
}
94+
95+
/**
96+
* Stores a new value in Redis.
97+
*
98+
* @param {string} key
99+
* @param {{b: number, d: any}|string} value
100+
* @param {{expire: number}} options
101+
* @returns {Promise<void>}
102+
*/
103+
async set(key, value, options) {
104+
let args = [];
105+
if (options.expire) {
106+
args = ['EX', options.expire];
107+
}
108+
if (typeof value !== 'string') {
109+
value = JSON.stringify(value);
110+
}
111+
112+
await this._set(key, value, ...args);
113+
}
114+
115+
/**
116+
* @param {string} key
117+
* @returns {Promise<void>}
118+
*/
119+
async unset(key) {
120+
await this._del(key);
121+
}
122+
}
123+
124+
module.exports = RedisBackend;

package-lock.json

Lines changed: 52 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,23 @@
3838
}
3939
},
4040
"dependencies": {
41-
"memcached-elasticache": "^1.1.1"
41+
"memcached-elasticache": "^1.1.1",
42+
"redis": "^3.1.2"
4243
},
4344
"devDependencies": {
4445
"@types/memcached": "^2.2.6",
46+
"@types/redis": "^2.8.30",
4547
"c8": "^7.7.3",
46-
"eslint": "^7.28.0",
48+
"eslint": "^7.29.0",
4749
"eslint-config-groupon": "^10.0.4",
4850
"eslint-plugin-import": "^2.23.4",
4951
"eslint-plugin-mocha": "^9.0.0",
5052
"eslint-plugin-node": "^11.1.0",
5153
"eslint-plugin-prettier": "^3.4.0",
52-
"mocha": "^9.0.0",
54+
"mocha": "^9.0.1",
5355
"nlm": "^5.5.1",
5456
"npm-run-all": "^4.1.5",
55-
"prettier": "^2.3.1"
57+
"prettier": "^2.3.2"
5658
},
5759
"author": {
5860
"name": "Groupon",

test/_backends.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@
44

55
const Cache = require('../lib/cache');
66

7-
const backendOptions = {
8-
hosts: `${process.env.MEMCACHED__HOST || '127.0.0.1'}:11211`,
9-
};
10-
117
module.exports = function withBackends(backends, createTestCases) {
128
backends.forEach(backendType => {
139
describe(`with backend "${backendType}"`, () => {
10+
let backendOptions;
11+
switch (backendType) {
12+
case 'memcached':
13+
backendOptions = {
14+
hosts: `${process.env.MEMCACHED__HOST || '127.0.0.1'}:11211`,
15+
};
16+
break;
17+
case 'redis':
18+
backendOptions = {
19+
host: '127.0.0.1',
20+
port: '6379',
21+
};
22+
break;
23+
24+
default:
25+
backendOptions = {};
26+
}
27+
1428
const cache = new Cache({
1529
backend: { ...backendOptions, type: backendType },
1630
name: 'awesome-name',

test/cache.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const assert = require('assert');
44

55
const Memcached = require('memcached-elasticache');
6+
const redis = require('redis');
67

78
const Cache = require('../lib/cache');
89

@@ -38,6 +39,23 @@ describe('Cache', () => {
3839
assert.ok(cache.backend.client instanceof Memcached);
3940
});
4041

42+
it('allows redis instance to be passed with the backend options', () => {
43+
const options = {
44+
name: 'my-redis',
45+
backend: {
46+
type: 'redis',
47+
client: redis.createClient({
48+
host: '127.0.0.1',
49+
port: '6379',
50+
}),
51+
},
52+
};
53+
54+
const cache = new Cache(options);
55+
56+
assert.ok(cache.backend.client instanceof redis.RedisClient);
57+
});
58+
4159
it('creates new memcached instance if passed client is not instance of Memcached', () => {
4260
const options = {
4361
name: 'my-memcached',

test/get-or-else.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ describe('Cache::getOrElse', () => {
5151
withBackends(
5252
[
5353
// 'memory',
54+
'redis',
5455
'memcached',
5556
],
5657
cache => {

test/get-set.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ const assert = require('assert');
55
const withBackends = require('./_backends');
66
const { delay } = require('./_helper');
77

8+
const backends = ['redis', 'memory', 'memcached'];
9+
810
describe('Cache::{get,set,unset}', () => {
9-
withBackends(['memory', 'memcached'], cache => {
11+
withBackends(backends, cache => {
1012
it('get(), set() and unset() return a promise', async () => {
1113
for (const fn of ['get', 'set', 'unset'])
1214
assert.ok(cache[fn]() instanceof Promise);

0 commit comments

Comments
 (0)