Fluent type verification for JavaScript.
Typester is inspired by the check-types library, but seeks to improve it by:
- Using a fluent declarative syntax that makes the type checking code easier to read, reducing the impulse developers otherwise have to unit-test their type verification code.
- Throwing appropriately typed errors so that developers that still do want to unit-test their type-verification code can easily do so.
- Providing rich textual error messages that inform developers why an argument has been deemed to be invalid.
- Failing if a method is invoked with more arguments than are expected.
- Using a smaller set of verification methods to do the same thing.
- Having better performance by using a hot-spotting mechanism that eventually causes type-verifications to be disabled within code hot spots.
- Leveraging topiarist to:
- Allow a single
isA()
check to be used for what would otherwise have to be done with bothinstanceof
andtypeof()
checks, as appropriate. - Provide a
fulfills()
method that can verify any object using shape-based duck typing. - Allow
isA()
checks to efficiently verify multiple inheritance, including sand-boxed mix-ins and implemented interfaces, for applications that used topiarist to set these up.
- Allow a single
Here's how you might use Typester to implement addEventListener()
:
var typester = require('typester');
var verifyAddEventListenerArgs = typester.createVerifier(function(verify) {
verify('target').fulfills(Postable);
verify('listener').isA(Function);
verify('useCapture').optionally.isA(Boolean);
});
Window.prototype.addEventListener = function(target, listener, useCapture) {
verifyAddEventListenerArgs(target, listener, useCapture);
// implement the actual method...
};
Some things to note here:
- The
isA()
method can be used to do bothinstanceof
checks against objects, andtypeof
checks against literal values. - The
fulfills()
method can be used do shape-based checks. - The
optionally
modifier can be used for optional arguments. - It's the developer's responsibility to ensure the
verify()
statements are performed in the correct order — the provided argument name is only used for informational purposes when an error is thrown. - it's the developer's responsibility to ensure that verifications are provided for all arguments — otherwise an error will be thrown.
Here's the list of core verifiers you can use in Typester:
isA()
classIsA()
fulfills()
classFulfills()
and here's the list of validating-verifiers that come with Typester:
object()
number()
positiveNumber()
negativeNumber()
integerNumber()
nonEmptyString()
nonEmptyArray()
Validating verifiers are usually some additional check over and above a simple isA()
invocation. The object()
and number()
verifiers are worth mentioning, as these enhance isA(Object)
and isA(Number)
respectively. By ensuring that typeof(object) == true
in the case of object()
, and by ensuring failure for NaN
, NEGATIVE_INFINITY
and POSITIVE_INFINITY
in the case of number()
.
Custom verifiers can be added using the typester.addCustomVerifier()
method. For example, here's how you might create a custom validating-verifier for email addresses:
var typester = require('typester');
var ValidationError = typester.ValidationError;
typester.addCustomVerifier({
isEmailAddress: function() {
this.isA(String);
if(this.argValue.indexOf('@') == -1) throw new ValidationError(this.argName +
' argument must be a valid email address');
}
})
Subsequently, our custom isEmailAddress()
verifier might be used like this:
var typester = require('typester');
var verifyPersonArgs = typester.createVerifier(function(verify) {
verify('name').isA(String);
verify('email').isEmailAddress();
});
function Person(name, email) {
verifyPersonArgs(name, email);
this.name = name;
this.email = email;
}
Typester verifications that fail will always throw one of the following four error types:
ArgumentError
: if a mandatory argument isn't provided.TypeError
: ifisA()
,classIsA()
,fulfills()
orclassFulfills()
fail.ValidationError
: if an argument is correctly typed but fails further validation.RangeError
: a special case ofValidationError
for validating-verifiers likepositiveNumber()
andnegativeNumber()
.
Typester has been written with performance in mind. On my machine using Chrome, I found that it adds about a tenth of a microsecond of execution-time per verified argument, when compared with doing the same verification using plain JavaScript. However, this is still inferior to statically typed languages where all type verification is performed at compile-time. To put JavaScript on a more level footing with such languages, typester includes a hot-spotting mechanism that disables type verification for methods that are being invoked thousands of times a second — actually a hundred times within a tenth of a second.
The hotspotting mechanism has the benefit that most unique function invocations will be verified at least once, akin to what is done in statically typed languages.
If you use NPM then you should install as follows:
npm install typester
and start making use of typester using the following code:
var typester = require('typester');
Alternatively, you can download the latest release, unzip, and start making use of typester using the following code:
<script src="dist/typester.js"></script>
If you've installed via NPM you can run the tests as follows:
npm test
which runs all the tests in Node.js (using mocha), and the main spec tests in Firefox and Chrome (using karma). You can run the tests against Firefox and Chrome, with automatic re-runs as files are changed, using the command:
npm run test-browser
Finally, you also have the option to manually open 'spec/index.html' in the browser of your choice. To ensure that changes to the source code automatically cause the bundles to be re-built, you can run:
npm run watch
If you haven't installed via NPM then there isn't too much point to running the tests since any changes to the source code won't be reflected. However, if you want to anyway, you can run the tests by opening 'spec/index.html' in the browser of your choice.