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

Commit e8157e4

Browse files
author
Andreas Richter
committed
refactor: rewrite, use memcached-elasticache & drop Node 8
- [x] chore: drop Node 8 support. Min required version is [email protected] - [x] refactor(deps): remove `lodash` and `bluebird` - [x] refactor(cache): rewrite `Cache` with ES6 class syntax - [x] refactor: `cache.set` no longer returns a value - [x] refactor: make inofficial cache api private - [x] fix: handle functions or promises as `cache.set` values as described in README.md - [x] fix: remove deprecation message, that warned when backend `name` was not a string. It will throw a type error now. - [x] deps: use `memcached-elasticache` instead of `memcached` - [x] chore(deps): upgrade dependencies - [x] test: add additional test to increase test coverage - [x] docs(README): update API section ### BREAKING CHANGES: #### General - Dropped Node 8.x. Use Node 10.13 or higher. - Uses `memcached-elasticache` with AWS support instead of `memcached`. See https://github.com/jkehres/memcached-elasticache#readme - API functions no longer support callbacks. - API functions use native Promises instead of `Bluebird`. - `cached()` needs to be called with a string argument as name. If the `name` argument is undefined, it will fallback to `"default"`. #### `Cache` - `Cache.setBackend()` no longer returns the created backend. - `Cache.getWrapped()` is removed. - Cache backend APIs need to be promise based. - Cache private function have been renamed. - The following function are now private: - `Cache.applyPrefix` - `Cache.end` - `Cache.prepareOptions` - `Cache.setBackend` #### `cache` - `cache.set()`, `cache.get()`, `cache.getOrElse()`, `cache.unset()` no longer support the callback argument. Use them as promise. - `cache.set()` no longer returns a value. - `cache.set(key, value)` accepts functions and promises as value. #### Backends - Any client passed with the memcached backend options has to be an instance of `Memcached`. - Backend setters no longer return a value. - backend `addType()` does not return backend class anymore.
1 parent aa75571 commit e8157e4

23 files changed

+3037
-1741
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ npm-debug.log
44
/tmp
55
*.log
66
/target
7+
/coverage

.travis.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
language: node_js
22
node_js:
3-
- 8
43
- 10
54
- 12
5+
- 14
6+
- 16
67
deploy:
78
- provider: script
89
script: npx nlm release
9-
skip_cleanup: true
10+
cleanup: false
1011
'on':
1112
branch: master
12-
node: 12
13+
node_js: 16
1314
services: memcached
14-
before_install:
15-
- npm i -g npm@^6

README.md

Lines changed: 133 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# cached
22

3-
A simple caching library, inspired by the [Play cache API](http://www.playframework.com/documentation/2.2.x/ScalaCache) and biased towards [showing stale data instead of dog piling](http://highscalability.com/strategy-break-memcache-dog-pile).
3+
A simple caching library, inspired by the [Play cache API](http://www.playframework.com/documentation/2.2.x/ScalaCache)
4+
and biased towards [showing stale data instead of dog piling](http://highscalability.com/strategy-break-memcache-dog-pile).
45
The interface only exposes very limited functionality, there's no multi-get or deletion of cached data.
56
The library is designed to support different caching backends, though right now only memcached is implemented.
67

@@ -17,99 +18,97 @@ More detailed API docs are in the next section.
1718
### Getting and setting
1819

1920
```js
20-
cached = require('cached');
21+
const cached = require('cached');
2122

22-
kittens = cached('kittens');
23+
const kittens = cached('kittens');
2324

24-
// Set a key using a plain value
25-
kittens.set('my.key', 'Hello World');
25+
async function cacheKittens() {
2626

27-
// Set a key using a lazily created promise (or value)
28-
kittens.set('my.key', function() {
29-
return cache.get('other.key');
30-
});
31-
32-
// Set a key using a callback-style function
33-
kittens.set('my.key', cached.deferred(function(done) {
34-
done(null, 'Hello World');
35-
}));
36-
37-
kittens.getOrElse('my.key', function() {
38-
// This will store "Hello World" for key "my.key" if
39-
// "my.key" was not found
40-
return 'Hello World';
41-
}).then(function(data) {
42-
console.log(data);
43-
});
44-
45-
kittens.get('my.key', function(err, data) {
46-
// Handle it the callback way
47-
});
48-
49-
// Handle it the promise way
50-
kittens.get('my.key').then(
51-
function(data) {
52-
console.log(data);
53-
},
54-
function(err) {
55-
throw err;
56-
}
57-
);
27+
// Set a key using a plain value
28+
await kittens.set('my.key', 'Hello World');
29+
30+
// Set a key using a lazily created promise
31+
await kittens.set('my.key', () => {
32+
return cache.get('other.key');
33+
});
34+
35+
// Set a key using a value)
36+
await kittens.set('my.key', 'kitty');
37+
38+
// Set a key using a callback-style function
39+
await kittens.set('my.key', cached.deferred(done => {
40+
done(null, 'Hello World');
41+
}));
42+
43+
const data = await kittens.getOrElse('my.key', () => {
44+
// This will store "Hello World" for key "my.key" if
45+
// "my.key" was not found
46+
return 'Hello World';
47+
});
48+
49+
// Handle it the promise way
50+
const data2 = await kittens.get('my.key').catch(err => {
51+
throw err;
52+
}
53+
);
54+
}
5855
```
5956

6057
## Supported backends
6158

6259
### Memcached
6360

64-
A thin wrapper around [memcached](https://github.com/3rd-Eden/node-memcached).
65-
You can either provide a readily configured client or a combination of hosts and additional options.
61+
A thin wrapper around [memcached-elasticache](https://github.com/jkehres/memcached-elasticache).
62+
You can either provide a readily configured client, or a combination of hosts and additional options.
6663
Without any additional options it will default to a local memcached on `11211`.
6764

6865
#### Custom client instance
6966

7067
```js
71-
var Memcached = require('memcached');
68+
const Memcached = require('memcached-elasticache');
7269

7370
cached('myStuff', { backend: {
7471
type: 'memcached',
75-
client: new Memcached('192.168.0.102:11212', { poolSize: 15 })
72+
client: new Memcached('192.168.0.102:11212', { poolSize: 15 }),
7673
}});
7774
```
7875

79-
#### Let cached create the instance
76+
#### Let `cached` create the instance
8077

8178
This will create the same cache as above.
8279

8380
```js
8481
cached('myStuff', { backend: {
8582
type: 'memcached',
8683
hosts: '192.168.0.102:11212',
87-
poolSize: 15
84+
poolSize: 15,
8885
}});
8986
```
9087

9188
### Memory
9289

9390
Stores all the data in an in-memory object.
94-
*__Caveat:__ `get()` will return a reference to the stored value. Mutating the returned value will affect the value in the cache.*
91+
*__Caveat:__ `get()` will return a reference to the stored value. Mutating the returned value will affect the
92+
value in the cache.*
9593

9694
#### Example
9795

9896
```js
9997
cached('myStuff', { backend: {
100-
type: 'memory'
98+
type: 'memory',
10199
}});
102100
```
103101

104102
### Noop
105103

106-
Doesn't store data at all. All `set` operations succeed and `get` operations behave as if the value were not found in the cache.
104+
Doesn't store data at all. All `set` operations succeed and `get` operations behave as if the value were not
105+
found in the cache.
107106

108107
#### Examples
109108

110109
```js
111110
cached('myStuff', { backend: {
112-
type: 'noop'
111+
type: 'noop',
113112
}});
114113
```
115114

@@ -124,14 +123,18 @@ cached('myStuff');
124123

125124
Creates a new named cache or returns a previously initialized cache.
126125

127-
* **name:** (required) A meaningful name for what is in the cache. This will also be used as a key-prefix. If the name is `"cars"`, all keys will be prefixed with `"cars:"`
126+
* **name:** (required) A meaningful name for what is in the cache. This will also be used as a key-prefix. If the
127+
name is `"cars"`, all keys will be prefixed with `"cars:"`
128128
* **options:** (optional)
129-
* **backend:** An object that has at least a `type` property. If no backend is configured, the cache will run in "noop"-mode, not caching anything. All other properties are forwarded to the backend, see [using different backends](#supported-backends) for which backend types exist and what options they support.
129+
* **backend:** An object that has at least a `type` property. If no backend is configured, the cache will run in
130+
"noop"-mode, not caching anything. All other properties are forwarded to the backend, see
131+
[using different backends](#supported-backends) for which backend types exist and what options they support.
130132
* **defaults:** Defaults to apply for all cache operations. See `Cache.setDefaults`
131133

132134
### cached.createCache(options) -> Cache
133135

134-
This allows you to circumvent the global named caches. The options are the same as above, just `name` is also part of the `options` object when using this function.
136+
This allows you to circumvent the global named caches. The options are the same as above, just `name` is also part
137+
of the `options` object when using this function.
135138

136139
### cached.dropNamedCache(name: string) -> cached
137140

@@ -143,67 +146,116 @@ Drop all named caches.
143146

144147
### cached.deferred(fn) -> () -> Promise
145148

146-
Convert a node-style function that takes a callback as its first parameter into a parameterless function that generates a promise.
147-
In other words: this is what you'd want to wrap your node-style functions in when using them as value arguments to `set` or `getOrElse`.
148-
149-
#### Example:
149+
Convert a node-style function that takes a callback as its first parameter into a parameterless function that
150+
generates a promise. In other words: this is what you'd want to wrap your node-style functions in when using them
151+
as value arguments to `set` or `getOrElse`.
150152

153+
**Example:**
151154
```js
152-
var f = cached.deferred(function(cb) {
153-
var req = require('http').get(myUrl, function(res) {
155+
const http = require('http');
156+
157+
const cache = cached('myStuff');
158+
const f = cached.deferred(cb => {
159+
const req = http.get(myUrl, res => {
154160
cb(null, res.statusCode);
155161
});
156162
req.once('error', cb);
157163
});
164+
158165
// f can now be called and the return value will be a promise
159166
f().then(function(statusCode) { console.log(statusCode); });
167+
160168
// More importantly it can be passed into cache.set
161-
cached('myStuff').set('someKey', f);
169+
await cache.set('someKey', f);
162170
```
163171

164172
### Cache.setDefaults(defaults) -> Cache.defaults
165173

166174
Extends the current defaults with the provided defaults.
167175
The two important ones are `freshFor` and `expire`:
168176

169-
* `expire` is the time in seconds after which a value should be deleted from the cache (or whatever expiring natively means for the backend). Usually you'd want this to be `0` (never expire).
170-
* `freshFor` is the time in seconds after which a value should be replaced. Replacing the value is done in the background and while the new value is generated (e.g. data is fetched from some service) the stale value is returned. Think of `freshFor` as a smarter `expire`.
177+
* `expire` is the time in seconds after which a value should be deleted from the cache
178+
(or whatever expiring natively means for the backend). Usually you'd want this to be `0` (never expire).
179+
* `freshFor` is the time in seconds after which a value should be replaced. Replacing the value is done in
180+
the background and while the new value is generated (e.g. data is fetched from some service) the stale
181+
value is returned. Think of `freshFor` as a smarter `expire`.
171182
* `timeout` is the maximum time in milliseconds to wait for cache operations to complete.
172183
Configuring a timeout ensures that all `get`, `set`, and `unset` operations fail fast.
173-
Otherwise there will be situations where one of the cache hosts goes down and reads hang for minutes while the memcached client retries to establish a connection.
184+
Otherwise, there will be situations where one of the cache hosts goes down and reads hang for minutes while
185+
the memcached client retries to establish a connection.
174186
It's **highly** recommended to set a timeout.
175-
If `timeout` is left `undefined`, no timeout will be set and the operations will only fail once the underlying client, e.g. [`memcached`](https://github.com/3rd-Eden/memcached), gave up.
187+
If `timeout` is left `undefined`, no timeout will be set, and the operations will only fail once the
188+
underlying client, e.g. [`memcached`](https://github.com/3rd-Eden/memcached), gave up.
189+
190+
### Cache.get(key) -> Promise\<value\>
191+
192+
Cache retrieve operation. `key` has to be a string.
193+
Cache misses are generally treated the same as retrieving `null`, errors should only be caused by transport
194+
errors and connection problems.
195+
If you want to cache `null`/`undefined` (e.g. 404 responses), you may want to wrap it or choose a different
196+
value, like `false`, to represent this condition.
197+
198+
**Example:**
199+
```js
200+
await cache.get('foo');
201+
```
176202

177-
### Cache.set(key, value, opts, cb) -> Promise[Value]
203+
### Cache.getOrElse(key, value, opts) -> Promise\<value\>
204+
205+
This is the function you'd want to use most of the time.
206+
It takes the same arguments as `set` but it will check the cache first.
207+
If a value is already cached, it will return it directly (respond as fast as possible).
208+
If the value is marked as stale (generated `n` seconds ago with `n > freshFor`), it will replace the value
209+
in the cache. When multiple `getOrElse` calls concurrently encounter the same stale value, it will only replace
210+
the value once. This is done on a per-instance level, so if you create many cache instances reading and writing
211+
the same keys, you are asking for trouble. If you don't, the worst case is every process in your system fetching
212+
the value at once. Which should be a smaller number than the number of concurrent requests in most cases.
213+
214+
**Examples:**
215+
```js
216+
// with a value
217+
const res = await cache.getOrElse('foo', 'bar');
218+
219+
// with a function returning a value
220+
const res = await cache.getOrElse('foo', () => { return 'bar' });
221+
222+
// with a function returning a promise
223+
const res = await cache.getOrElse('foo', () => { return Promise.resolve('bar') });
224+
225+
// with a promise function
226+
const res = await cache.getOrElse('foo', async () => { return 'bar' });
227+
```
228+
229+
### Cache.set(key, value, opts) -> Promise\<void\>
178230

179231
Cache store operation. `key` has to be a string, for possible `opts` see `Cache.setDefaults`.
180232
The value can be any of the following:
181233

182-
a. Anything that can be converted to JSON
183-
b. A Promise of (a)
184-
c. A function returning (a) or (b)
234+
a) Anything that can be converted to JSON<br>
235+
b) A Promise of (a)<br>
236+
c) A function returning (a) or (b)<br>
185237

186-
The callback will be called with the resolved value, following node conventions (error, value).
238+
**Examples:**
239+
```js
240+
// with a value
241+
await cache.set('foo', 'bar');
187242

188-
### Cache.get(key, cb) -> Promise[Value]
243+
// with a function returning a value
244+
await cache.set('foo', () => { return 'bar' });
189245

190-
Cache retrieve operation.
191-
`key` has to be a string.
192-
Cache misses are generally treated the same as retrieving `null`, errors should only be caused by transport errors and connection problems.
193-
If you want to cache `null`/`undefined` (e.g. 404 responses), you may want to wrap it or choose a different value, like `false`, to represent this condition.
246+
// with a function returning a promise
247+
await cache.set('foo', () => { return Promise.resolve('bar') });
194248

195-
### Cache.getOrElse(key, value, opts, cb) -> Promise[Value]
196-
197-
This is the function you'd want to use most of the time.
198-
It takes the same arguments as `set` but it will check the cache first.
199-
If a value is already cached, it will return it directly (respond as fast as possible).
200-
If the value is marked as stale (generated `n` seconds ago with `n > freshFor`), it will replace the value in the cache.
201-
When multiple `getOrElse` calls concurrently encounter the same stale value, it will only replace the value once.
202-
This is done on a per-instance level, so if you create many cache instances reading and writing the same keys, you are asking for trouble.
203-
If you don't, the worst case is every process in your system fetching the value at once.
204-
Which should be a smaller number than the number of concurrent requests in most cases.
249+
// with a promise function
250+
await cache.set('foo', async () => { return 'bar' });
251+
```
205252

206-
### Cache.unset(key, cb) -> Promise
253+
### Cache.unset(key) -> Promise\<void\>
207254

208255
Cache delete operation.
209256
`key` has to be a string.
257+
258+
**Example:**
259+
```js
260+
await cache.unset('foo');
261+
```

0 commit comments

Comments
 (0)