Comparing version 2.1.0 to 2.2.0
@@ -14,2 +14,22 @@ # Changelog | ||
## v2.2.0 | ||
- **New Feature** | ||
- added `intersection` combinator fix #111 | ||
**Example** | ||
```js | ||
const Min = t.subtype(t.String, function (s) { return s.length > 2; }, 'Min'); | ||
const Max = t.subtype(t.String, function (s) { return s.length < 5; }, 'Max'); | ||
const MinMax = t.intersection([Min, Max], 'MinMax'); | ||
MinMax.is('abc'); // => true | ||
MinMax.is('a'); // => false | ||
MinMax.is('abcde'); // => false | ||
``` | ||
- **Internal** | ||
- optimised the generation of default names for types | ||
## v2.1.0 | ||
@@ -46,20 +66,1 @@ | ||
* getFunctionName | ||
## v1.0.3 | ||
- **Internal** | ||
+ fix tcomb lists don't currently play nice with es6 classes due to inability to invoke classes without new #92 | ||
## v1.0.2 | ||
- **Internal** | ||
+ Remove `debugger` statement #90 | ||
## v1.0.1 | ||
- **Internal** | ||
+ Add react-native compatibility #89 | ||
## v1.0.0 | ||
First release |
26
GUIDE.md
@@ -433,2 +433,28 @@ # Setup | ||
## The intersection combinator | ||
You can define an intersection of types using the `intersection(types, name)` combinator where: | ||
* `types` is a list of types | ||
* `name` is an optional string useful for debugging purposes | ||
```js | ||
const Min = t.subtype(t.String, function (s) { return s.length > 2; }, 'Min'); | ||
const Max = t.subtype(t.String, function (s) { return s.length < 5; }, 'Max'); | ||
const MinMax = t.intersection([Min, Max], 'MinMax'); | ||
MinMax.is('abc'); // => true | ||
MinMax.is('a'); // => false | ||
MinMax.is('abcde'); // => false | ||
``` | ||
Intersections have the following `meta` object: | ||
```js | ||
{ | ||
kind: 'intersection', | ||
types: types | ||
} | ||
``` | ||
## The maybe combinator | ||
@@ -435,0 +461,0 @@ |
175
index.js
@@ -89,5 +89,6 @@ 'use strict'; | ||
function getTypeName(constructor) { | ||
return isType(constructor) ? | ||
constructor.displayName : | ||
getFunctionName(constructor); | ||
if (isType(constructor)) { | ||
return constructor.displayName; | ||
} | ||
return getFunctionName(constructor); | ||
} | ||
@@ -156,6 +157,7 @@ | ||
update.commands = { | ||
'$apply': function (f, value) { | ||
$apply: function (f, value) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(isFunction(f), 'Invalid argument f supplied to immutability helper {"$apply": f}: expected a function'); | ||
assert(isFunction(f), 'Invalid argument f supplied to immutability helper { $apply: f }: expected a function'); | ||
} | ||
@@ -165,6 +167,7 @@ | ||
}, | ||
'$push': function (elements, arr) { | ||
$push: function (elements, arr) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {"$push": elements}: expected an array'); | ||
assert(isArray(elements), 'Invalid argument elements supplied to immutability helper { $push: elements }: expected an array'); | ||
assert(isArray(arr), 'Invalid value supplied to immutability helper "$push": expected an array'); | ||
@@ -175,6 +178,7 @@ } | ||
}, | ||
'$remove': function (keys, obj) { | ||
$remove: function (keys, obj) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(isArray(keys), 'Invalid argument keys supplied to immutability helper {"$remove": keys}: expected an array'); | ||
assert(isArray(keys), 'Invalid argument keys supplied to immutability helper { $remove: keys }: expected an array'); | ||
assert(isObject(obj), 'Invalid value supplied to immutability helper $remove: expected an object'); | ||
@@ -188,9 +192,11 @@ } | ||
}, | ||
'$set': function (value) { | ||
$set: function (value) { | ||
return value; | ||
}, | ||
'$splice': function (splices, arr) { | ||
$splice: function (splices, arr) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(list(Arr).is(splices), 'Invalid argument splices supplied to immutability helper {"$splice": splices}: expected an array of arrays'); | ||
assert(list(Arr).is(splices), 'Invalid argument splices supplied to immutability helper { $splice: splices }: expected an array of arrays'); | ||
assert(isArray(arr), 'Invalid value supplied to immutability helper $splice: expected an array'); | ||
@@ -204,8 +210,9 @@ } | ||
}, | ||
'$swap': function (config, arr) { | ||
$swap: function (config, arr) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(isObject(config), 'Invalid argument config supplied to immutability helper {"$swap": config}: expected an object'); | ||
assert(isNumber(config.from), 'Invalid argument config.from supplied to immutability helper {"$swap": config}: expected a number'); | ||
assert(isNumber(config.to), 'Invalid argument config.to supplied to immutability helper {"$swap": config}: expected a number'); | ||
assert(isObject(config), 'Invalid argument config supplied to immutability helper { $swap: config }: expected an object'); | ||
assert(isNumber(config.from), 'Invalid argument config.from supplied to immutability helper { $swap: config }: expected a number'); | ||
assert(isNumber(config.to), 'Invalid argument config.to supplied to immutability helper { $swap: config }: expected a number'); | ||
assert(isArray(arr), 'Invalid value supplied to immutability helper $swap'); | ||
@@ -219,6 +226,7 @@ } | ||
}, | ||
'$unshift': function (elements, arr) { | ||
$unshift: function (elements, arr) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {"$unshift": elements}'); | ||
assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {$unshift: elements}'); | ||
assert(isArray(arr), 'Invalid value supplied to immutability helper $unshift'); | ||
@@ -229,5 +237,7 @@ } | ||
}, | ||
'$merge': function (obj, value) { | ||
$merge: function (obj, value) { | ||
return mixin(mixin({}, value), obj, true); | ||
} | ||
}; | ||
@@ -294,2 +304,8 @@ | ||
function getDefaultStructName(props) { | ||
return '{' + Object.keys(props).map(function (prop) { | ||
return prop + ': ' + getTypeName(props[prop]); | ||
}).join(', ') + '}'; | ||
} | ||
function struct(props, name) { | ||
@@ -302,8 +318,4 @@ | ||
var defaultName = '{' + Object.keys(props).map(function (prop) { | ||
return prop + ': ' + getTypeName(props[prop]); | ||
}).join(', ') + '}'; | ||
var displayName = name || getDefaultStructName(props); | ||
var displayName = name || defaultName; | ||
function Struct(value) { | ||
@@ -372,2 +384,6 @@ | ||
function getDefaultUnionName(types) { | ||
return types.map(getTypeName).join(' | '); | ||
} | ||
function union(types, name) { | ||
@@ -380,6 +396,4 @@ | ||
var defaultName = types.map(getTypeName).join(' | '); | ||
var displayName = name || getDefaultUnionName(types); | ||
var displayName = name || defaultName; | ||
function Union(value) { | ||
@@ -426,2 +440,53 @@ | ||
function getDefaultIntersectionName(types) { | ||
return types.map(getTypeName).join(' & '); | ||
} | ||
function intersection(types, name) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(isArray(types) && types.every(isFunction) && types.length >= 2, 'Invalid argument types = ' + exports.stringify(types) + ' supplied to intersection(types, name): expected an array of at least 2 types'); | ||
assert(isTypeName(name), 'Invalid argument name = ' + exports.stringify(name) + ' supplied to intersection(types, name): expected a string'); | ||
} | ||
var displayName = name || getDefaultIntersectionName(types); | ||
function Intersection(value) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
forbidNewOperator(this, Intersection); | ||
} | ||
if (process.env.NODE_ENV !== 'production') { | ||
assert(Intersection.is(value), 'Invalid argument value = ' + exports.stringify(value) + ' supplied to intersection ' + displayName); | ||
} | ||
return value; | ||
} | ||
Intersection.meta = { | ||
kind: 'intersection', | ||
types: types, | ||
name: name | ||
}; | ||
Intersection.displayName = displayName; | ||
Intersection.is = function (x) { | ||
return types.every(function (type) { | ||
return is(x, type); | ||
}); | ||
}; | ||
Intersection.update = function (instance, spec) { | ||
return Intersection(exports.update(instance, spec)); | ||
}; | ||
return Intersection; | ||
} | ||
function getDefaultMaybeName(type) { | ||
return '?' + getTypeName(type); | ||
} | ||
function maybe(type, name) { | ||
@@ -441,3 +506,3 @@ | ||
name = name || ('?' + getTypeName(type)); | ||
name = name || getDefaultMaybeName(type); | ||
@@ -466,2 +531,6 @@ function Maybe(value) { | ||
function getDefaultEnumsName(map) { | ||
return Object.keys(map).map(function (k) { return exports.stringify(k); }).join(' | '); | ||
} | ||
function enums(map, name) { | ||
@@ -474,6 +543,4 @@ | ||
var defaultName = Object.keys(map).map(function (k) { return exports.stringify(k); }).join(' | '); | ||
var displayName = name || getDefaultEnumsName(map); | ||
var displayName = name || defaultName; | ||
function Enums(value) { | ||
@@ -511,2 +578,6 @@ if (process.env.NODE_ENV !== 'production') { | ||
function getDefaultTupleName(types) { | ||
return '[' + types.map(getTypeName).join(', ') + ']'; | ||
} | ||
function tuple(types, name) { | ||
@@ -519,6 +590,4 @@ | ||
var defaultName = '[' + types.map(getTypeName).join(', ') + ']'; | ||
var displayName = name || getDefaultTupleName(types); | ||
var displayName = name || defaultName; | ||
function isTuple(x) { | ||
@@ -560,3 +629,2 @@ return types.every(function (type, i) { | ||
types: types, | ||
length: types.length, | ||
name: name | ||
@@ -580,2 +648,6 @@ }; | ||
function getDefaultSubtypeName(type, predicate) { | ||
return '{' + getTypeName(type) + ' | ' + getFunctionName(predicate) + '}'; | ||
} | ||
function subtype(type, predicate, name) { | ||
@@ -589,6 +661,4 @@ | ||
var defaultName = '{' + getTypeName(type) + ' | ' + getFunctionName(predicate) + '}'; | ||
var displayName = name || getDefaultSubtypeName(type, predicate); | ||
var displayName = name || defaultName; | ||
function Subtype(value) { | ||
@@ -629,2 +699,6 @@ | ||
function getDefaultListName(type) { | ||
return 'Array<' + getTypeName(type) + '>'; | ||
} | ||
function list(type, name) { | ||
@@ -637,6 +711,4 @@ | ||
var defaultName = 'Array<' + getTypeName(type) + '>'; | ||
var displayName = name || getDefaultListName(type); | ||
var displayName = name || defaultName; | ||
function isList(x) { | ||
@@ -660,2 +732,3 @@ return x.every(function (e) { | ||
} | ||
var arr = []; | ||
@@ -693,2 +766,6 @@ for (var i = 0, len = value.length; i < len; i++ ) { | ||
function getDefaultDictName(domain, codomain) { | ||
return '{[key: ' + getTypeName(domain) + ']: ' + getTypeName(codomain) + '}'; | ||
} | ||
function dict(domain, codomain, name) { | ||
@@ -702,6 +779,4 @@ | ||
var defaultName = '{[key: ' + getTypeName(domain) + ']: ' + getTypeName(codomain) + '}'; | ||
var displayName = name || getDefaultDictName(domain, codomain); | ||
var displayName = name || defaultName; | ||
function isDict(x) { | ||
@@ -771,2 +846,6 @@ for (var k in x) { | ||
function getDefaultFuncName(domain, codomain) { | ||
return '(' + domain.map(getTypeName).join(', ') + ') => ' + getTypeName(codomain); | ||
} | ||
function func(domain, codomain, name) { | ||
@@ -782,6 +861,4 @@ | ||
var defaultName = '(' + domain.map(getTypeName).join(', ') + ') => ' + getTypeName(codomain); | ||
var displayName = name || getDefaultFuncName(domain, codomain); | ||
var displayName = name || defaultName; | ||
function FuncType(value, uncurried) { | ||
@@ -839,3 +916,2 @@ | ||
if (len === domain.length) { | ||
/* jshint validthis: true */ | ||
return create(codomain, f.apply(this, args)); | ||
@@ -902,3 +978,4 @@ } | ||
dict: dict, | ||
func: func | ||
func: func, | ||
intersection: intersection | ||
}); |
{ | ||
"name": "tcomb", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "Type checking and DDD for JavaScript", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
119
README.md
[![build status](https://img.shields.io/travis/gcanti/tcomb/master.svg?style=flat-square)](https://travis-ci.org/gcanti/tcomb) | ||
[![dependency status](https://img.shields.io/david/gcanti/tcomb.svg?style=flat-square)](https://david-dm.org/gcanti/tcomb) | ||
![npm downloads](https://img.shields.io/npm/dm/tcomb.svg) | ||
[![Join the chat at https://gitter.im/gcanti/tcomb](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gcanti/tcomb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
tcomb is a library for Node.js and the browser which allows you to **check the types** of JavaScript values at runtime with a simple and concise syntax. It's great for **Domain Driven Design** and for adding safety to your internal code. | ||
> "Si vis pacem, para bellum" - (Vegetius 5th century) | ||
# Code example | ||
```js | ||
import t from 'tcomb'; | ||
// a user defined type | ||
const Integer = t.subtype(t.Number, (n) => n % 1 === 0); | ||
// a struct | ||
const Person = t.struct({ | ||
name: t.String, // required string | ||
surname: t.maybe(t.String), // optional string | ||
age: Integer, // required integer | ||
tags: t.list(t.String) // a list of strings | ||
}); | ||
// methods are defined as usual | ||
Person.prototype.getFullName = function () { | ||
return `${this.name} ${this.surname}`; | ||
}; | ||
// an instance of Person (the keyword new is optional) | ||
const person = new Person({ | ||
name: 'Giulio', | ||
surname: 'Canti', | ||
age: 41, | ||
tags: ['js developer', 'rock climber'] | ||
}); | ||
``` | ||
# Features | ||
@@ -23,2 +56,3 @@ | ||
* unions | ||
* intersections | ||
* the option type | ||
@@ -31,18 +65,38 @@ * tuples | ||
Blog posts: | ||
- [JavaScript, Types and Sets - Part I](https://gcanti.github.io/2014/09/29/javascript-types-and-sets.html) | ||
- [JavaScript, Types and Sets - Part II](https://gcanti.github.io/2014/10/07/javascript-types-and-sets-part-II.html) | ||
## Safety | ||
## Type safety | ||
All models defined by tcomb are type checked (using a built-in `assert(guard, [message])` function). | ||
All models are type checked: | ||
```js | ||
const person = new Person({ | ||
name: 'Giulio', | ||
// missing required field "age" | ||
tags: ['js developer', 'rock climber'] | ||
}); | ||
``` | ||
Output to console: | ||
```js | ||
[tcomb] Invalid argument value = undefined supplied to irreducible type Number | ||
``` | ||
See "Debugging with Chrome DevTools" section for details. | ||
## Immutability and immutability helpers | ||
Instances are immutables by default using `Object.freeze`. This means you can use standard JavaScript objects and arrays. You don't have to change how you normally code. You can update an immutable instance with the provided `update` function: | ||
Instances are immutables using `Object.freeze`. This means you can use standard JavaScript objects and arrays. You don't have to change how you normally code. You can update an immutable instance with the provided `update(instance, spec)` function: | ||
```js | ||
MyType.update(instance, spec) | ||
const person2 = Person.update(person, { | ||
name: {$set: 'Guido'} | ||
}); | ||
``` | ||
The following commands are compatible with the [Facebook Immutability Helpers](http://facebook.github.io/react/docs/update.html): | ||
where `spec` is an object contaning *commands*. The following commands are compatible with the [Facebook Immutability Helpers](http://facebook.github.io/react/docs/update.html): | ||
@@ -56,14 +110,14 @@ * `$push` | ||
See [Updating immutable instances](GUIDE.md#updating-immutable-instances) in the docs for details. | ||
## Speed | ||
`Object.freeze` and the asserts are executed only during development and stripped out in production (using `process.env.NODE_ENV = 'production'` tests). | ||
`Object.freeze` calls and asserts are executed only in development and stripped out in production (using `process.env.NODE_ENV = 'production'` tests). | ||
## Debugging with Chrome DevTools | ||
You can customize the behaviour when an assert fails leveraging the power of Chrome DevTools. | ||
You can customize the behavior when an assert fails leveraging the power of Chrome DevTools. | ||
```js | ||
import t from 'tcomb'; | ||
// default behaviour | ||
// use the default... | ||
t.fail = function fail(message) { | ||
@@ -73,6 +127,6 @@ throw new TypeError('[tcomb] ' + message); // set "Pause on exceptions" on the "Sources" panel | ||
// .. or define your own handler | ||
// .. or define your own behavior | ||
t.fail = function fail(message) { | ||
debugger; // starts the Chrome DevTools debugger | ||
throw new TypeError('[tcomb] ' + message); | ||
throw new TypeError(message); | ||
}; | ||
@@ -83,44 +137,15 @@ ``` | ||
Every model is inspectable at runtime. You can read and reuse the informations stored in your types (in a `meta` object). See: | ||
All models are inspectable at runtime. You can read and reuse the informations stored in your types (in a `meta` static property). See [The meta object](GUIDE.md#the-meta-object) in the docs for details. | ||
Libraries exploiting tcomb's RTI: | ||
- [tcomb-validation](https://github.com/gcanti/tcomb-validation) | ||
- [tcomb-form](https://github.com/gcanti/tcomb-form) | ||
- [JSON API Validation In Node.js](https://gcanti.github.io/2014/09/15/json-api-validation-in-node.html) | ||
- Blog post: [JSON API Validation In Node.js](https://gcanti.github.io/2014/09/15/json-api-validation-in-node.html) | ||
## Easy JSON serialization / deseralization | ||
Encodes / decodes your domain models to / from JSON for free. See: | ||
Encodes / decodes your domain models to / from JSON for free. | ||
- Blog post: [JSON Deserialization Into An Object Model](https://gcanti.github.io/2014/09/12/json-deserialization-into-an-object-model.html) | ||
- [JSON Deserialization Into An Object Model](https://gcanti.github.io/2014/09/12/json-deserialization-into-an-object-model.html) | ||
# Code example | ||
```js | ||
import t from 'tcomb'; | ||
// a user defined type | ||
const Integer = t.subtype(t.Number, (n) => n % 1 === 0); | ||
// a struct | ||
const Person = t.struct({ | ||
name: t.String, // required string | ||
surname: t.maybe(t.String), // optional string | ||
age: Integer, // required integer | ||
tags: t.list(t.String) // a list of strings | ||
}); | ||
// methods are defined as usual | ||
Person.prototype.getFullName = function () { | ||
return `${this.name} ${this.surname}`; | ||
}; | ||
// an instance of Person (the keyword new is optional) | ||
const person = new Person({ | ||
name: 'Giulio', | ||
surname: 'Canti', | ||
age: 41, | ||
tags: ['js developer', 'rock climber'] | ||
}); | ||
``` | ||
# Docs | ||
@@ -127,0 +152,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50127
728
158