redis-dataloader
Advanced tools
Comparing version 0.1.0 to 0.2.0
88
index.js
@@ -10,5 +10,13 @@ 'use strict'; | ||
const parse = resp => Q.Promise((resolve, reject) => { | ||
const parse = (resp, opt) => Q.Promise((resolve, reject) => { | ||
try { | ||
resolve(resp !== '' && resp !== null ? JSON.parse(resp) : resp); | ||
if(resp === '' || resp === null) { | ||
resolve(resp); | ||
} | ||
else if(opt.deserialize) { | ||
resolve(opt.deserialize(resp)); | ||
} | ||
else { | ||
resolve(JSON.parse(resp)); | ||
} | ||
} | ||
@@ -20,6 +28,9 @@ catch(err) { | ||
const toString = val => { | ||
const toString = (val, opt) => { | ||
if(val === null) { | ||
return Q(''); | ||
} | ||
else if(opt.serialize) { | ||
return Q(opt.serialize(val)); | ||
} | ||
else if(_.isObject(val)) { | ||
@@ -33,38 +44,45 @@ return Q(JSON.stringify(val)); | ||
const rSet = (keySpace, key, rawVal, expire) => toString(rawVal) | ||
.then(val => Q.Promise((resolve, reject) => redis.set( | ||
`${keySpace}:${key}`, val, (err, resp) => { | ||
if(err) { | ||
reject(err); | ||
} | ||
else { | ||
if(expire) { | ||
redis.expire(`${keySpace}:${key}`, expire); | ||
} | ||
resolve(resp); | ||
} | ||
const makeKey = (keySpace, key) => `${keySpace}:${key}`; | ||
const rSetAndGet = (keySpace, key, rawVal, opt) => toString(rawVal, opt) | ||
.then(val => Q.Promise((resolve, reject) => { | ||
const fullKey = makeKey(keySpace, key); | ||
const multi = redis.multi(); | ||
multi.set(fullKey, val); | ||
if(opt.expire) { | ||
multi.expire(fullKey, opt.expire); | ||
} | ||
))); | ||
multi.get(fullKey); | ||
multi.exec((err, replies) => err ? | ||
reject(err) : parse(_.last(replies), opt).then(resolve) | ||
); | ||
})); | ||
const rMGet = (keySpace, keys) => { | ||
return Q.Promise((resolve, reject) => redis.mget( | ||
_.map(keys, k => `${keySpace}:${k}`), | ||
const rGet = (keySpace, key, opt) => Q.Promise( | ||
(resolve, reject) => redis.get( | ||
makeKey(keySpace, key), | ||
(err, result) => err ? reject(err) : parse(result, opt).then(resolve) | ||
) | ||
); | ||
const rMGet = (keySpace, keys, opt) => Q.Promise( | ||
(resolve, reject) => redis.mget( | ||
_.map(keys, k => makeKey(keySpace, k)), | ||
(err, results) => err ? | ||
reject(err) : | ||
Q.all(_.map(results, parse)).then(resolve) | ||
)); | ||
}; | ||
Q.all(_.map(results, r => parse(r, opt))).then(resolve) | ||
) | ||
); | ||
const rDel = (keySpace, key) => Q.Promise((resolve, reject) => redis.del( | ||
`${keySpace}:${key}`, (err, resp) => err ? reject(err) : resolve(resp) | ||
makeKey(keySpace, key), (err, resp) => err ? reject(err) : resolve(resp) | ||
)); | ||
return class RedisDataLoader { | ||
constructor(ks, userLoader, options) { | ||
constructor(ks, userLoader, opt) { | ||
const customOptions = ['expire', 'serialize', 'deserialize']; | ||
this.opt = _.pick(opt, customOptions) || {}; | ||
this.keySpace = ks; | ||
this.expire = options && options.expire; | ||
this.loader = new DataLoader( | ||
keys => rMGet(this.keySpace, keys) | ||
keys => rMGet(this.keySpace, keys, this.opt) | ||
.then(results => Q.all(_.map( | ||
@@ -78,6 +96,6 @@ results, | ||
return userLoader.load(keys[i]) | ||
.then(resp => { | ||
return rSet(this.keySpace, keys[i], resp, this.expire) | ||
.then(() => resp); | ||
}); | ||
.then(resp => rSetAndGet( | ||
this.keySpace, keys[i], resp, this.opt | ||
)) | ||
.then(r => r === '' ? null : r); | ||
} | ||
@@ -89,3 +107,3 @@ else { | ||
))), | ||
_.omit(options, 'expire') | ||
_.omit(opt, customOptions) | ||
); | ||
@@ -103,4 +121,4 @@ } | ||
prime(key, val) { | ||
return rSet(this.keySpace, key, val, this.expire) | ||
.then(() => this.loader.prime(key, val)); | ||
return rSetAndGet(this.keySpace, key, val, this.opt) | ||
.then(resp => this.loader.clear(key).prime(key, resp)); | ||
} | ||
@@ -107,0 +125,0 @@ |
{ | ||
"name": "redis-dataloader", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "DataLoader Using Redis as a Cache", | ||
@@ -17,3 +17,4 @@ "main": "index.js", | ||
"Redis", | ||
"Batch" | ||
"Batch", | ||
"Facebook" | ||
], | ||
@@ -20,0 +21,0 @@ "author": "Brian Detering", |
# Redis Dataloader | ||
Batching and Caching layer using Redis as the Caching layer. | ||
Redis Dataloader is based on the [Facebook Dataloader](https://github.com/facebook/dataloader), | ||
and uses it internally. | ||
Redis Dataloader wraps [Facebook Dataloader](https://github.com/facebook/dataloader), | ||
adding Redis as a caching layer. | ||
@@ -10,5 +10,5 @@ ## Example | ||
```javascript | ||
const redis = require('redis').createClient(); | ||
const redisClient = require('redis').createClient(); | ||
const DataLoader = require('dataloader'); | ||
const RedisDataLoader = require('redis-dataloader')({ redis: redis }); | ||
const RedisDataLoader = require('redis-dataloader')({ redis: redisClient }); | ||
@@ -24,7 +24,12 @@ const loader = new RedisDataLoader( | ||
{ | ||
// caching here is a local in memory cache | ||
// caching here is a local in memory cache. Caching is always done | ||
// to redis. | ||
cache: true, | ||
// if set redis keys will be set to expire after this many seconds | ||
// this may be useful as a fallback for a redis cache. | ||
expire: 60 | ||
expire: 60, | ||
// can include a custom serialization and deserialization for | ||
// storage in redis. | ||
serialize: date => date.getTime(), | ||
deserialize: timestamp => new Date(timestamp) | ||
} | ||
@@ -45,5 +50,6 @@ ); | ||
- `clear` returns a promise (waits until redis succeeds at deleting the key) | ||
- `clear` returns a promise (waits until redis succeeds at deleting the key). Facebook Dataloader's `clear` method is synchronous. | ||
- `clearAll` is not available (redis does not have an efficient way to do this?) | ||
- `prime` will always overwrite the redis cache. It in turn calls prime on the local cache (which does not adjust the cache if the key already exists) | ||
- `prime` will always overwrite the cache. Facebook Dataloader will only write to | ||
its cache if a value is not already present. Prime is asyncronous and returns a Promise. | ||
- dataloader results must be either `null` or a JSON object. | ||
@@ -50,0 +56,0 @@ |
63
test.js
@@ -53,23 +53,2 @@ 'use strict'; | ||
describe('load', () => { | ||
it('should handle redis key expiration if set', done => { | ||
const loader = new RedisDataLoader( | ||
this.keySpace, | ||
this.userLoader(), | ||
{ cache: false, expire: 1 } | ||
); | ||
loader.load('json') | ||
.then(data => { | ||
expect(data).to.deep.equal(this.data.json); | ||
setTimeout(() => { | ||
loader.load('json') | ||
.then(data => { | ||
expect(data).to.deep.equal(this.data.json); | ||
expect(this.loadFn.callCount).to.equal(2); | ||
done(); | ||
}).done(); | ||
}, 1100); | ||
}).done(); | ||
}); | ||
it('should load json value', done => { | ||
@@ -141,2 +120,41 @@ this.loader.load('json').then(data => { | ||
}); | ||
it('should handle redis key expiration if set', done => { | ||
const loader = new RedisDataLoader( | ||
this.keySpace, | ||
this.userLoader(), | ||
{ cache: false, expire: 1 } | ||
); | ||
loader.load('json') | ||
.then(data => { | ||
expect(data).to.deep.equal(this.data.json); | ||
setTimeout(() => { | ||
loader.load('json') | ||
.then(data => { | ||
expect(data).to.deep.equal(this.data.json); | ||
expect(this.loadFn.callCount).to.equal(2); | ||
done(); | ||
}).done(); | ||
}, 1100); | ||
}).done(); | ||
}); | ||
it('should handle custom serialize and deserialize method', done => { | ||
const loader = new RedisDataLoader( | ||
this.keySpace, | ||
this.userLoader(), | ||
{ | ||
serialize: v => 100, | ||
deserialize: v => new Date(Number(v)) | ||
} | ||
); | ||
loader.load('json') | ||
.then(data => { | ||
expect(data).to.be.instanceof(Date); | ||
expect(data.getTime()).to.equal(100); | ||
done(); | ||
}).done(); | ||
}); | ||
}); | ||
@@ -187,3 +205,4 @@ | ||
it('should require a key', done => { | ||
this.loader.clear().catch(err => { | ||
this.loader.clear() | ||
.catch(err => { | ||
expect(err.message).to.equal('Key parameter is required'); | ||
@@ -190,0 +209,0 @@ done(); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16307
300
110