2.8.4 • Published 4 years ago

sails-hook-req-validate v2.8.4

Weekly downloads
347
License
MIT
Repository
github
Last release
4 years ago

Build Status dependencies dev dependencies

Downloads Downloads npm version License


Ultimate Request Validator Hook for SailsJS

Over 160 Validator Types & 17 Converters + Flexible Usages & Custom Options and Configurations

This sails-hook-req-validate package uses req.allParams() (Sails Document).

FYI, sails' req.allParams() returns the value of all parameters sent in the request which includes querystring and url path. Which means passing parameters may be string type, use converter to convert the type to an expected value type. But please note that some validation types have build-in converters, check validation type list for more information.

For example: /api/user/:age/:ismale or /api/user?age=12&ismale=true. req.allParams() returns all parameters as string, however sails-hook-req-validate can correctly validate the type and convert to the correct output type in most cases.

Installation

  npm install sails-hook-req-validate --save 

This is it! Once you npm install the hook will be automatically activated. However, if you want more control, see below for the config/validate.js setting.

Report Bug & Suggest Improvement

Please feel free to leave any comment and suggestion . https://github.com/JohnKimDev/sails-hook-req-validate/issues/new



Optional Global Setting (config/validate.js)

You can create a config/validate.js file in TWO different ways. Please note that the local, (req.validate() configuration) can overwrite the global configuration.

OPTION 1: Simple Object Notation (no response & request objects will be passed)

/**
 * Sails-Hook-Req-Validate Global Configuration
 * 
 * For more information on the settings in this file, see:
 * https://github.com/JohnKimDev/sails-hook-req-validate/blob/master/readme.md
 **/

module.exports.validate = {
  /***************************************************************************
  * When validation error occurs, the program can send theres.badRequest 
  * response automatically. 
  * 
  * By Default, `sendResponse` is enabled.
  ***************************************************************************/
  // sendResponse: true,

  /***************************************************************************
  * After the validation check, the program returns data as object or you 
  * can use a callback. But it can return the data as a PROMISE if you set
  * `usePromise` as true.
  * 
  * By Default, `usePromise` is disabled.
  ***************************************************************************/
  // usePromise: false,

  /***************************************************************************
  * When there are more incoming request parameters than listed in the
  * `req.validate` option, by default, it will return all incoming parameters
  * If you disable `returnAllParams` option, it will filter parameters and only
  * return the parameters that are listed in the `req.validate` validate option
  * 
  * By Default, `returnAllParams` is enabled.
  ***************************************************************************/
  // returnAllParams: true,

  /***************************************************************************
  * Error output format 
  * errMessage : ((string)) short explanation of a reason for the invalidation
  * invalidKeys : ((array))[string] list of invalid parameter key(s)
  * 
  * Output can be any format you want. The return value from `onErrorOutput` 
  * will be passed to the final error return.
  ***************************************************************************/
  // onErrorOutput: function (errMessage, invalidKeys) {
  //   return { message: errMessage, invalid: invalidKeys };
  // },

  /***************************************************************************
  * Required error message output
  * keys : ((string)) or ((array))[string] one or more list of invalid 
  *        parameter key(s)
  * 
  * [output] ((string)) 
  ***************************************************************************/
  // requiredErrorMessage: function(keys) {
  //   keys = keys || [];
  //   let isare = (keys.length > 1) ? 'are' : 'is';
  //   let s = (keys.length > 1) ? 's' : ''
  //   return `The "${keys.join('", "')}" parameter${s} ${isare} required.`;
  // },

  /***************************************************************************
  * req.validate validation format error message output
  * key : ((string)) invalid parameter key
  * 
  * [output] ((string)) 
  ***************************************************************************/
  // formatErrorMessage: function(key) {
  //   return `The "${key}" parameter has an invalid format.`;
  // },

  /***************************************************************************
  * Validation configuration format error message output
  * key : ((string)) invalid parameter key
  * typeMessage: ((string)) types[key].message from `validationTypes.js`
  * https://github.com/JohnKimDev/sails-hook-req-validate/blob/master/lib/validationTypes.js
  * 
  * [output] ((string)) 
  ***************************************************************************/
  // typeErrorMessage: function(key, typeMessage) {
  //   let a = (typeMessage && typeMessage.length) ? /[aeiouAEIOU]/.test(typeMessage.charAt(0)) ? 'an' : 'a' : '';
  //   return `The "${key}" parameter should be ${a} ${typeMessage}.`;
  // },

  /***************************************************************************
  * Incoming request parameter invalid error message output
  * key : ((string)) invalid parameter key
  * typeMessage: ((string)) types[key].message from `validationTypes.js`
  * https://github.com/JohnKimDev/sails-hook-req-validate/blob/master/lib/validationTypes.js
  * 
  * [output] ((string)) 
  ***************************************************************************/
  // inputErrorMessage: function(key, typeMessage) {
  //   let a = (typeMessage && typeMessage.length) ? /[aeiouAEIOU]/.test(typeMessage.charAt(0)) ? 'an' : 'a' : '';
  //   return `The "${key}" parameter has an invalid input type` + (typeMessage ? `, it should be ${a} ${typeMessage}` : '') + '.';
  // },

  /***************************************************************************
  * Incoming request parameter invalid error message output of OR validation
  * example: 'string|number`
  * orKey : ((string)) invalid parameter key
  * orTypeMessages: ((string)) combined types[key].message from `validationTypes.js`
  * https://github.com/JohnKimDev/sails-hook-req-validate/blob/master/lib/validationTypes.js
  * 
  * [output] ((string)) 
  ***************************************************************************/
  // orInputErrorMessage: function(orKey, orTypeMessages) {
  //   return `Invalid input type, it should be one of the following types; ${orTypeMessages}.`;
  // }
};

OPTION 2: Function return (response & request objects will be passed)

/**
 * Sails-Hook-Req-Validate Global Configuration
 * 
 * For more information on the settings in this file, see:
 * https://github.com/JohnKimDev/sails-hook-req-validate/blob/master/readme.md
 * 
 * The response and request objects will be passed to the configuration function when initializes
 **/

module.exports.validate = function(req, res) {
  return {
    /***************************************************************************
    * When `sendResponse` is enabled. The program will use this `responseMethod`
    * to send the error data out. By default, it will use res.badRequest. 
    * The `res` object is from the passing parameter with is a response object
    * from the controller.
    * 
    * You can overwrite the `responseMethod` with another response function or 
    * create your own response handler.
    * example: 
    *    responseMethod: (data) => {
    *       sails.log.error(data);
    *       res.set(400).send(data);
    *    }
    ***************************************************************************/
    // responseMethod: res.badRequest

    /**
     * You can use all other configurations from the OPTION 1 example
     **/
  };
};

USAGE - Validator

sails-hook-req-validate is very flexible and can be used in many different formats and configurations.


New in 2.8.x

Function Validation

const params = req.validate({
  'evenNum': (val) => { return (val % 2 === 0); }   // you can also use a function instead of an arrow function
}); 

// PRO TIP: (val) => { return (val % 2 === 0); } can be shorten to (val) => (val % 2 === 0) 

Single parameter

const params = req.validate('id'); 

Multiple parameters

const params = req.validate(['id', 'firstname', 'lastname']); 

Simple validator

const params = req.validate({
  'id': 'base64|number|boolean' // OR operation
  'name': ['string', 'email'],  // AND operation
  'email?': 'email',            // OPTIONAL parameter
  'zipcode': 'postalcode'
}); 

Combined validators

const params = req.validate({
  'name': ['string', { default: 'John Doe' }],  // default value if missing 
  'email?': ['email', { converter: 'normalizeEmail' }],   // optional parameter & with converter 
  'type': { enum: ['user', 'admin', 'manager'], default: 'user' } 
}); 

Combined validators as array

const params = req.validate([
  { 'name': ['string', { default: 'John Doe' }] },  // default value if missing 
  { 'email?': ['email', { converter: 'normalizeEmail' }] },   // optional parameter & with converter 
  { 'type': { enum: ['user', 'admin', 'manager'], default: 'user' } }
]); 

Multiple converters

const params = req.validate({
  'email': ['any', { converter: ['string', 'normalizeEmail'] }]
}); 
// not a good example but I hope you get the idea

Custom converter (set FUNCTION to converter)

const params = req.validate({
  'phone':  { converter: (val) => { 
    var match = val.replace(/\D/g, '').match(/^(\d{3})(\d{3})(\d{4})$/);
    return '(' + match[1] + ') ' + match[2] + '-' + match[3]; // (123) 456-7890
  }}
}); 

Validator & Converter Options

OptionDescription
enum((array)) any combination of expected values
default((string|number|boolean)) default value if the parameter is missing
converter((array|function)) converter name as string or array of string can be used. You can pass function for custom converter.You can also mixed types + functions in an array.
validator((function)) if you need a custom validator, use this option@param param ((any)): passing parameter@return ((boolean)): return true if valid, false otherwise.

More Usage Examples With Options

You can use with any of validator combination above. req.validate(<TYPE_VALIDATOR>, <CALLBACK>)orreq.validate(<TYPE_VALIDATOR>, <OPTION>, <CALLBACK>)

USAGE (synchronous) - DIRECT

Only for direct method, onErrorOutput will be ignored! When an error occurs, false will be returned.

const params = req.validate('id');
if (params === false) { 
  sails.log.error('The validation failed');
} else {
  sails.log.info('ID:', params.id);
}

USAGE (asynchronous) - CALLBACK (error, params)

req.validate('id', (err, params) => {
  // callback
  if (err) {                    // err object is the output of `onErrorOutput`
    sails.log.error('The validation failed.', err.message); 
  } else {
    sails.log.info(params.id);   // callback data
  }
});

USAGE (asynchronous) - PROMISE

You can use the local configuration to enable promise return or use the global configuration.

ES5 Promise

req.validate('id', {
  usePromise: true  
})
.then(params => {
  // callback
  sails.log.info(params.id); // forwarded parameters if the validation passes
})
.catch(err => {
  sails.log.error(err);     // err object is the output of `onErrorOutput`
});

ES6 Async/Await Promise

try {
  const params = await req.validate('id', {
    usePromise: true        // or use the global setting
  });
  sails.log.info(params.id);
catch (err) {
  sails.log.error(err);     // err object is the output of `onErrorOutput`
}  

My Personal Favorite Usage Method

I like to wrap the controller codes with try-catch in case of unexpected error and use promise for sails-hook-req-validate

// config/validate.js
module.exports.validate = {
  sendResponse: false,
  usePromise: true,
  returnAllParams: false,
  onErrorOutput: (errMessage, invalidKeys) => errMessage
};

// in someController.js
module.exports = {
  index: async (req, res) => {
    try {
      const params = await req.validate({
        'id': 'string',
        'name': 'string'
      });
      console.log(params.id, params.name);
      return res.ok();
    } catch (err) {
      return res.serverError(err);
    }
  }
};

Setting Options / Configurations

OptionDescription
responseMethod((function)) method/function to call when a validation error occursdefault: res.badRequest@param errorOutput : output from onErrorOutput option
sendResponse((boolean)) toggle the responseMethod call when a validation error occursdefault: true
usePromise((boolean)) enable PROMISE response instead of callbackdefault: false
returnAllParams((boolean)) if ther are more passing params than listed in the req.validate, you can choose to pass-though all or filter to only listed params.default: true
onErrorOutput((function)) when a validation error occurs, it will be called to generate the error outputdefault: see above config/validation.js example@param errMessage ((string)): combined error message(s)@param invalidKeys ((string[])): list of invalid param keys@return : any form you want
requiredErrorMessage((function)) output message for param required error messagedefault: see above config/validation.js example@param keys ((string | string[])): list of invalid param key(s).@return ((string)): formated error messgae as string type
formatErrorMessage((function)) output message for a validator format error message for param keydefault: see above config/validation.js example@param key ((string)): invalid param key.@param typeMessage ((string)): type error message, see message for each type of the validationType file@return ((string)): formated error messgae as string type
typeErrorMessage((function)) output message for a parm type error messagedefault: see above config/validation.js example@param key ((string)): invalid param key.@param typeMessage ((string)): type error message, see message for each type of the validationType file@return ((string)): formated error messgae as string type
inputErrorMessage((function)) output message for a parm input error messagedefault: see above config/validation.js example@param key ((string)): invalid param key.@param typeMessage ((string)): type error message, see message for each type of the validationType file@return ((string)): formated error messgae as string type
orInputErrorMessage((function)) output message for a parm input error message with OR opertation. The error message for each OR validation wil be combineddefault: see above config/validation.js example@param orKey ((string)): invalid OR param key. (ex: 'string|email')@param orTypeMessage ((string)): combined type error messages, see message for each type of the validationType file@return ((string)): formated error messgae as string type

Validators

List of preset available validators.

NOTE: all validators are case insensitive

ValidatorDescription
alphacheck if the string contains only letters (a-zA-Z).
alphanumericcheck if the string contains only letters and numbers.
asciicheck if the string contains ASCII chars only.
arraycheck if the value is array format. The validator uses lodash _.isArray to validate the type.
anyreturn true for any type except undefined.
base64check if a string is base64 encoded.
boleancheck if a string is a boolean.
byteLengthcheck if the string's length (in UTF-8 bytes) falls in a range.
creditcardcheck if the string is a credit card.
currencycheck if the string is a valid currency amount. Currency symbol is optional.
currencyWithSymbolcheck if the string is a valid currency amount. Currency symbol is required.
dataURIcheck if the string is a data uri format.
magnetURIcheck if the string is a magnet uri format.
emailcheck if the string is an valid email address.
FQDNcheck if the string is a fully qualified domain name (e.g. domain.com)
floatcheck if the string is a float.
hashcheck if the string is a hash of type algorithm.Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']
hexColorcheck if the string is a hexadecimal color.
hexadecimalcheck if the string is a hexadecimal number.
ipcheck if the string is an IP (either version 4 or 6).
ipv4check if the string is an IP version 4 only).
ipv6check if the string is an IP version 6 only.
ISBNcheck if the string is an ISBN (either version 10 or 13).
ISBN10check if the string is an ISBN version 10 only.
ISBN13check if the string is an ISBN version 13 only.
ISSNcheck if the string is an ISSN.
ISINcheck if the string is an ISIN (stock/security identifier).
ISO8601check if the string is a valid ISO 8601 date
RFC3339check if the string is a valid RFC 3339 date.
ISO31661Alpha2check if the string is a valid ISO 3166-1 alpha-2 officially assigned country code.
ISO31661Alpha3check if the string is a valid ISO 3166-1 alpha-3 officially assigned country code.
ISRCcheck if the string is a ISRC.
intcheck if the string is an integer.Built-In Converter: int
JSONcheck if the string is valid JSON(note: uses JSON.parse).
JWTcheck if the string is valid JWT token.
LatLongcheck if the string is a valid latitude-longitude coordinate in the format lat,long or lat, long.
lowercasecheck if the string is lowercase.
MACAddresscheck if the string is a MAC address.
MD5check if the string is a MD5 hash.
MimeTypecheck if the string matches to a valid MIME type format
mobilePhonecheck if the string is a mobile phone number (all locales).
mobilephone-ar-AEcheck if the string is a mobile phone number of ar-AE locale
mobilephone-ar-DZcheck if the string is a mobile phone number of ar-DZ locale
mobilephone-ar-EGcheck if the string is a mobile phone number of ar-EG locale
mobilephone-ar-IQcheck if the string is a mobile phone number of ar-IQ locale
mobilephone-ar-JOcheck if the string is a mobile phone number of ar-JO locale
mobilephone-ar-KWcheck if the string is a mobile phone number of ar-KW locale
mobilephone-ar-SAcheck if the string is a mobile phone number of ar-SA locale
mobilephone-ar-SYcheck if the string is a mobile phone number of ar-SY locale
mobilephone-ar-TNcheck if the string is a mobile phone number of ar-TN locale
mobilephone-be-BYcheck if the string is a mobile phone number of be-BY locale
mobilephone-bg-BGcheck if the string is a mobile phone number of bg-BG locale
mobilephone-bn-BDcheck if the string is a mobile phone number of bn-BD locale
mobilephone-cs-CZcheck if the string is a mobile phone number of cs-CZ locale
mobilephone-de-DEcheck if the string is a mobile phone number of de-DE locale
mobilephone-da-DKcheck if the string is a mobile phone number of da-DK locale
mobilephone-el-GRcheck if the string is a mobile phone number of el-GR locale
mobilephone-en-AUcheck if the string is a mobile phone number of en-AU locale
mobilephone-en-CAcheck if the string is a mobile phone number of en-CA locale
mobilephone-en-GBcheck if the string is a mobile phone number of en-GB locale
mobilephone-en-GHcheck if the string is a mobile phone number of en-GH locale
mobilephone-en-HKcheck if the string is a mobile phone number of en-HK locale
mobilephone-en-IEcheck if the string is a mobile phone number of en-IE locale
mobilephone-en-INcheck if the string is a mobile phone number of en-IN locale
mobilephone-en-KEcheck if the string is a mobile phone number of en-KE locale
mobilephone-en-MUcheck if the string is a mobile phone number of en-MU locale
mobilephone-en-NGcheck if the string is a mobile phone number of en-NG locale
mobilephone-en-NZcheck if the string is a mobile phone number of en-NZ locale
mobilephone-en-RWcheck if the string is a mobile phone number of en-RW locale
mobilephone-en-SGcheck if the string is a mobile phone number of en-SG locale
mobilephone-en-UGcheck if the string is a mobile phone number of en-UG locale
mobilephone-en-UScheck if the string is a mobile phone number of en-US locale
mobilephone-en-TZcheck if the string is a mobile phone number of en-TZ locale
mobilephone-en-ZAcheck if the string is a mobile phone number of en-ZA locale
mobilephone-en-ZMcheck if the string is a mobile phone number of en-ZM locale
mobilephone-en-PKcheck if the string is a mobile phone number of en-PK locale
mobilephone-es-EScheck if the string is a mobile phone number of es-ES locale
mobilephone-es-MXcheck if the string is a mobile phone number of es-MX locale
mobilephone-es-UYcheck if the string is a mobile phone number of es-UY locale
mobilephone-et-EEcheck if the string is a mobile phone number of et-EE locale
mobilephone-fa-IRcheck if the string is a mobile phone number of fa-IR locale
mobilephone-fi-FIcheck if the string is a mobile phone number of fi-FI locale
mobilephone-fr-FRcheck if the string is a mobile phone number of fr-FR locale
mobilephone-he-ILcheck if the string is a mobile phone number of he-IL locale
mobilephone-hu-HUcheck if the string is a mobile phone number of hu-HU locale
mobilephone-it-ITcheck if the string is a mobile phone number of it-IT locale
mobilephone-ja-JPcheck if the string is a mobile phone number of ja-JP locale
mobilephone-kk-KZcheck if the string is a mobile phone number of kk-KZ locale
mobilephone-ko-KRcheck if the string is a mobile phone number of ko-KR locale
mobilephone-lt-LTcheck if the string is a mobile phone number of lt-LT locale
mobilephone-ms-MYcheck if the string is a mobile phone number of ms-MY locale
mobilephone-nb-NOcheck if the string is a mobile phone number of nb-NO locale
mobilephone-nn-NOcheck if the string is a mobile phone number of nn-NO locale
mobilephone-pl-PLcheck if the string is a mobile phone number of pl-PL locale
mobilephone-pt-PTcheck if the string is a mobile phone number of pt-PT locale
mobilephone-pt-BRcheck if the string is a mobile phone number of pt-BR locale
mobilephone-ro-ROcheck if the string is a mobile phone number of ro-RO locale
mobilephone-ru-RUcheck if the string is a mobile phone number of ru-RU locale
mobilephone-sl-SIcheck if the string is a mobile phone number of sl-SI locale
mobilephone-sk-SKcheck if the string is a mobile phone number of sk-SK locale
mobilephone-sr-RScheck if the string is a mobile phone number of sr-RS locale
mobilephone-sv-SEcheck if the string is a mobile phone number of sv-SE locale
mobilephone-th-THcheck if the string is a mobile phone number of th-TH locale
mobilephone-tr-TRcheck if the string is a mobile phone number of tr-TR locale
mobilephone-uk-UAcheck if the string is a mobile phone number of uk-UA locale
mobilephone-vi-VNcheck if the string is a mobile phone number of vi-VN locale
mobilephone-zh-CNcheck if the string is a mobile phone number of zh-CN locale
mobilephone-zh-HKcheck if the string is a mobile phone number of zh-HK locale
mobilephone-zh-TWcheck if the string is a mobile phone number of zh-TW locale
mongoIdcheck if the string is a valid hex-encoded representation of a MongoDB ObjectId.
multibytecheck if the string contains one or more multibyte chars.
numericcheck if the string contains only numbers. Some symbols are allow (e.g. +, -, or .).
numericOnlycheck if the string contains only numbers 0-9. No symbol allow.
numbersame as numeric
objectcheck if value is a plain object. The validator uses lodash _.isPlainObject to validate the type.
portcheck if the string is a valid port number.
postalCodecheck if the string is a postal code (all locales).
postalCode-ADcheck if the string is a postal code of AD
postalCode-ATcheck if the string is a postal code of AT
postalCode-AUcheck if the string is a postal code of AU
postalCode-BEcheck if the string is a postal code of BE
postalCode-BGcheck if the string is a postal code of BG
postalCode-CAcheck if the string is a postal code of CA
postalCode-CHcheck if the string is a postal code of CH
postalCode-CZcheck if the string is a postal code of CZ
postalCode-DEcheck if the string is a postal code of DE
postalCode-DKcheck if the string is a postal code of DK
postalCode-DZcheck if the string is a postal code of DZ
postalCode-EScheck if the string is a postal code of ES
postalCode-FIcheck if the string is a postal code of FI
postalCode-FRcheck if the string is a postal code of FR
postalCode-GBcheck if the string is a postal code of GB
postalCode-GRcheck if the string is a postal code of GR
postalCode-ILcheck if the string is a postal code of IL
postalCode-IScheck if the string is a postal code of IS
postalCode-ITcheck if the string is a postal code of IT
postalCode-JPcheck if the string is a postal code of JP
postalCode-KEcheck if the string is a postal code of KE
postalCode-LIcheck if the string is a postal code of LI
postalCode-MXcheck if the string is a postal code of MX
postalCode-NLcheck if the string is a postal code of NL
postalCode-NOcheck if the string is a postal code of NO
postalCode-PLcheck if the string is a postal code of PL
postalCode-PTcheck if the string is a postal code of PT
postalCode-ROcheck if the string is a postal code of RO
postalCode-RUcheck if the string is a postal code of RU
postalCode-SAcheck if the string is a postal code of SA
postalCode-SEcheck if the string is a postal code of SE
postalCode-TWcheck if the string is a postal code of TW
postalCode-UScheck if the string is a postal code of US
postalCode-ZAcheck if the string is a postal code of ZA
postalCode-ZMcheck if the string is a postal code of ZM
surrogatePaircheck if the string contains any surrogate pairs chars.
stringcheck if value is string. BE CAUTIOUS of using this type! May return invalid result. For the parameter values from a querystring and a URL path are always STRING type, using this validator type may have an unexpected result.
URLcheck if the string is an URL. allow protocols 'http', 'https', 'ftp'.
UUIDcheck if the string is a UUID (version 3, 4 or 5).
UUIDv3check if the string is a UUID version 3
UUIDv4check if the string is a UUID version 4
UUIDv5check if the string is a UUID version 5
uppercasecheck if the string is uppercase.

Converters

List of converters available.

NOTE: all converters are case insenstive

ConverterDescription
booleanconvert the input string to a boolean. Everything except for '0', 'false' and '' returns true. In strict mode only '1' and 'true' return true.
dateconvert the input string to a date, or null if the input is not a date.
escapereplace <, >, &, ', " and / with HTML entities.
unescapereplaces HTML encoded entities with <, >, &, ', " and /.
JSONconvert to JSON using JSON.parse
JSON5convert to JSON using JSON5.parse
ltrimtrim characters from the left-side of the input.
rtrimtrim characters from the right-side of the input.
removeHTMLstrip HTML codes from the string
removeSpaceremove all white space from the string
removeLineBreakremove all line breaks from the string, \r\n\t, \n, \t\t
normalizeEmailcanonicalizes an email address. (This doesn't validate that the input is an email, if you want to validate the email use email validator beforehand).
lowercaseconvert to lower case
stringconvert to string
intconvert the input string to an integer, or NaN if the input is not an integer.
floatconvert the input string to a float, or NaN if the input is not a float.
trimtrim characters (whitespace by default) from both sides of the input.
uppercaseconvert to upper case
2.8.4

4 years ago

2.8.3

4 years ago

2.8.2

4 years ago

2.8.1

4 years ago

2.8.0

4 years ago

2.7.1

5 years ago

2.7.0

5 years ago

2.6.5

5 years ago

2.6.4

5 years ago

2.6.3

5 years ago

2.6.2

5 years ago

2.6.1

5 years ago

2.6.0

5 years ago

2.5.2

5 years ago

2.5.1

5 years ago

2.5.0

5 years ago

2.4.2

5 years ago

2.4.1

5 years ago

2.4.0

5 years ago

2.3.0

5 years ago

2.2.2

5 years ago

2.2.1

5 years ago

2.2.0

5 years ago

2.1.2

5 years ago

2.1.1

5 years ago

2.1.0

5 years ago

2.0.6

5 years ago

2.0.5

5 years ago

2.0.4

5 years ago

2.0.3

5 years ago

2.0.2

5 years ago

2.0.1

5 years ago

2.0.0

5 years ago

1.6.1

5 years ago

1.6.0

5 years ago

1.5.2

5 years ago

1.5.1

5 years ago

1.5.0

5 years ago

1.4.0

5 years ago

1.3.3

5 years ago

1.3.2

5 years ago

1.3.1

5 years ago

1.3.0

5 years ago

1.2.1

5 years ago

1.2.0

5 years ago

1.1.0

5 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago

0.2.1

8 years ago

0.2.0

8 years ago

0.1.5

8 years ago

0.1.4

8 years ago

0.1.3

8 years ago

0.1.2

8 years ago

0.1.1

8 years ago

0.1.0

8 years ago