trent
v0.0.2
Published
Type specifications
Downloads
5
Maintainers
Readme
Trent
Type specifications
npm install trent
This library provides runtime type specifications with a focus on:
- A small, native-feeling syntax for defining specifications
- Good error messages
Usage
import {createSpec} from 'trent'
const Matrix = createSpec(() => [[Number]])
const errors = Matrix.getErrors([
[1, 2, 3],
[4, 5, 'wrong']
])
console.log(errors)
/* => [ Error('Value[1][2] must be of type "number"') ] */
Documentation
createSpec(builder: builtInChecks -> Check): Spec
Create a Spec
using the builder
function which receives the built-in checks described below. The builder must return a valid check or an error throws. The Spec
object returned has a method getErrors(value)
which returns an array of type errors for value
.
Checks
- Value checks
- Built-in checks
- Custom checks
Spec
Check that a provided value matches the Spec
.
import {createSpec} from 'trent'
import assert from 'assert'
const Num = createSpec(() => Number)
const Matrix = createSpec(() => [[Num]])
const errors = Matrix.getErrors([[1, 2, 3], [4, 5, 6]])
assert(errors.length === 0)
Constructor
Check that a provided value is an instanceof Constructor
, or typeof
if the constructor is for a primitive such as Number
or String
.
import {createSpec} from 'trent'
import assert from 'assert'
const Num = createSpec(() => Number)
const errors = Num.getErrors(8)
assert(errors.length === 0)
Note: Any Constructor will work if root[Constructor.name] === Constructor
where root
is the window
in browsers and global
in Node.
Array [check]
Check that a provided value is an array where every element passes the check
.
import {createSpec} from 'trent'
import assert from 'assert'
const Numbers = createSpec(() => [Number])
const errors = Numbers.getErrors([8])
assert(errors.length === 0)
Object {key: check, ..., keyN: checkN}
Check that a provided value is an object where every key
value passes its check
.
import {createSpec} from 'trent'
import assert from 'assert'
const Point = createSpec(() => ({x: Number, y: Number}))
const errors = Point.getErrors({x: 8, y: 8})
assert(errors.length === 0)
is(value : any)
Check that a provided value must strictly equal (===
) the value
.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({is}) => is(8))
const errors = Eight.getErrors(8)
assert(errors.length === 0)
or(checks : [Check])
Check that a provided value matches at least one of the checks
.
import {createSpec} from 'trent'
import assert from 'assert'
const NumberOrString = createSpec(({or}) => or([Number, String]))
const errors = NumberOrString.getErrors(8)
assert(errors.length === 0)
and(checks : [Check])
Check that a provided value matches at every one of the checks
.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({and, is}) => and([Number, is(8)]))
const errors = Eight.getErrors(8)
assert(errors.length === 0)
not(check : Check)
Check that a provided value does not pass the check
.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({not, is}) => not(is(9)))
const errors = Eight.getErrors(8)
assert(errors.length === 0)
maybe(check : Check)
Check that a provided value matches check
or is null
or undefined
.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({maybe, is}) => maybe(is(8)))
assert(Eight.getErrors(8).length === 0)
assert(Eight.getErrors(null).length === 0)
assert(Eight.getErrors(undefined).length === 0)
tuple(checks : [Check])
Check that a provided value is an array with length equal to checks.length
and the first element passes the first check and so on.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({tuple, is}) => tuple([is(8), is(8)]))
assert(Eight.getErrors([8, 8]).length === 0)
nullable(check : Check)
Check that a provided value matches check
or is null
.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({nullable, is}) => nullable(is(8)))
assert(Eight.getErrors(8).length === 0)
assert(Eight.getErrors(null).length === 0)
voidable(check : Check)
Check that a provided value matches check
or is undefined
.
import {createSpec} from 'trent'
import assert from 'assert'
const Eight = createSpec(({voidable, is}) => voidable(is(8)))
assert(Eight.getErrors(8).length === 0)
assert(Eight.getErrors(undefined).length === 0)
Using checkCustomCheck({descriptor: String, isValid: Function})
Create a check where descriptor
fits the sentence {subject} must be {descriptor}
for error messages and isValid(value: any)
returns whether the value passes the check.
import {createSpec, createCustomCheck} from 'trent'
import assert from 'assert'
const lowercase = createCustomCheck({
descriptor: 'lowercase',
isValid (x) {
return typeof x === 'string' && x.toLowerCase() === x
}
})
const Code = createSpec(() => lowercase)
assert(Code.getErrors('8').length === 0)
Note: Custom checks cannot nest checks within them. In a tree structure analogy, custom checks must be leaves. The reason for this limitation is the complexity of creating good error messages.
createDependentSpecs(builder : builtInChecks -> {Check}) {Spec}
Create a collection of Spec
s which are dependent and/or recursive.
For example, we have two types Foo
and Bar
which both can contain each other. We can try to write this system with createSpec
:
import {createSpec} from 'trent'
var Foo = createSpec(() => ({barList: [Bar]}))
var Bar = createSpec(() => ({fooList: [Foo]}))
Creating Foo
with undefined Bar
will not work. We need createDependentSpecs
to enable a "late-binding" where order does not matter. We use the ref
built-in check available to createDependentSpecs
to reference Spec
s.
import {createDependentSpecs} from 'trent'
import assert from 'assert'
const {Foo} = createDependentSpecs(({ref}) => ({
Foo: {barList: [ref('Bar')]},
Bar: {fooList: [ref('Foo')]}
}))
assert(Foo.getErrors({
barList: [{
fooList: [{
barList: [{
fooList: []
}]
}]
}]
}).length === 0)
Real-life example
I maintain the HTML parser Himalaya which follows a strict specification for its output. The output contains Nodes which can have children Nodes, so we need a recursive type.
import {
createSpec,
createDependentSpecs
} from 'trent'
// I pull this out to show that you can
const Text = createSpec(({is}) => ({
type: is('text'),
content: String
}))
export const {Node} = createDependentSpecs(({is, or, ref, nullable}) => ({
Node: or([
ref('Element'),
ref('Comment'),
Text
]),
Element: {
type: is('element'),
tagName: String,
children: [ref('Node')],
attributes: [{
key: String,
value: nullable(String)
}]
},
Comment: {
type: is('comment'),
content: String
}
}))