Awesome
Treacherous
A modern async validation framework with a raft of features for node or the browser.
Treacherous is an attempt to bring some consistency to validation in the javascript world, allowing you to write your validation rules for your models once, and re-use them on any platform with any framework.
Benefits
- Fully async validation
- Separation of rules and validation allowing composable rule sets
- Supports nested complex objects/arrays
- Outside in validation, does not augment your models in any way
- Works in browser or server
- Write once, use anywhere
- Can be integrated with any view framework (i.e vue, knockout, aurelia etc)
- Generic pipeline for localization customization
Features
Reactive Validation
Treacherous can optionally watch your model to see if any properties it is validating change and automatically re-validate those fields when changed, even in nested objects or arrays.
Composite Rules / Virtual Properties
Support for validating at property level using normal rules or for creating composite rules which are applied at the model level, allowing you to validate multiple properties within the same context.
Predicate Based Validation
You can specify predicates to only apply certain validation rules when criteria are met allowing your model validity to be contextual and flexible.
Property Alias'
This is mainly for when you are using treacherous within the browser, but you can provide alias' to properties so when errors are reported the property alias is displayed rather than the actual property name, as who wants to see hasConfirmedTermsAndConditions
when you could just alias that field as T&Cs
.
Full Support for Typescript
The whole of Treacherous was written in typescript and can bring some nice time saving features to typescript users like async/await
and lambda style interactions.
Don't worry if you dont use/like typescript, you can still use all of treacherous' features as if it were a native javascript framework.
Installing
Via NPM
Just do an npm install @treacherous/core
In browser
As this is distributed as a commonjs module it is recommended that you consume it via your existing module loader, or in the scenario where you do not have one it is recommended that you use webpack to just package it up as a UMD module to consume with a global name, this may automatically happen when typescript 2.0 provides this functionality for UMD modules out of the box.
Simple Examples
Validating simple models
import {createRuleset, createGroup} from "@treacherous/core";
const simpleModel = {
foo: 20,
bar: "hello"
};
const ruleset = createRuleset()
.forProperty("foo")
.addRule("required") // The property is required
.addRule("maxValue", 20) // The property needs a value <= 20
.forProperty("bar")
.addRule("maxLength", 5) // The property neds a length <= 5
.build();
const validationGroup = createGroup()
.build(simpleModel, ruleset);
validationGroup.validate()
.then((isValid) => {
console.log(isValid); // should write true
});
Ruleset Shorthand
const ruleset = createRuleset()
.forProperty("foo")
.required()
.maxValue(20)
.forProperty("bar")
.maxLength(5)
.build()
Validating simple arrays in models
const simpleModel = {
foo: [10, 20, 30]
};
const ruleset = createRuleset()
.forProperty("foo")
.addRule("maxLength", 5) // The array can only contain <= 5 elements
.addRuleForEach("maxValue", 20) // Each element needs a value <= 20
.build();
const validationGroup = createGroup().build(simpleModel, ruleset);
validationGroup.getModelErrors(true) // the true value indicates a full revalidation
.then((errors) => {
console.log(errors); // should contain { "foo[2]": "<some error about max value>" }
});
Nested validation on the fly
const complexObject = {
foo: {
bar: "hello"
}
};
const ruleset = createRuleset()
.forProperty("foo")
.then(fooBuilder => {
fooBuilder.forProperty("bar")
.required()
.maxLength(2)
})
.build();
const validationGroup = createGroup().build(complexObject, ruleset);
validationGroup.getModelErrors(true)
.then((errors) => {
console.log(errors); // should contain { "foo.bar": "<some error about max length>" }
});
Creating Validation Groups
The validation group is the object which manages validation state, you can find out a lot more information on this within the docs.
Here are a few simple examples to save you trawling the docs.
Check current validity
const validationGroup = createGroup()
.build(...);
validationGroup.validate()
.then((isValid) => {
// true is valid, false is invalid
));
Get all errors
const validationGroup = createGroup()
.build(...);
validationGroup.getModelErrors()
.then((propertyErrors) => {...));
Subscribe validation changes
const validationGroup = createGroup()
.build(...);
validationGroup.propertyStateChangedEvent.subscribe((propertyValidationChangedEvent) => {...));
Typescript users
As typescript users you can get some nicer features and intellisense so you can create typed rules allowing you to use lambda style property location like so:
const ruleset = createRuleset<SomeModel>()
.addProperty(x => x.SomeProperty)
.required()
.matches(x => x.SomeOtherProperty)
.build();
You can also make use of async/await
for almost all async methods like so:
const modelErrors = await validationGroup.getModelErrors();
console.log(modelErrors);
Validation rules
The framework comes with built in validators for the following:
date
- The value is expressible as a datedecimal
- The value is expressible as a float/singleemail
- The value conforms to a valid email addressequal
- The value is equal to another valueisoDate
- The value conforms to a valid ISO date formatmaxLength
- The value must have a length <= valuemaxValue
- The value must have a value <= valueminLength
- The value must have a length >= valueminValue
- The value must have a value >= valuenotEqual
- The value is not equal to another valuenumber
- The value is expressible as an integerregex
- The value matches the regex patternrequired
- The value is not a null-like valuestep
- The value conforms to the numeric steps providedmatches
- The value must match another property in the model
Creating custom rules
Creating custom rules is pretty easy, you need to:
- Implement
IValidationRule
with your custom logic (JS users just match the signatures of the interface) - Add validation messages for supported locales
- Register your rule with the ruleRegistry
There is a whole doc on the subject which can be found in the docs section.
Localization
There is a whole doc on the subject, but at a high level BY DEFAULT treacherous will pre-load the en-us
locale for you which will be used by the library, but you can easily supplement that locale, or register and use new locales. You can also completely replace the default localization handler, but see the docs for more info on this.
Documentation
Just look in the docs
folder for more documentation on certain scenarios or subject matters.
Related Libraries
This library is the core treacherous framework, which purely handles the validation of models, however there are a few other libraries which build on top of this such as:
-
treacherous-decorators (Allows validation rules to be defined by decorators i.e
@withRule(..)
) -
treacherous-view (Convention based classes for view framework integration)
-
treacherous-knockout (Knockout bindings for treacherous)
-
treacherous-vue (Vue plugin for treacherous)
-
treacherous-aurelia (Aurelia plugin for treacherous)
Developing
If you want to develop it further just clone the repo, npm install
and gulp
it should then provide you
a working version to play with. If you want to minify it you can do gulp minify
which will minify the
output files, we don't minify by default.
You can also run gulp run-tests
which will run the tests to make sure everythign works as expected.
Credits
"Mountains" Icon courtesy of The Noun Project, by Aleksandr Vector, under CC 3.0