rate-limiter-flexible
Advanced tools
Comparing version 0.6.1 to 0.7.0
@@ -10,7 +10,7 @@ module.exports = class BlockedKeys { | ||
for(let key in this._keys) { | ||
Object.keys(this._keys).forEach((key) => { | ||
if (this._keys[key] <= now) { | ||
delete this._keys[key]; | ||
} | ||
} | ||
}); | ||
@@ -27,3 +27,3 @@ this._addedKeysAmount = Object.keys(this._keys).length; | ||
add(key, sec) { | ||
this._keys[key] = Date.now() + sec * 1000; | ||
this._keys[key] = Date.now() + (sec * 1000); | ||
this._addedKeysAmount++; | ||
@@ -30,0 +30,0 @@ if (this._addedKeysAmount > 999) { |
@@ -1,7 +0,8 @@ | ||
const expect = require('chai').expect; | ||
const { describe, it, beforeEach } = require('mocha'); | ||
const { expect } = require('chai'); | ||
const BlockedKeys = require('./BlockedKeys'); | ||
describe('BlockedKeys', function() { | ||
describe('BlockedKeys', () => { | ||
let blockedKeys; | ||
beforeEach(function() { | ||
beforeEach(() => { | ||
blockedKeys = new BlockedKeys(); | ||
@@ -40,3 +41,3 @@ }); | ||
it('collect expired on add if there more than 999 blocked keys', (done) => { | ||
for(let i = 0; i < 1000 ; i++) { | ||
for (let i = 0; i < 1000; i++) { | ||
blockedKeys.add(`key${i}`, 1); | ||
@@ -47,3 +48,4 @@ } | ||
blockedKeys.add('key1', 1); | ||
expect(Object.keys(blockedKeys._keys).length === 1 && blockedKeys._addedKeysAmount === 1).to.equal(true); | ||
expect(Object.keys(blockedKeys._keys).length === 1 && blockedKeys._addedKeysAmount === 1) | ||
.to.equal(true); | ||
done(); | ||
@@ -57,3 +59,4 @@ }, 1001); | ||
blockedKeys.msBeforeExpire('key'); | ||
expect(Object.keys(blockedKeys._keys).length === 1 && blockedKeys._addedKeysAmount === 1).to.equal(true); | ||
expect(Object.keys(blockedKeys._keys).length === 1 && blockedKeys._addedKeysAmount === 1) | ||
.to.equal(true); | ||
done(); | ||
@@ -60,0 +63,0 @@ }, 1001); |
const BlockedKeys = require('./BlockedKeys'); | ||
module.exports = BlockedKeys; | ||
module.exports = BlockedKeys; |
const MemoryStorage = require('./MemoryStorage'); | ||
module.exports = MemoryStorage; | ||
module.exports = MemoryStorage; |
@@ -20,10 +20,8 @@ const Record = require('./Record'); | ||
return new Res(this._storage[key].value, msBeforeExpires); | ||
} else { | ||
clearTimeout(this._storage[key].timeoutId); | ||
} | ||
clearTimeout(this._storage[key].timeoutId); | ||
return this.set(key, value, durationSec); | ||
} | ||
} else { | ||
return this.set(key, value, durationSec); | ||
} | ||
return this.set(key, value, durationSec); | ||
} | ||
@@ -40,3 +38,3 @@ | ||
return new Res(value, durationMs); | ||
}; | ||
} | ||
@@ -52,6 +50,5 @@ /** | ||
return new Res(this._storage[key].value, msBeforeExpires); | ||
} else { | ||
return null; | ||
} | ||
return null; | ||
} | ||
}; | ||
}; |
@@ -1,5 +0,6 @@ | ||
const expect = require('chai').expect; | ||
const { describe, it, beforeEach } = require('mocha'); | ||
const { expect } = require('chai'); | ||
const MemoryStorage = require('./MemoryStorage'); | ||
describe('MemoryStorage', function() { | ||
describe('MemoryStorage', function () { | ||
const testKey = 'test'; | ||
@@ -41,3 +42,2 @@ const val = 34; | ||
}); | ||
}); | ||
}); |
@@ -40,2 +40,2 @@ module.exports = class Record { | ||
} | ||
}; | ||
}; |
@@ -1,2 +0,3 @@ | ||
const expect = require('chai').expect; | ||
const { describe, it, beforeEach } = require('mocha'); | ||
const { expect } = require('chai'); | ||
const Record = require('./Record'); | ||
@@ -6,3 +7,3 @@ | ||
let record; | ||
beforeEach(function() { | ||
beforeEach(() => { | ||
record = new Record(); | ||
@@ -9,0 +10,0 @@ }); |
@@ -22,2 +22,2 @@ module.exports = class Res { | ||
} | ||
}; | ||
}; |
@@ -51,3 +51,3 @@ module.exports = class RateLimiterAbstract { | ||
if (typeof value !== 'string') { | ||
throw new Error("keyPrefix must be string"); | ||
throw new Error('keyPrefix must be string'); | ||
} | ||
@@ -58,3 +58,3 @@ this._keyPrefix = value; | ||
getKey(key) { | ||
return this.keyPrefix + ':' + key; | ||
return `${this.keyPrefix}:${key}`; | ||
} | ||
@@ -61,0 +61,0 @@ |
@@ -26,10 +26,8 @@ const RateLimiterAbstract = require('./RateLimiterAbstract'); | ||
reject(new RateLimiterRes(0, storageRes.msBeforeNext)); | ||
} else if (this.execEvenly && storageRes.msBeforeNext > 0 && !isFirstInDuration) { | ||
const delay = Math.ceil(storageRes.msBeforeNext / ((this.points - storageRes.consumedPoints) + 2)); | ||
setTimeout(resolve, delay, res); | ||
} else { | ||
if (this.execEvenly && storageRes.msBeforeNext > 0 && !isFirstInDuration) { | ||
const delay = Math.ceil(storageRes.msBeforeNext / ((this.points - storageRes.consumedPoints) + 2)); | ||
setTimeout(resolve, delay, res); | ||
} else { | ||
resolve(res); | ||
} | ||
resolve(res); | ||
} | ||
@@ -41,3 +39,3 @@ }); | ||
const rlKey = this.getKey(key); | ||
return new Promise((resolve, reject) => { | ||
return new Promise((resolve) => { | ||
const res = this._memoryStorage.incrby(rlKey, points, this.duration); | ||
@@ -50,3 +48,3 @@ resolve(new RateLimiterRes(this.points - res.consumedPoints, res.msBeforeNext)); | ||
const rlKey = this.getKey(key); | ||
return new Promise((resolve, reject) => { | ||
return new Promise((resolve) => { | ||
const res = this._memoryStorage.incrby(rlKey, -points, this.duration); | ||
@@ -53,0 +51,0 @@ resolve(new RateLimiterRes(this.points - res.consumedPoints, res.msBeforeNext)); |
@@ -1,5 +0,6 @@ | ||
const expect = require('chai').expect; | ||
const { describe, it } = require('mocha'); | ||
const { expect } = require('chai'); | ||
const RateLimiterMemory = require('./RateLimiterMemory'); | ||
describe('RateLimiterMemory with fixed window', function() { | ||
describe('RateLimiterMemory with fixed window', function () { | ||
this.timeout(5000); | ||
@@ -9,3 +10,3 @@ | ||
const testKey = 'consume1'; | ||
const rateLimiterMemory = new RateLimiterMemory({points: 2, duration: 5}); | ||
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5 }); | ||
rateLimiterMemory.consume(testKey) | ||
@@ -24,3 +25,3 @@ .then(() => { | ||
const testKey = 'consume2'; | ||
const rateLimiterMemory = new RateLimiterMemory({points: 1, duration: 5}); | ||
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 }); | ||
rateLimiterMemory.consume(testKey, 2) | ||
@@ -39,3 +40,3 @@ .then(() => {}) | ||
const testKey = 'consumeEvenly'; | ||
const rateLimiterMemory = new RateLimiterMemory({points: 2, duration: 5, execEvenly: true}); | ||
const rateLimiterMemory = new RateLimiterMemory({ points: 2, duration: 5, execEvenly: true }); | ||
rateLimiterMemory.consume(testKey) | ||
@@ -67,3 +68,3 @@ .then(() => { | ||
const testKey = 'penalty1'; | ||
const rateLimiterMemory = new RateLimiterMemory({points: 3, duration: 5}); | ||
const rateLimiterMemory = new RateLimiterMemory({ points: 3, duration: 5 }); | ||
rateLimiterMemory.consume(testKey) | ||
@@ -88,3 +89,3 @@ .then(() => { | ||
const testKey = 'reward1'; | ||
const rateLimiterMemory = new RateLimiterMemory({points: 1, duration: 5}); | ||
const rateLimiterMemory = new RateLimiterMemory({ points: 1, duration: 5 }); | ||
rateLimiterMemory.consume(testKey) | ||
@@ -110,6 +111,6 @@ .then(() => { | ||
const keyPrefix = 'test'; | ||
const rateLimiterMemory = new RateLimiterMemory({keyPrefix: keyPrefix, points: 1, duration: 5}); | ||
const rateLimiterMemory = new RateLimiterMemory({ keyPrefix, points: 1, duration: 5 }); | ||
expect(rateLimiterMemory.getKey(testKey)).to.equal('test:key'); | ||
}); | ||
}); | ||
}); |
@@ -5,3 +5,3 @@ const RateLimiterAbstract = require('./RateLimiterAbstract'); | ||
const handleRedisError = function(funcName, resolve, reject, key, pointsToConsume) { | ||
const handleRedisError = function (funcName, resolve, reject, key, pointsToConsume) { | ||
if (!(this.insuranceLimiter instanceof RateLimiterAbstract)) { | ||
@@ -16,7 +16,7 @@ reject(new Error('Redis Client error')); | ||
reject(res); | ||
}) | ||
}); | ||
} | ||
}; | ||
const afterConsume = function(resolve, reject, rlKey, results) { | ||
const afterConsume = function (resolve, reject, rlKey, results) { | ||
const [resSet, consumed, resTtlMs] = results; | ||
@@ -43,9 +43,7 @@ const res = new RateLimiterRes(); | ||
reject(res); | ||
} else if (this.execEvenly && res.msBeforeNext > 0 && !isFirstInDuration) { | ||
const delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2)); | ||
setTimeout(resolve, delay, res); | ||
} else { | ||
if (this.execEvenly && res.msBeforeNext > 0 && !isFirstInDuration) { | ||
const delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2)); | ||
setTimeout(resolve, delay, res); | ||
} else { | ||
resolve(res); | ||
} | ||
resolve(res); | ||
} | ||
@@ -52,0 +50,0 @@ }; |
@@ -1,6 +0,7 @@ | ||
const expect = require('chai').expect; | ||
const { describe, it, beforeEach } = require('mocha'); | ||
const { expect } = require('chai'); | ||
const RateLimiterRedis = require('./RateLimiterRedis'); | ||
const redisMock = require('redis-mock'); | ||
describe('RateLimiterRedis with fixed window', function() { | ||
describe('RateLimiterRedis with fixed window', function () { | ||
this.timeout(5000); | ||
@@ -14,8 +15,8 @@ const redisMockClient = redisMock.createClient(); | ||
multi.exec = (cb) => { | ||
cb(new Error('closed'), []) | ||
} | ||
cb(new Error('closed'), []); | ||
}; | ||
return multi; | ||
} | ||
}; | ||
} | ||
@@ -26,11 +27,10 @@ const redisClientClosedRaw = new RedisClient(); | ||
get: (func, name) => { | ||
if( name in redisClientClosedRaw ) { | ||
if (name in redisClientClosedRaw) { | ||
return redisClientClosedRaw[name]; | ||
} | ||
return function() { | ||
const args = [].slice.call(arguments); | ||
return function (...args) { | ||
const cb = args.pop(); | ||
cb(Error('closed')); | ||
} | ||
} | ||
}; | ||
}, | ||
}); | ||
@@ -44,3 +44,3 @@ | ||
const testKey = 'consume1'; | ||
const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 2, duration: 5}); | ||
const rateLimiter = new RateLimiterRedis({ redis: redisMockClient, points: 2, duration: 5 }); | ||
rateLimiter.consume(testKey) | ||
@@ -53,3 +53,3 @@ .then(() => { | ||
} | ||
}) | ||
}); | ||
}) | ||
@@ -63,3 +63,3 @@ .catch((err) => { | ||
const testKey = 'consume2'; | ||
const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 1, duration: 5}); | ||
const rateLimiter = new RateLimiterRedis({ redis: redisMockClient, points: 1, duration: 5 }); | ||
rateLimiter.consume(testKey, 2) | ||
@@ -70,3 +70,3 @@ .then(() => {}) | ||
done(); | ||
}) | ||
}); | ||
}); | ||
@@ -76,3 +76,5 @@ | ||
const testKey = 'consumeEvenly'; | ||
const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 2, duration: 5, execEvenly: true}); | ||
const rateLimiter = new RateLimiterRedis({ | ||
redis: redisMockClient, points: 2, duration: 5, execEvenly: true, | ||
}); | ||
rateLimiter.consume(testKey) | ||
@@ -104,3 +106,3 @@ .then(() => { | ||
const testKey = 'penalty1'; | ||
const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 3, duration: 5}); | ||
const rateLimiter = new RateLimiterRedis({ redis: redisMockClient, points: 3, duration: 5 }); | ||
rateLimiter.consume(testKey) | ||
@@ -115,3 +117,3 @@ .then(() => { | ||
} | ||
}) | ||
}); | ||
}) | ||
@@ -129,3 +131,3 @@ .catch((err) => { | ||
const testKey = 'reward'; | ||
const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 1, duration: 5}); | ||
const rateLimiter = new RateLimiterRedis({ redis: redisMockClient, points: 1, duration: 5 }); | ||
rateLimiter.consume(testKey) | ||
@@ -140,3 +142,3 @@ .then(() => { | ||
} | ||
}) | ||
}); | ||
}) | ||
@@ -159,3 +161,3 @@ .catch((err) => { | ||
blockOnPointsConsumed: 2, | ||
blockDuration: 10 | ||
blockDuration: 10, | ||
}); | ||
@@ -192,3 +194,3 @@ rateLimiter.consume(testKey) | ||
}) | ||
.catch((rejRes) => { | ||
.catch(() => { | ||
setTimeout(() => { | ||
@@ -214,2 +216,3 @@ rateLimiter.consume(testKey) | ||
}); | ||
rateLimiter.reward('test'); | ||
} catch (err) { | ||
@@ -228,2 +231,3 @@ expect(err instanceof Error).to.equal(true); | ||
}); | ||
rateLimiter.reward('test'); | ||
} catch (err) { | ||
@@ -238,3 +242,2 @@ expect(err instanceof Error).to.equal(true); | ||
const redisClientClosed = new RedisClient(); | ||
const rateLimiter = new RateLimiterRedis({ | ||
@@ -257,3 +260,2 @@ redis: redisClientClosed, | ||
const redisClientClosed = new RedisClient(); | ||
const rateLimiter = new RateLimiterRedis({ | ||
@@ -266,4 +268,4 @@ redis: redisClientClosed, | ||
duration: 2, | ||
redis: redisMockClient | ||
}) | ||
redis: redisMockClient, | ||
}), | ||
}); | ||
@@ -277,3 +279,3 @@ | ||
}) | ||
.catch((rejRes) => {done(rejRes)}); | ||
.catch((rejRes) => { done(rejRes); }); | ||
}); | ||
@@ -291,8 +293,8 @@ | ||
duration: 2, | ||
redis: redisMockClient | ||
}) | ||
redis: redisMockClient, | ||
}), | ||
}); | ||
rateLimiter.penalty(testKey) | ||
.then((res) => { | ||
.then(() => { | ||
redisMockClient.get(rateLimiter.getKey(testKey), (err, consumedPoints) => { | ||
@@ -303,5 +305,5 @@ if (!err) { | ||
} | ||
}) | ||
}); | ||
}) | ||
.catch((rejRes) => {done(rejRes)}); | ||
.catch((rejRes) => { done(rejRes); }); | ||
}); | ||
@@ -319,10 +321,10 @@ | ||
duration: 2, | ||
redis: redisMockClient | ||
}) | ||
redis: redisMockClient, | ||
}), | ||
}); | ||
rateLimiter.consume(testKey, 2) | ||
.then((res) => { | ||
.then(() => { | ||
rateLimiter.reward(testKey) | ||
.then((res) => { | ||
.then(() => { | ||
redisMockClient.get(rateLimiter.getKey(testKey), (err, consumedPoints) => { | ||
@@ -333,7 +335,7 @@ if (!err) { | ||
} | ||
}) | ||
}); | ||
}) | ||
.catch((rejRes) => {done(rejRes)}); | ||
.catch((rejRes) => { done(rejRes); }); | ||
}) | ||
.catch((rejRes) => {done(rejRes)}); | ||
.catch((rejRes) => { done(rejRes); }); | ||
}); | ||
@@ -344,7 +346,6 @@ | ||
const keyPrefix = 'test'; | ||
const rateLimiter = new RateLimiterRedis({keyPrefix: keyPrefix, redis: redisClientClosed}); | ||
const rateLimiter = new RateLimiterRedis({ keyPrefix, redis: redisClientClosed }); | ||
expect(rateLimiter.getKey(testKey)).to.equal('test:key'); | ||
}); | ||
}); | ||
}); |
module.exports = class RateLimiterRes { | ||
constructor(remainingPoints, msBeforeNext) { | ||
this.remainingPoints = typeof remainingPoints === 'undefined' ? 0 : remainingPoints ; // Remaining points in current duration | ||
this.msBeforeNext = typeof msBeforeNext === 'undefined' ? 0 : msBeforeNext ; // Milliseconds before next action | ||
this.remainingPoints = typeof remainingPoints === 'undefined' ? 0 : remainingPoints; // Remaining points in current duration | ||
this.msBeforeNext = typeof msBeforeNext === 'undefined' ? 0 : msBeforeNext; // Milliseconds before next action | ||
} | ||
@@ -6,0 +6,0 @@ |
@@ -1,2 +0,3 @@ | ||
const expect = require('chai').expect; | ||
const { describe, it, beforeEach } = require('mocha'); | ||
const { expect } = require('chai'); | ||
const RateLimiterRes = require('./RateLimiterRes'); | ||
@@ -6,3 +7,3 @@ | ||
let rateLimiterRes; | ||
beforeEach(function() { | ||
beforeEach(() => { | ||
rateLimiterRes = new RateLimiterRes(); | ||
@@ -12,3 +13,4 @@ }); | ||
it('setup defaults on construct', () => { | ||
expect(rateLimiterRes.msBeforeNext === 0 && rateLimiterRes.remainingPoints === 0).to.be.true; | ||
expect(rateLimiterRes.msBeforeNext === 0 && rateLimiterRes.remainingPoints === 0) | ||
.to.be.equal(true); | ||
}); | ||
@@ -15,0 +17,0 @@ |
{ | ||
"name": "rate-limiter-flexible", | ||
"version": "0.6.1", | ||
"version": "0.7.0", | ||
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications", | ||
@@ -9,3 +9,5 @@ "main": "index.js", | ||
"debug-test": "mocha --inspect-brk lib/**/**.test.js", | ||
"coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls" | ||
"coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls", | ||
"eslint": "node_modules/eslint/bin/eslint.js --quiet lib/*", | ||
"eslint-fix": "node_modules/eslint/bin/eslint.js --fix lib/*" | ||
}, | ||
@@ -35,2 +37,7 @@ "repository": { | ||
"coveralls": "^3.0.1", | ||
"eslint": "^4.19.1", | ||
"eslint-config-airbnb-base": "^12.1.0", | ||
"eslint-plugin-import": "^2.7.0", | ||
"eslint-plugin-node": "^6.0.1", | ||
"eslint-plugin-security": "^1.4.0", | ||
"istanbul": "^0.4.5", | ||
@@ -37,0 +44,0 @@ "mocha": "^5.1.1", |
@@ -216,1 +216,9 @@ [![Build Status](https://travis-ci.org/animir/node-rate-limiter-flexible.png)](https://travis-ci.org/animir/node-rate-limiter-flexible) | ||
Returns Promise, where result is consumed points in current duration | ||
## Contribution | ||
Make sure you've launched `npm run eslint`, before creating PR. | ||
You can try to run `npm run eslint-fix` to fix some issues. | ||
Appreciated, feel free! |
Sorry, the diff of this file is not supported yet
96489
28
997
223
10