Skip to content

Commit e06315d

Browse files
author
Lee Brooks
authored
Initial development (#1)
1 parent ea8ae7c commit e06315d

File tree

12 files changed

+299
-66
lines changed

12 files changed

+299
-66
lines changed

.eslintrc.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
root: true,
3+
parser: '@typescript-eslint/parser',
4+
plugins: [
5+
'@typescript-eslint',
6+
],
7+
extends: [
8+
'airbnb-typescript/base',
9+
],
10+
rules: {
11+
'@typescript-eslint/indent': ['error', 4],
12+
'arrow-parens': ['error', 'as-needed']
13+
},
14+
};

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/coverage
21
/node_modules
32
/lib
43
package-lock.json

.travis.yml

Lines changed: 0 additions & 26 deletions
This file was deleted.

README.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
1-
# Node Package Template
1+
# Cache JS
22

3-
A template repository for a Node.js package using TypeScript.
3+
[![Build Status](https://travis-ci.com/Pod-Point/cache-js.svg?branch=master)](https://travis-ci.com/Pod-Point/cache-js)
4+
5+
A cache service for JS.
46

57
## Usage
68

7-
See the following link for how to create a repository from a template:
8-
https://help.github.com/en/articles/creating-a-repository-from-a-template
9+
To install this package, run the following command:
10+
```bash
11+
npm install @pod-point/cache-js
12+
```
913

10-
### Installation
14+
Once installed, simply create a new instance of the Cache service and begin using it e.g.
15+
```
16+
import { Redis } from '@pod-point/cache-js';
1117
12-
To install the latest version of this templates dev dependencies, run the following command:
13-
```bash
14-
npm install --save-dev @types/faker @types/jest @pod-point/tslint-config-podpoint-base faker jest ts-jest tslint typescript
18+
const cacheService = new Redis();
19+
20+
await cacheService.put('foo', 'bar');
1521
```
1622

23+
There are only 3 simple methods a cache service can carry out, and these are `put`, `get` and `remove`, all fairly self-explanatory!
24+
25+
When putting key/value pairs into the cache you can also set an expiry date, or a time in seconds until it should expire.
26+
1727
## Development
1828

29+
### Installation
30+
31+
To install this packages dependencies, run the following command:
32+
```bash
33+
npm install
34+
```
35+
1936
### Testing
2037

2138
This package uses jest. To run the test suites for this project, run the following command:

package.json

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
11
{
2-
"name": "@pod-point/node-package",
3-
"version": "1.0.0",
4-
"description": "A template repository for a Node.js package using TypeScript.",
2+
"name": "@pod-point/cache-js",
3+
"version": "1.0.2",
4+
"description": "A cache service for JS.",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",
77
"author": "Pod Point Software Team <software@pod-point.com>",
88
"license": "MIT",
99
"repository": {
1010
"type": "git",
11-
"url": "git+https://github.com/Pod-Point/node-package.git"
11+
"url": "git+https://github.com/Pod-Point/cache-js.git"
1212
},
1313
"bugs": {
14-
"url": "https://github.com/Pod-Point/node-package/issues"
14+
"url": "https://github.com/Pod-Point/cache-js/issues"
1515
},
16-
"homepage": "https://github.com/Pod-Point/node-package#readme",
16+
"homepage": "https://github.com/Pod-Point/cache-js#readme",
1717
"keywords": [
18-
"TypeScript"
18+
"TypeScript",
19+
"JavaScript",
20+
"Cache",
21+
"Redis"
1922
],
2023
"scripts": {
2124
"build": "node_modules/.bin/tsc",
22-
"lint": "node_modules/.bin/tslint 'src/**/*.{ts,tsx}'",
25+
"lint": "node_modules/.bin/eslint 'src/**/*.{ts,tsx}'",
2326
"lint:fix": "npm run lint -- --fix",
2427
"test": "node_modules/.bin/jest --watch",
2528
"test:coverage": "node_modules/.bin/jest --coverage --runInBand --ci"
2629
},
27-
"dependencies": {},
28-
"devDependencies": {},
30+
"dependencies": {
31+
"redis": "^2.8.0"
32+
},
33+
"devDependencies": {
34+
"@types/faker": "^4.1.8",
35+
"@types/jest": "^24.0.25",
36+
"@types/node": "^13.1.1",
37+
"@types/redis": "^2.8.14",
38+
"@typescript-eslint/eslint-plugin": "^2.13.0",
39+
"@typescript-eslint/parser": "^2.13.0",
40+
"eslint": "^6.8.0",
41+
"eslint-config-airbnb-typescript": "^6.3.1",
42+
"eslint-plugin-import": "^2.19.1",
43+
"faker": "^4.1.0",
44+
"jest": "^24.9.0",
45+
"ts-jest": "^24.2.0",
46+
"typescript": "^3.7.4"
47+
},
2948
"jest": {
3049
"collectCoverageFrom": [
3150
"src/**/*.ts",
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { createClient } from 'redis';
2+
import Redis from '../../../services/Redis';
3+
4+
const mockSet = jest.fn((key, value, cb) => cb());
5+
const mockQuit = jest.fn();
6+
const mockDel = jest.fn((key, cb) => cb());
7+
const mockExpire = jest.fn((key, time, cb) => cb());
8+
const mockExpireAt = jest.fn((key, time, cb) => cb());
9+
const mockGet = jest.fn((key, cb) => cb(null, 'someData'));
10+
11+
jest.mock('redis', () => ({
12+
createClient: jest.fn(() => ({
13+
del: mockDel,
14+
expire: mockExpire,
15+
expireat: mockExpireAt,
16+
get: mockGet,
17+
set: mockSet,
18+
quit: mockQuit,
19+
})),
20+
}));
21+
22+
describe('services/redis/Redis', () => {
23+
const key = 'foo';
24+
const value = 'bar';
25+
26+
[true, false].forEach(ephemeral => {
27+
const isEphemeral = ephemeral ? 'ephemeral' : 'nonEphemeral';
28+
const cache = new Redis({}, ephemeral);
29+
30+
describe(isEphemeral, () => {
31+
beforeEach(() => {
32+
mockQuit.mockReset();
33+
});
34+
35+
afterEach(() => {
36+
if (ephemeral) {
37+
expect(createClient).toHaveBeenCalled();
38+
expect(mockQuit).toHaveBeenCalledWith();
39+
}
40+
});
41+
42+
it('persists the key/value pair in the cache', async () => {
43+
await cache.put(key, value);
44+
45+
expect(mockSet).toHaveBeenCalledWith('foo', 'bar', expect.any(Function));
46+
});
47+
48+
it('persists the key/value pair in the cache to expire at a particular time', async () => {
49+
const timestamp = (new Date()).getTime();
50+
await cache.put(key, value, {
51+
at: timestamp,
52+
});
53+
54+
expect(mockSet).toHaveBeenCalledWith('foo', 'bar', expect.any(Function));
55+
expect(mockExpireAt).toHaveBeenCalledWith(
56+
'foo',
57+
timestamp,
58+
expect.any(Function),
59+
);
60+
});
61+
62+
it('persists the key/value pair in the cache to expire in a given number of seconds', async () => {
63+
await cache.put(key, value, {
64+
in: 1,
65+
});
66+
67+
expect(mockSet).toHaveBeenCalledWith('foo', 'bar', expect.any(Function));
68+
expect(mockExpire).toHaveBeenCalledWith('foo', 1, expect.any(Function));
69+
});
70+
71+
it('retrieves the keys value from the cache', async () => {
72+
const data = await cache.get(key);
73+
74+
expect(mockGet).toHaveBeenCalledWith('foo', expect.any(Function));
75+
expect(data).toEqual('someData');
76+
});
77+
78+
it('removes the key/value pair from the cache', async () => {
79+
await cache.remove(key);
80+
81+
expect(mockDel).toHaveBeenCalledWith('foo', expect.any(Function));
82+
});
83+
});
84+
});
85+
});

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
// Export all the things from here.
1+
export { default as Redis } from './services/Redis';
2+
export { default as Service } from './types/Service';

src/services/Redis.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { ClientOpts, createClient } from 'redis';
2+
import { promisify } from 'util';
3+
import Expire from '../types/Expire';
4+
import Service from '../types/Service';
5+
import RedisService from '../types/redis/Service';
6+
7+
class Redis implements Service {
8+
private config: ClientOpts;
9+
10+
private ephemeral: boolean;
11+
12+
private service: RedisService;
13+
14+
/**
15+
* Creates a Redis client instance depending on config.
16+
*/
17+
public constructor(config?: ClientOpts, ephemeral?: boolean) {
18+
this.ephemeral = ephemeral;
19+
this.config = config;
20+
21+
if (!this.ephemeral) {
22+
this.service = this.getService(true);
23+
}
24+
}
25+
26+
/**
27+
* Retrieves the keys value from the cache.
28+
*/
29+
public async get(key: string): Promise<string> {
30+
const service = this.getService();
31+
32+
const value = await service.get(key);
33+
34+
this.quitIfNeeded(service);
35+
36+
return value;
37+
}
38+
39+
/**
40+
* Persists the key/value pair in the cache,
41+
* optionally setting it to expire at a particular time or in a given number of seconds.
42+
*/
43+
public async put(key: string, value: string, expire?: Expire): Promise<void> {
44+
const service = this.getService();
45+
46+
await service.set(key, value);
47+
48+
if (expire) {
49+
if (expire.at) {
50+
await service.expireAt(key, expire.at);
51+
}
52+
53+
if (expire.in) {
54+
await service.expire(key, expire.in);
55+
}
56+
}
57+
58+
this.quitIfNeeded(service);
59+
}
60+
61+
/**
62+
* Removes the key/value pair from the cache.
63+
*/
64+
public async remove(key: string): Promise<void> {
65+
const service = this.getService();
66+
67+
await service.del(key);
68+
69+
this.quitIfNeeded(service);
70+
}
71+
72+
/**
73+
* Quits the redis client if required.
74+
*/
75+
private async quitIfNeeded(service): Promise<void> {
76+
if (this.ephemeral) {
77+
service.client.quit();
78+
}
79+
}
80+
81+
/**
82+
* Creates new Redis instance if one does not already exist or if is configured to be ephemeral.
83+
*/
84+
private getService(force: boolean = false): RedisService {
85+
if (this.ephemeral || force) {
86+
const client = createClient(this.config);
87+
this.service = {
88+
client,
89+
del: promisify(client.del).bind(client),
90+
expire: promisify(client.expire).bind(client),
91+
expireAt: promisify(client.expireat).bind(client),
92+
get: promisify(client.get).bind(client),
93+
set: promisify(client.set).bind(client),
94+
};
95+
}
96+
97+
return this.service;
98+
}
99+
}
100+
101+
export default Redis;

src/types/Expire.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface Expire {
2+
/**
3+
* The timestamp when the key/value pair should expire.
4+
*/
5+
at?: number;
6+
7+
/**
8+
* The number of seconds until the key/value pair should expire.
9+
*/
10+
in?: number;
11+
}
12+
13+
export default Expire;

src/types/Service.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
interface Service {
2+
/**
3+
* Retrieves the keys value from the cache.
4+
*/
5+
get(key: string): Promise<string>;
6+
7+
/**
8+
* Persists the key/value pair in the cache.
9+
*/
10+
put(key: string, value: string): Promise<void>;
11+
12+
/**
13+
* Removes the key/value pair from the cache.
14+
*/
15+
remove(key: string): Promise<void>;
16+
}
17+
18+
export default Service;

src/types/redis/Service.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { RedisClient } from 'redis';
2+
3+
interface RedisService {
4+
client: RedisClient,
5+
del: (key: string) => Promise<void>;
6+
expire: (key: string, seconds: number) => Promise<void>;
7+
expireAt: (key: string, seconds: number) => Promise<void>;
8+
get: (key: string) => Promise<string>;
9+
set: (key: string, value: string) => Promise<void>;
10+
}
11+
12+
export default RedisService;

0 commit comments

Comments
 (0)