What's a type? In tcomb a type is represented by a function T
such that:
- has signature
T(value)
where value depends on the nature ofT
- is idempotent, that is
T(T(value)) = T(value)
- owns a static function
T.is(x)
returningtrue
ifx
is an instance ofT
Every type defined with tcomb
owns a static meta
member containing at least the following properties:
kind
an enum containing the type kind ('irreducible'
,'struct'
, etc...)name
an optional string, useful for debugging purposesidentity
a boolean,true
if the type can be treated as the identity function in production builds
See the documentation of each combinator for specific additional properties.
Note. Meta objects are a distinctive feature of tcomb, allowing runtime type introspection.
Signature
(guard: boolean, message?: string | () => string) => void
Example
const x = 0;
t.assert(x !== 0, 'cannot divide by x'); // => throws '[tcomb] cannot divide by x'
Signature
(message: string) => void
Note. You can change the default behaviour when an assert fails overriding the t.fail
function.
t.String
: stringst.Number
: numberst.Integer
: integerst.Boolean
: booleanst.Array
: arrayst.Object
: plain objectst.Function
: functionst.Error
: errorst.RegExp
: regular expressionst.Date
: datest.Nil
:null
orundefined
t.Any
: any valuet.Type
: atcomb
type
Signature
(name: string, predicate: (x: any) => boolean) => TcombType
Example. Representing a native Promise
:
const PromiseType = t.irreducible('PromiseType', (x) => x instanceof Promise);
The meta
object
{
kind: 'irreducible',
name: name,
identity: true,
predicate: predicate
}
Note. All the built-in types exported by tcomb
are defined through the irreducible
combinator.
Signature
(type: tcombType, predicate: (x: any) => boolean, name?: string) => TcombType
Example. Representing a positive number:
const Positive = t.refinement(t.Number, (n) => n >= 0, 'Positive');
The meta
object
{
kind: 'subtype',
name: name,
identity: ...depends on type,
type: type,
predicate: predicate
}
Signature
(map: Object, name?: string) => TcombType
where map
is a hash whose keys are the enums (values are free).
Example
const Country = t.enums({
IT: 'Italy',
US: 'United States'
}, 'Country');
The meta
object
{
kind: 'enums',
name: name,
identity: true,
map: map
}
If you don't care of values you can use enums.of
:
(keys: string | Array<string | number>, name?: string) => TcombType
where keys
is the array of enums or a string where the enums are separated by spaces.
Example
// values will mirror the keys
const Country = t.enums.of('IT US', 'Country');
// same as
const Country = t.enums.of(['IT', 'US'], 'Country');
// same as
const Country = t.enums({
IT: 'IT',
US: 'US'
}, 'Country');
In tcomb
optional values of type T
can be represented by union([Nil, T])
. Since it's very common to handle optional values, tcomb
provide an ad-hoc combinator.
Signature
(type: tcombType, name?: string) => TcombType
Example
const Person = t.struct({
name: t.String,
age: t.maybe(t.Number) // an optional number
});
Person({ name: 'Giulio' }); // => ok
Person({ name: 'Giulio', age: null }); // => ok
Person({ name: 'Giulio', age: undefined }); // => ok
Person({ name: 'Giulio', age: 'a string' }); // => throws
The meta
object
{
kind: 'maybe',
name: name,
identity: ...depends on type,
type: type
}
Signature
type Options = {
name?: string,
strict?: boolean,
defaultProps?: Object
};
(props: {[key: string]: TcombType;}, options?: string | Options) => TcombType
Example
const Point = t.struct({
x: t.Number,
y: t.Number
}, 'Point');
// point is immutable, the new keyword is optional
const point = Point({ x: 1, y: 2 });
Note. Point.is
internally uses instanceof
.
The meta
object
{
kind: 'struct',
name: options.name,
identity: false,
props: props,
strict: options.strict,
defaultProps: options.defaultProps
}
If strict = true
then no additional props are allowed:
const Point = t.struct({
x: t.Number,
y: t.Number
}, 'Point');
Point({ x: 1, y: 2, z: 3 }); // => ok
const Point = t.struct({
x: t.Number,
y: t.Number
}, { name: 'Point', strict: true });
Point({ x: 1, y: 2, z: 3 }); // => throws '[tcomb] Invalid additional prop "z" supplied to Point'
Methods are defined as usual:
Point.prototype.toString = function () {
return `(${this.x}, ${this.y})`;
};
Every struct constructor owns an extend
function:
type Props = {[key: String]: Type};
type Mixin = Props | TcombStruct | TcombInterface | refinement(Mixin);
type Options = {
name?: string,
strict?: boolean,
defaultProps?: Object
};
extend(mixins: Mixin | Array<Mixin>, options?: : string | Options) => TcombStruct
Example
const Point3D = Point.extend({ z: t.Number }, 'Point3D');
// multiple inheritance
const A = struct({...});
const B = struct({...});
const MixinC = {...};
const MixinD = {...};
const E = A.extend([B, MixinC, MixinD]);
Note. extend
supports prototypal inheritance:
const Rectangle = t.struct({
width: t.Number,
height: t.Number
});
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
const Cube = Rectangle.extend({
thickness: t.Number
});
// typeof Cube.prototype.getArea === 'function'
Cube.prototype.getVolume = function () {
return this.getArea() * this.thickness;
};
Note. Repeated props are not allowed (unless they are strictly equal):
const Wrong = Point.extend({ x: t.String }); // => throws '[tcomb] Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "x" of target object'
Alternatively you can use the t.struct.extend(mixins: Array<Mixin>, options?: string | Options) => TcombType
function:
const Point3D = t.struct.extend([Point, { z: t.Number }], 'Point3D');
Note. Repeated defaultProps overwrite previously set values:
const Base = t.struct(
{ value: t.String },
{ defaultProps: { value: "base" }}
);
const Extended = Base.extend(
{},
{ defaultProps: { value: "override-extended" }}
);
const DeepExtended = t.struct.extend(
[Base, Extended, {}],
{ defaultProps: { value: "override-deep" }}
);
Base({}).value; // equals 'base'
Extended({}).value; // equals 'override-extended'
DeepExtended({}).value; // equals 'override-deep'
Note. The implementation uses the top level function extend(combinator, mixins, options)
defined in tcomb/lib/extend
Signature
(types: Array<TcombType>, name?: string) => TcombType
Note. Instances of tuples are plain old JavaScript arrays.
Example
const Area = t.tuple([t.Number, t.Number]);
// area is immutable
const area = Area([1, 2]);
The meta
object
{
kind: 'tuple',
name: name,
identity: ...depends on types,
types: types
}
Signature
(type: TcombType, name?: string) => TcombType
Note. Instances of lists are plain old JavaScript arrays.
Example
const Path = t.list(Point);
// path is immutable
const path = Path([
{x: 0, y: 0}, // tcomb automatically hydrates using the `Point` constructor
{x: 1, y: 1}
]);
The meta
object
{
kind: 'list',
name: name,
identity: ...depends on type,
type: type
}
Signature
(domain: TcombType, codomain: TcombType, name?: string) => TcombType
Note. Instances of dicts are plain old JavaScript objects.
Example
const Phones = t.dict(t.String, t.Number);
// phones is immutable
const phones = Tel({ 'jack': 4098, 'sape': 4139 });
The meta
object
{
kind: 'dict',
name: name,
identity: ..depends on domain and codomain,
domain: domain,
codomain: codomain
}
Signature
(types: Array<TcombType>, name?: string) => TcombType
Example
const IncrementAction = t.struct({ step: t.Number });
const DecrementAction = t.struct({ step: t.Number });
const Actions = t.union([IncrementAction, DecrementAction])
The meta
object
{
kind: 'union',
name: name,
identity: ...depends on types,
types: types
}
In order to use a union as a constructor you must implement the dispatch
static function:
(x: any) => TcombType
Example
Actions.dispatch = function dispatch(x) {
const typeToConstructor = {
'increment': IncrementAction,
'decrement': DecrementAction
};
return typeToConstructor[x.type];
};
const incrementAction = Actions({ type: 'increment', step: 1 });
Note. tcomb
provides a default implementation of dispatch
which you can override.
Signature
(types: Array<TcombType>, name?: string) => TcombType
Example
const Min = t.refinement(t.String, (s) => s.length > 2);
const Max = t.refinement(t.String, (s) => s.length < 5);
const MinMax = t.intersection([Min, Max]);
MinMax.is('abc'); // => true
MinMax.is('a'); // => false
MinMax.is('abcde'); // => false
The meta
object
{
kind: 'intersection',
name: name,
identity: ...depends on types,
types: types
}
There is an alias (inter
) for IE8 compatibility.
Differences from structs
is
doesn't leverageinstanceof
, structural typing is used instead- doesn't filter additional props
- also checks prototype keys
Signature
type Options = {
name?: string,
strict?: boolean
};
(props: {[key: string]: TcombType;}, options?: string | Options) => TcombType
Example
const Foo = t.interface({
x: t.Number,
y: t.Number
}, 'Foo');
var foo = Foo({ x: 1, y: 2 }); // => { x: 1, y: 2 }
foo instanceof Foo; // => false (it's a pojo)
Foo.is({ x: 1, y: 2 }); // => true
// allows additional props
Foo.is({ x: 1, y: 2, z: 3 }); // => true
// checks types
Foo.is({ x: 1, y: '2' }); // => false
// doesn't allow missing props
Foo.is({ x: 1 }); // => false
const Point = t.struct({
x: t.Number,
y: t.Number
}, 'Point');
const Bar = t.interface({
point: Point
}, 'Bar');
// hydrates prop values
const bar = Bar({ point: {x: 0, y: 0} });
Point.is(bar.point); // => true
const Serializable = t.interface({
serialize: t.Function
})
Point.prototype.serialize = function () {
...
};
function doSerialize(serializable: Serializable) {
...
}
doSerialize(bar.point); // => ok
The meta
object
{
kind: 'interface',
name: options.name,
identity: ...depends on props,
props: props,
strict: options.strict
}
If strict = true
then no additional props or prototype keys are allowed:
Foo({ x: 1, y: 2, z: 3 }); // => ok
const Foo = t.interface({
x: t.Number,
y: t.Number
}, { name: 'Foo', strict: true });
Foo({ x: 1, y: 2, z: 3 }); // => throws '[tcomb] Invalid additional prop "z" supplied to Foo'
Foo(new Point({ x: 1, y: 2 })); // => throws '[tcomb] Invalid additional prop "serialize" supplied to Foo'
Every interface constructor owns an extend
function:
type Props = {[key: String]: Type};
type Mixin = Props | TcombStruct | TcombInterface | refinement(Mixin);
type Options = {
name?: string,
strict?: boolean
};
extend(mixins: Mixin | Array<Mixin>, options?: string | Options) => TcombStruct
Example
const Point3D = Point.extend({ z: t.Number }, 'Point3D');
// multiple inheritance
const A = interface({...});
const B = struct({...});
const MixinC = {...};
const MixinD = {...};
const E = A.extend([B, MixinC, MixinD]);
Note. Repeated props are not allowed (unless they are strictly equal):
const Wrong = Point.extend({ x: t.String }); // => throws '[tcomb] Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "x" of target object'
Alternatively you can use the t.interface.extend(mixins: Array<Mixin>, options?: string | Options) => TcombType
function:
const Point3D = t.interface.extend([Point, { z: t.Number }], 'Point3D');
Note. The implementation uses the top level function extend(combinator, mixins, options)
defined in tcomb/lib/extend
Signature
(domain: Array<TcombType>, codomain: TcombType, name?: string) => TcombType
Example
Typed functions may be defined like this:
// add takes two `t.Number`s and returns a `t.Number`
const add = t.func([t.Number, t.Number], t.Number)
.of((x, y) => x + y);
And used like this:
add("Hello", 2); // Raises error: Invalid `Hello` supplied to `t.Number`
add("Hello"); // Raises error: Invalid `Hello` supplied to `t.Number`
add(1, 2); // Returns: 3
add(1)(2); // Returns: 3
You can define a typed function using the func(domain, codomain, name?)
combinator where:
domain
is the type of the function's argument (or list of types of the function's arguments)codomain
is the type of the function's return valuename
: is an optional string useful for debugging purposes
Returns a function type whose functions have their domain and codomain specified and constrained.
func
can be used to define function types using native types:
// An `A` takes a `t.String` and returns an `t.Number`
const A = t.func(t.String, t.Number);
The domain and codomain can also be specified using types from any combinator including func
:
// A `B` takes a `Func` (which takes a `t.String` and returns a `t.Number`) and returns a `t.String`.
const B = t.func(t.func(t.String, t.Number), t.String);
// An `ExcitedString` is a `t.String` containing an exclamation mark
const ExcitedString = t.refinement(t.String, (s) => s.indexOf('!') !== -1, 'ExcitedString');
// An `Exciter` takes a `t.String` and returns an `ExcitedString`
const Exciter = t.func(t.String, ExcitedString);
Additionally the domain can be expressed as a list of types:
// A `C` takes an `A`, a `B` and a `t.String` and returns a `t.Number`
const C = t.func([A, B, t.String], t.Number);
The meta
object
{
kind: 'func',
name: name,
identity: true,
domain: domain,
codomain: codomain
}
func(A, B).of(f);
Returns a function where the domain and codomain are typechecked against the function type.
If the function is passed values which are outside of the domain or returns values which are outside of the codomain it will raise an error:
const simpleQuestionator = Exciter.of((s) => s + '?');
const simpleExciter = Exciter.of((s) => s + '!');
// Raises error:
// Invalid `Hello?` supplied to `ExcitedString`, insert a valid value for the refinement
simpleQuestionator('Hello');
// Raises error: Invalid `1` supplied to `String`
simpleExciter(1);
// Returns: 'Hello!'
simpleExciter('Hello');
The returned function may also be partially applied passing a curried
additional param:
// We can reasonably suggest that add has the following type signature
// add : t.Number -> t.Number -> t.Number
const add = t.func([t.Number, t.Number], t.Number)
.of((x, y) => x + y, true);
const addHello = add("Hello"); // As this raises: "Error: Invalid `Hello` supplied to `t.Number`"
const add2 = add(2);
add2(1); // And this returns: 3
func(A, B).is(x);
Returns true
if x belongs to the type.
Exciter.is(simpleExciter); // Returns: true
Exciter.is(simpleQuestionator); // Returns: true
const id = (x) => x;
t.func([t.Number, t.Number], t.Number).is(func([t.Number, t.Number], t.Number).of(id)); // Returns: true
t.func([t.Number, t.Number], t.Number).is(func(t.Number, t.Number).of(id)); // Returns: false
- Typed functions' domains are checked when they are called
- Typed functions' codomains are checked when they return
- The domain and codomain of a typed function's type is checked when the typed function is passed to a function type (such as when used as an argument in another typed function)
t.declare([name])
declares a type name to be used in other combinators without requiring a definition right away. This enables the construction of recursive or mutually recursive types.
const Tree = t.declare('Tree');
Tree.define(t.struct({
value: t.Number,
left: t.maybe(Tree),
right: t.maybe(Tree)
}));
const bst = Tree({
value: 5,
left: {
value: 2
},
right: {
left: {
value: 6
},
value: 7
}
});
const A = t.declare('A');
const B = t.struct({
a: t.maybe(A)
});
A.define(t.struct({
b: t.maybe(B)
}));
Warning. Do not try to type-check structures with circular references, that would blow the stack.
Warning. If you define a union with a custom dispatch
, do define the dispatch
function before calling define
const U = t.declare('U')
// good
U.dispatch = (x) => t.String.is(x) ? t.String : U
U.define(t.union([t.String, t.list(U)]))
// bad
// U.dispatch = (x) => t.String.is(x) ? t.String : U
You can update an immutable instance with the provided update
function:
MyTcombType.update(instance, patch)
The following commands are compatible with the Facebook Immutability Helpers:
$push
$unshift
$splice
$set
$apply
$merge
Example:
const p = Point({ x: 1, y: 2 });
p = Point.update(p, {x: { '$set': 3 }}); // => { x: 3, y: 2 }
Note. $apply
can be used only with shallow cloneable values, i.e. Object
s, Array
s and primitive values (counterexample: an instance of Date
is not shallow cloneable).
Removing a value from a dict
const MyType = dict(t.String, t.Number);
const instance = MyType({ a: 1, b: 2 });
const updated = MyType.update(instance, { $remove: ['a'] }); // => { b: 2 }
Swapping two list elements
const MyType = list(t.Number);
const instance = MyType([1, 2, 3, 4]);
const updated = MyType.update(instance, { '$swap': { from: 1, to: 2 } }); // => [1, 3, 2, 4]
Adding other commands
You can add your custom commands updating the t.update.commands
hash.
Signature
(x: any, ...cases: Array<Case>) => any
where each Case
has the following structure:
type, [guard], block
type
a tcomb typeguard
an optional predicate(x: any) => any
block
a function(x: any) => any
called when the match succeeded
Example
const A = t.struct({...});
const result = t.match(1,
t.String, (s) => 'a string',
t.Number, (n) => n > 2, (n) => 'a number gt 2', // case with a guard (optional)
t.Number, (n) => 'a number lte 2',
A, (a) => 'an instance of A',
t.Any, (x) => 'other...' // catch all
);
console.log(result); // => 'a number lte 2'
Note. If a match is not found an error will be raised.
Immutability helper for POJOs.
Signature
update(instance: Object, patch: Object) => Object
Example
const x = { a: 1 };
t.update(x, { a: { $set: 2 } }); // => { a: 2 }, x is untouched
Note. You can change the default behaviour overriding the t.update
function:
t.update = function (instance, patch) {
// your implementation here
};
Returns the name of a tcomb type.
Signature
(type: TcombType) => string
Example
t.getTypeName(t.String); // => 'String'
If a name is not specified when defining the type, a default name will be provided according to http://flowtype.org syntax for type annotations.
t.getTypeName(t.maybe(t.String)); // => ?String
t.getTypeName(t.struct({ name: t.String, surname: t.String })); // => '{name: String, surname: String}'
t.getTypeName(t.union([t.String, t.Number])); // => String | Number
t.getTypeName(t.dict(t.String, t.Number)); // => {[key: String]: Number}
...
Safe version of mixin, properties cannot be overwritten.
Signature
(target: Object, source: Object, unsafe?: boolean = false) => Object
Example
t.mixin({ a: 1 }, { b: 2 }); // => { a: 1, b: 2 }
t.mixin({ a: 1 }, { a: 2 }); // => throws
t.mixin({ a: 1 }, { a: 1 }); // => ok
t.mixin({ a: 1 }, { a: 2 }, true); // { a: 2 }
Used internally to format the error messages.
Signature
(x: any) => String
Since by default JSON.stringify
is used, in a performance intensive application you may want to override it:
// override with a less verbose but much faster function
t.stringify = (x) => x.toString();
Returns true
if x
is a tcomb type.
Signature
(x: any) => boolean
Returns true
if x
is an instance of type
.
Signature
(x: any, type: TcombType) => boolean
Generic deserialization function.
Signature
(value: any, type: TcombType) => type
Example
import fromJSON from 'tcomb/lib/fromJSON'
const Person = t.struct({
name: t.String,
birthDate: Date
});
const source = {
name: 'Giulio',
birthDate: new Date(1973, 10, 30)
};
const json = JSON.parse(JSON.stringify(source));
Person(json); // => throws '[tcomb] Invalid value "1973-11-29T23:00:00.000Z" supplied to Person/birthDate: Date'
const person = fromJSON(json, Person);
assert.ok(person instanceof Person); // => true
assert.deepEqual(person, source); // => ok
You can add a static fromJSON: (jsonValue: any) => any
function to your types as a custom reviver.
Chrome Dev Tools custom formatter for tcomb types.
Chrome (v47+) has support for custom "formatters". A formatter tells Chrome's Dev Tools how to display values in the Console, Scope list, etc. This means we can display Structs, Lists, Dicts and other types, in a much better way.
Essentially, it turns this:
into:
Setup
import installTypeFormatter from 'tcomb/lib/installTypeFormatter'
installTypeFormatter()
Function for determining whether one type is compatible with another type.
Signature
(subset: Type, superset: Type) => boolean
Example
import t from 'tcomb'
import isSubsetOf from 'tcomb/lib/isSubsetOf';
isSubsetOf(t.Integer, t.Number) // => true
const Point = t.interface({
x: t.Number,
y: t.Number
}, 'Point');
const Point3D = t.interface({
x: t.Number,
y: t.Number,
z: t.Number
}, 'Point3D');
isSubsetOf(Point3D, Point) // => true
const StrictPoint = t.interface({
x: t.Number,
y: t.Number
}, { name: 'StrictPoint', strict: true });
isSubsetOf(Point3D, StrictPoint) // => false