Skip to content
Type specifications
JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
test
.gitignore
.travis.yml
LICENSE
README.md
package.json

README.md

Trent

Type specifications

npm install trent

npm Build Status Greenkeeper badge

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


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 Specs 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 Specs.

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
  }
}))
You can’t perform that action at this time.