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)returningtrueifxis an instance ofT
Every type defined with tcomb owns a static meta member containing at least the following properties:
kindan enum containing the type kind ('irreducible','struct', etc...)namean optional string, useful for debugging purposesidentitya boolean,trueif 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) => voidExample
const x = 0;
t.assert(x !== 0, 'cannot divide by x'); // => throws '[tcomb] cannot divide by x'Signature
(message: string) => voidNote. 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:nullorundefinedt.Any: any valuet.Type: atcombtype
Signature
(name: string, predicate: (x: any) => boolean) => TcombTypeExample. 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) => TcombTypeExample. 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) => TcombTypewhere 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) => TcombTypewhere 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) => TcombTypeExample
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' }); // => throwsThe 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) => TcombTypeExample
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) => TcombStructExample
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. The implementation uses the top level function extend(combinator, mixins, options) defined in tcomb/lib/extend
Signature
(types: Array<TcombType>, name?: string) => TcombTypeNote. 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) => TcombTypeNote. 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) => TcombTypeNote. 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) => TcombTypeExample
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) => TcombTypeExample
Actions.dispatch = function dispatch(x) {
const typeToConstructor = {
'increment': IncrementAction,
'decrement': DecrementAction
};
return typeToConstructor[x.type];
};
const incrementAction = Action({ type: 'increment', step: 1 });Note. tcomb provides a default implementation of dispatch which you can override.
Signature
(types: Array<TcombType>, name?: string) => TcombTypeExample
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'); // => falseThe meta object
{
kind: 'intersection',
name: name,
identity: ...depends on types,
types: types
}There is an alias (inter) for IE8 compatibility.
Differences from structs
isdoesn'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) => TcombTypeExample
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); // => okThe 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) => TcombStructExample
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) => TcombTypeExample
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: 3You can define a typed function using the func(domain, codomain, name?) combinator where:
domainis the type of the function's argument (or list of types of the function's arguments)codomainis 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: 3func(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 : UYou 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. Objects, Arrays 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>) => anywhere each Case has the following structure:
type, [guard], blocktypea tcomb typeguardan optional predicate(x: any) => anyblocka function(x: any) => anycalled 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) => ObjectExample
const x = { a: 1 };
t.update(x, { a: { $set: 2 } }); // => { a: 2 }, x is untouchedNote. 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) => stringExample
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) => ObjectExample
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) => StringSince 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) => booleanReturns true if x is an instance of type.
Signature
(x: any, type: TcombType) => booleanGeneric deserialization function.
Signature
(value: any, type: TcombType) => typeExample
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); // => okYou 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) => booleanExample
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
