rolling-window-throttler
Advanced tools
Comparing version 1.0.0 to 1.0.1
50
index.js
'use strict'; | ||
module.exports = require('./lib/rolling-window-throttler'); | ||
const parseDuration = require('parse-duration'); | ||
module.exports = options => { | ||
const throttler = new RollingWindowThrottler(options); | ||
return { | ||
tryAcquire: key => throttler.tryAcquire(key) | ||
}; | ||
}; | ||
class RollingWindowThrottler { | ||
constructor(options) { | ||
this.max = options.max; | ||
this.durationWindow = RollingWindowThrottler.calculateDuration(options.durationWindow); | ||
this.invocations = {}; | ||
} | ||
tryAcquire(key) { | ||
const invocation = this.getOrElseCreate(key); | ||
RollingWindowThrottler.incrementInvocationCount(invocation); | ||
if (this.isExpires(invocation)) { | ||
delete this.invocations[key]; | ||
return this.tryAcquire(key); | ||
} | ||
return invocation.count <= this.max; | ||
} | ||
isExpires(invocation) { | ||
return ((Date.now() - invocation.timestamp) > this.durationWindow) | ||
} | ||
getOrElseCreate(key) { | ||
return this.invocations[key] || (this.invocations[key] = RollingWindowThrottler.newInvocation()); | ||
} | ||
static newInvocation() { | ||
return { | ||
count: 0, | ||
timestamp: Date.now() | ||
}; | ||
} | ||
static incrementInvocationCount(invocation) { | ||
invocation.count++; | ||
} | ||
static calculateDuration(durationWindow) { | ||
return isNaN(durationWindow) ? parseDuration(durationWindow) : durationWindow; | ||
} | ||
} |
{ | ||
"name": "rolling-window-throttler", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "rolling window throttler for node server", | ||
@@ -27,4 +27,5 @@ "main": "index.js", | ||
"chai": "~3.5.0", | ||
"mocha": "~3.1.0" | ||
"mocha": "~3.1.0", | ||
"sinon": "~1.17.6" | ||
} | ||
} |
@@ -14,3 +14,3 @@ # Rolling Window Throttler [![Build Status](https://img.shields.io/travis/wix/rolling-window-throttler-js/master.svg?label=build%20status)](https://travis-ci.org/wix/rolling-window-throttler-js) [![npm version](https://badge.fury.io/js/rolling-window-throttler-js.svg)](https://badge.fury.io/js/rolling-window-throttler-js) | ||
const rollingWindowThrottler = require('rolling-window-throttler') | ||
const throttler = rollingWindowThrottler.get({max: 1, durationWindow: '1s'}) | ||
const throttler = rollingWindowThrottler({max: 1, durationWindow: '1s'}) | ||
@@ -24,3 +24,3 @@ if (throttler.tryAcquire('some-key')) { | ||
### get({max, durationWindow}): RollingWindowThrottler | ||
### ({max, durationWindow}): RollingWindowThrottler | ||
Create new instance of `RollingWindowThrottler`. | ||
@@ -27,0 +27,0 @@ |
'use strict'; | ||
const expect = require('chai').expect, | ||
factory = require('..'); | ||
rollingWindowThrottler = require('..'), | ||
sinon = require('sinon'); | ||
describe('rolling window throttler', () => { | ||
const key = '192.168.2.1'; | ||
const aThrottler = (clock, durationWindow) => factory.get({ | ||
max: 1, | ||
durationWindow: durationWindow, | ||
clock: clock | ||
}); | ||
let throttler; | ||
beforeEach(function () { | ||
this.clock = new FakeClock(); | ||
this.throttler = aThrottler(this.clock, 1000); | ||
}); | ||
beforeEach(() => throttler = aThrottler(1000)); | ||
it('Should allow single request', function () { | ||
expect(this.throttler.tryAcquire(key)).to.be.true; | ||
it('Should allow single request', () => { | ||
expect(throttler.tryAcquire(key)).to.equal(true); | ||
}); | ||
it('Should throttle second request', function () { | ||
this.throttler.tryAcquire(key); | ||
expect(this.throttler.tryAcquire(key)).to.be.false; | ||
it('Should throttle second request', () => { | ||
throttler.tryAcquire(key); | ||
expect(throttler.tryAcquire(key)).to.equal(false); | ||
}); | ||
it('should allow second request but from different key', function () { | ||
it('should allow second request but from different key', () => { | ||
const anotherKey = '200.200.200.1'; | ||
expect(this.throttler.tryAcquire(key)).to.be.true; | ||
expect(this.throttler.tryAcquire(anotherKey)).to.be.true; | ||
expect(throttler.tryAcquire(key)).to.equal(true); | ||
expect(throttler.tryAcquire(anotherKey)).to.equal(true); | ||
}); | ||
it('should re-allow request after the rolling window', function () { | ||
this.throttler.tryAcquire(key); | ||
this.clock.age(2000); | ||
expect(this.throttler.tryAcquire(key)).to.be.true; | ||
}); | ||
it('should work with verbal durationWindow and not milliseconds', function () { | ||
const throttler = aThrottler(this.clock, '1s'); | ||
it('should re-allow request after the rolling window', sinon.test(function() { | ||
throttler.tryAcquire(key); | ||
this.clock.age(2000); | ||
expect(throttler.tryAcquire(key)).to.be.true; | ||
}); | ||
this.clock.tick(2000); | ||
expect(throttler.tryAcquire(key)).to.equal(true); | ||
})); | ||
it('should work with verbal durationWindow and not milliseconds', sinon.test(function() { | ||
throttler = aThrottler('1s'); | ||
throttler.tryAcquire(key); | ||
this.clock.tick(2000); | ||
expect(throttler.tryAcquire(key)).to.equal(true); | ||
})); | ||
it('Not providing clock should work, has default clock', function () { | ||
const throttler = aThrottler(null, 1000); | ||
expect(throttler.tryAcquire(key)).to.be.true; | ||
}); | ||
}); | ||
class FakeClock { | ||
constructor() { | ||
this.currentTime = new Date().getTime(); | ||
function aThrottler(durationWindow) { | ||
return rollingWindowThrottler({max: 1, durationWindow: durationWindow || 1000}); | ||
} | ||
age(ms) { | ||
this.currentTime = this.currentTime + ms; | ||
} | ||
currentMillis() { | ||
return this.currentTime; | ||
} | ||
} | ||
}); |
Sorry, the diff of this file is not supported yet
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
7244
3
8
75