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

feat: add redis backend #62

Merged
merged 2 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- uses: niden/actions-memcached@v7
- name: Start Memcached
uses: niden/actions-memcached@v7
- name: Start Redis
uses: supercharge/[email protected]
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ name: Publish to NPM

on:
push:
branches: [ master ]
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: niden/actions-memcached@v7
- name: Start Memcached
uses: niden/actions-memcached@v7
- name: Start Redis
uses: supercharge/[email protected]
- uses: actions/setup-node@v2
with:
node-version: 14
Expand Down
2 changes: 2 additions & 0 deletions lib/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

const Memcached = require('./backends/memcached');
const Memory = require('./backends/memory');
const Redis = require('./backends/redis');

/**
* @typedef {Object} BackendImpl
Expand Down Expand Up @@ -70,6 +71,7 @@ function addType(type, Backend) {
function register() {
addType('memory', Memory);
addType('memcached', Memcached);
addType('redis', Redis);
}

module.exports = {
Expand Down
4 changes: 4 additions & 0 deletions lib/backends/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class MemoryBackend {
this.type = 'memory';
}

end() {
this.cache = null;
}

/**
* @param {string} key
* @param {any} value
Expand Down
124 changes: 124 additions & 0 deletions lib/backends/redis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (c) 2014, Groupon, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of GROUPON nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

'use strict';

const { promisify } = require('util');
const redis = require('redis');

/**
* @param {import('redis').ClientOpts} options
* @returns {RedisClient}
*/
function createClient(options) {
if (options.client instanceof redis.RedisClient) {
return options.client;
}
options = {
host: '127.0.0.1',
port: '6379',
...options,
};
return redis.createClient(options);
}

/* Uses anything supporting the memcached protocol */
class RedisBackend {
/**
* @param {import('redis').ClientOpts} options
*/
constructor(options) {
this.type = 'redis';
this.client = createClient(options);
this._get = promisify(this.client.get.bind(this.client));
this._set = promisify(this.client.set.bind(this.client));
this._del = promisify(this.client.del.bind(this.client));
this._flush = promisify(this.client.flushall.bind(this.client));
}

/**
* Closes all active memcached connections.
*/
end() {
this.client.end(true);
}

/**
* Get the value for the given key.
*
* @param {string} key
* @returns {Promise<any>}
*/
async get(key) {
const res = await this._get(key);
return JSON.parse(res);
}

/**
* Flushes the memcached server
*
* @returns {Promise<void>}
*/
async flush() {
await this._flush();
}

/**
* Stores a new value in Redis.
*
* @param {string} key
* @param {{b: number, d: any}|string} value
* @param {{expire: number}} options
* @returns {Promise<void>}
*/
async set(key, value, options) {
let args = [];
if (options.expire) {
args = ['EX', options.expire];
}
if (typeof value !== 'string') {
value = JSON.stringify(value);
}

await this._set(key, value, ...args);
}

/**
* @param {string} key
* @returns {Promise<void>}
*/
async unset(key) {
await this._del(key);
}
}

module.exports = RedisBackend;
61 changes: 52 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,23 @@
}
},
"dependencies": {
"memcached-elasticache": "^1.1.1"
"memcached-elasticache": "^1.1.1",
"redis": "^3.1.2"
},
"devDependencies": {
"@types/memcached": "^2.2.6",
"@types/redis": "^2.8.30",
"c8": "^7.7.3",
"eslint": "^7.28.0",
"eslint": "^7.29.0",
"eslint-config-groupon": "^10.0.4",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"mocha": "^9.0.0",
"mocha": "^9.0.1",
"nlm": "^5.5.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.1"
"prettier": "^2.3.2"
},
"author": {
"name": "Groupon",
Expand Down
22 changes: 18 additions & 4 deletions test/_backends.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@

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

const backendOptions = {
hosts: `${process.env.MEMCACHED__HOST || '127.0.0.1'}:11211`,
};

module.exports = function withBackends(backends, createTestCases) {
backends.forEach(backendType => {
describe(`with backend "${backendType}"`, () => {
let backendOptions;
switch (backendType) {
case 'memcached':
backendOptions = {
hosts: `${process.env.MEMCACHED__HOST || '127.0.0.1'}:11211`,
};
break;
case 'redis':
backendOptions = {
host: '127.0.0.1',
port: '6379',
};
break;

default:
backendOptions = {};
}

const cache = new Cache({
backend: { ...backendOptions, type: backendType },
name: 'awesome-name',
Expand Down
18 changes: 18 additions & 0 deletions test/cache.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const assert = require('assert');

const Memcached = require('memcached-elasticache');
const redis = require('redis');

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

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

it('allows redis instance to be passed with the backend options', () => {
const options = {
name: 'my-redis',
backend: {
type: 'redis',
client: redis.createClient({
host: '127.0.0.1',
port: '6379',
}),
},
};

const cache = new Cache(options);

assert.ok(cache.backend.client instanceof redis.RedisClient);
});

it('creates new memcached instance if passed client is not instance of Memcached', () => {
const options = {
name: 'my-memcached',
Expand Down
1 change: 1 addition & 0 deletions test/get-or-else.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe('Cache::getOrElse', () => {
withBackends(
[
// 'memory',
'redis',
'memcached',
],
cache => {
Expand Down
4 changes: 3 additions & 1 deletion test/get-set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ const assert = require('assert');
const withBackends = require('./_backends');
const { delay } = require('./_helper');

const backends = ['redis', 'memory', 'memcached'];

describe('Cache::{get,set,unset}', () => {
withBackends(['memory', 'memcached'], cache => {
withBackends(backends, cache => {
it('get(), set() and unset() return a promise', async () => {
for (const fn of ['get', 'set', 'unset'])
assert.ok(cache[fn]() instanceof Promise);
Expand Down