├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── API.md ├── GUIDE.md └── images │ ├── after_formatting.png │ ├── before_formatting.png │ ├── chrome-dev-tools-fail.png │ ├── chrome-dev-tools-sum.png │ ├── display-name.png │ ├── meta-object.png │ ├── struct-deserialisation.png │ ├── throws.png │ └── type-error.png ├── index.d.ts ├── index.js ├── issue_template.md ├── lib ├── Any.js ├── Array.js ├── Boolean.js ├── Date.js ├── Error.js ├── Function.js ├── Integer.js ├── Nil.js ├── Number.js ├── Object.js ├── RegExp.js ├── String.js ├── Type.js ├── assert.js ├── assign.js ├── create.js ├── declare.js ├── decompose.js ├── dict.js ├── enums.js ├── extend.js ├── fail.js ├── forbidNewOperator.js ├── fromJSON.d.ts ├── fromJSON.js ├── func.js ├── getDefaultInterfaceName.js ├── getFunctionName.js ├── getTypeName.js ├── installTypeFormatter.js ├── interface.js ├── intersection.js ├── irreducible.js ├── is.js ├── isArray.js ├── isBoolean.js ├── isFunction.js ├── isIdentity.js ├── isInterface.js ├── isMaybe.js ├── isNil.js ├── isNumber.js ├── isObject.js ├── isString.js ├── isStruct.js ├── isSubsetOf.d.ts ├── isSubsetOf.js ├── isType.js ├── isTypeName.js ├── isUnion.js ├── list.js ├── match.js ├── maybe.js ├── mixin.js ├── refinement.js ├── stringify.js ├── struct.js ├── tuple.js ├── union.js └── update.js ├── package-lock.json ├── package.json ├── perf └── perf.js ├── tcomb-tests.ts ├── test ├── assert.js ├── declare.js ├── dict.js ├── enums.js ├── es6.js ├── extend.js ├── fromJSON.js ├── func.js ├── getTypeName.js ├── installTypeFormatter.js ├── interface.js ├── intersection.js ├── irreducible.js ├── irreducibles.js ├── isSubsetOf.js ├── list.js ├── match.js ├── maybe.js ├── mixin.js ├── refinement.js ├── struct.js ├── tuple.js ├── union.js ├── update.js └── util.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "ecmaFeatures": { 8 | "modules": true 9 | }, 10 | "rules": { 11 | "semi": 1 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | before_script: 5 | - export DISPLAY=:99.0 6 | - sh -e /etc/init.d/xvfb start 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | > **Tags:** 4 | > - [New Feature] 5 | > - [Bug Fix] 6 | > - [Breaking Change] 7 | > - [Documentation] 8 | > - [Internal] 9 | > - [Polish] 10 | > - [Experimental] 11 | 12 | **Note**: Gaps between patch versions are faulty/broken releases. 13 | **Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice. 14 | 15 | # v3.2.29 16 | 17 | - **Bug Fix** 18 | - [typescript] fix `interface`'s `extend` signature, #329 (@apepper) 19 | 20 | # v3.2.28 21 | 22 | - **Bug Fix** 23 | - Enums.is() with an array value should always be false, #327 (@phorsuedzie) 24 | 25 | # v3.2.27 26 | 27 | - **Bug Fix** 28 | - struct typescript extend signature, #317 (@lramel) 29 | - defaultProps missing from StructOptions, #317 (@lramel) 30 | 31 | # v3.2.25 32 | 33 | - **Bug Fix** 34 | - fromJSON makes use of defaultProps while deserializing a struct, fix #312 (@rkmax) 35 | 36 | # v3.2.24 37 | 38 | - **Bug Fix** 39 | - Struct extension of a refinement of a struct now use the correct displayName, fix #297 (@gcanti) 40 | 41 | # v3.2.23 42 | 43 | - **Bug Fix** 44 | - declare: remove unnecessary limitation, fix #291 (@gcanti) 45 | 46 | # v3.2.22 47 | 48 | - **Polish** 49 | - Update TypeScript definitions to allow module augmentation (@RedRoserade) 50 | 51 | # v3.2.21 52 | 53 | - **Bug Fix** 54 | - TypeScript definition file: `Nil` should be `void | null` (@francescogior) 55 | 56 | # v3.2.20 57 | 58 | - **Polish** 59 | - add `options` (struct, interface) to typescript definition (@gcanti) 60 | 61 | # v3.2.19 62 | 63 | - **Polish** 64 | - add `strict` (struct, interface) to typescript definition (@gcanti) 65 | 66 | # v3.2.18 67 | 68 | - **Bug Fix** 69 | - fix `define` in typescript definition (@gcanti) 70 | 71 | # v3.2.17 72 | 73 | - **Bug Fix** 74 | - add missing `t.Integer` to typescript definition (@gcanti) 75 | 76 | # v3.2.16 77 | 78 | - **Bug Fix** 79 | - strict structs with additional methods should not throw on updating, fix #267 (@gcanti) 80 | 81 | # v3.2.15 82 | 83 | - **New Feature** 84 | - Added support for overwriting `defaultProps` in `t.struct.extend`, fix #257 (@tehnomaag) 85 | 86 | # v3.2.14 87 | 88 | - **Bug Fix** 89 | - replace `instanceof Array` with `Array.isArray`, fix #255 (@ewnd9) 90 | 91 | # v3.2.13 92 | 93 | - **Bug Fix** 94 | - fromJSON: typecasting of values inside `t.intersection`, fix #250 (@gcanti) 95 | 96 | # v3.2.12 97 | 98 | - **Bug Fix** 99 | - now `interface` doesn't filter additional props when props contain a struct, fix #245 (@gcanti) 100 | 101 | # v3.2.11 102 | 103 | - **Bug Fix** 104 | - allow declare'd unions with custom dispatch, fix #242 (@gcanti) 105 | 106 | # v3.2.10 107 | 108 | - **Bug Fix** 109 | - handle nully values in interface `is` function (@gcanti) 110 | 111 | # v3.2.9 112 | 113 | - **New Feature** 114 | - fromJSON: track error path, fix #235 (@gcanti) 115 | - **Internal** 116 | - change shallow copy in order to improve perfs (@gcanti) 117 | 118 | # v3.2.8 119 | 120 | - **Bug Fix** 121 | - mixing types and classes in a union throws, fix #232 (@gcanti) 122 | 123 | # v3.2.7 124 | 125 | - **Bug Fix** 126 | - add support for class constructors, `fromJSON` module (@gcanti) 127 | - type-check the value returned by a custom reviver, `fromJSON` module (@gcanti) 128 | 129 | # v3.2.6 130 | 131 | - **Bug Fix** 132 | - null Maybes should stringify to null, fix #227 (@gcanti) 133 | 134 | # v3.2.5 135 | 136 | - **Polish** 137 | - prevent bugs when enums are defined through `t.declare` (@gcanti) 138 | 139 | # v3.2.4 140 | 141 | - **Polish** 142 | - decouple usage of new operator in create() function, fix #223 (@gcanti) 143 | 144 | # v3.2.3 145 | 146 | - **Polish** 147 | - add `isNil` check in interface constructor 148 | - **Experimental** 149 | - add support for [babel-plugin-tcomb](https://github.com/gcanti/babel-plugin-tcomb), fix #218 (@gcanti) 150 | 151 | # v3.2.2 152 | 153 | - **Bug Fix** 154 | - relax `isObject` contraint in interface combinator, fix #214 155 | 156 | # v3.2.1 157 | 158 | - **Bug Fix** 159 | - fix missing path argument in FuncType 160 | - **Polish** 161 | - better stringify serialization for functions 162 | 163 | # v3.2.0 164 | 165 | - **New Feature** 166 | - `isSubsetOf` module, function for determining whether one type is compatible with another type (@R3D4C73D) 167 | - default props for structs (thanks @timoxley) 168 | - **Documentation** 169 | - global strict settings are deprecated (see https://github.com/gcanti/tcomb/issues/168#issuecomment-222422999) 170 | 171 | # v3.1.0 172 | 173 | - **New Feature** 174 | - add `t.Integer` to standard types 175 | - add `t.Type` to standard types 176 | - `interface` combinator, fix #195, [docs](https://github.com/gcanti/tcomb/blob/master/docs/API.md#the-interface-combinator) (thanks @ctrlplusb) 177 | - add interface support to fromJSON (@minedeljkovic) 178 | - add support for extending refinements, fix #179, [docs](https://github.com/gcanti/tcomb/blob/master/docs/API.md#extending-structs) 179 | - local and global `strict` option for structs and interfaces, fix #203, [docs](https://github.com/gcanti/tcomb/blob/master/docs/API.md#strictness) 180 | - Chrome Dev Tools custom formatter for tcomb types [docs](https://github.com/gcanti/tcomb/blob/master/docs/API.md#the-libinstalltypeformatter-module) 181 | - **Bug Fix** 182 | - More intelligent immutability update handling, fix #199 (thanks @ctrlplusb) 183 | - func combinator: support optional arguments, fix #198 (thanks @ivan-kleshnin) 184 | - **Internal** 185 | - add "Struct" prefix to structs default name 186 | - `mixin()` now allows identical references for overlapping properties 187 | 188 | # v3.0.0 189 | 190 | **Warning**. If you don't rely in your codebase on the property `maybe(MyType)(undefined) === null` this **is not a breaking change** for you. 191 | 192 | - **Breaking Change** 193 | - prevent `Maybe` constructor from altering the value when `Nil`, fix #183 (thanks @gabro) 194 | 195 | # v2.7.0 196 | 197 | - **New Feature** 198 | - `lib/fromJSON` module: generic deserialize, fix #169 199 | - `lib/fromJSON` TypeScript definition file 200 | - **Bug Fix** 201 | - t.update module: $apply doesn't play well with dates and regexps, fix #172 202 | - t.update: cannot $merge and $remove at once, fix #170 (thanks @grahamlyus) 203 | - TypeScript: fix Exported external package typings file '...' is not a module 204 | - misleading error message in `Struct.extend` functions, fix #177 (thanks @Firfi) 205 | 206 | # v2.6.0 207 | 208 | - **New Feature** 209 | - `declare` API: recursive and mutually recursive types (thanks @utaal) 210 | - typescript definition file, fix #160 (thanks @DanielRosenwasser) 211 | - `t.struct.extend`, fix #164 (thanks @dzdrazil) 212 | - **Internal** 213 | - split main file to separate modules, fix #158 214 | - add "typings" field to package.json (TypeScript) 215 | - add `predicate` field to irreducibles meta objects 216 | - **Documentation** 217 | - revamp [API.md](https://github.com/gcanti/tcomb/blob/master/docs/API.md) 218 | - add ["A little guide to runtime type checking and runtime type introspection"](https://github.com/gcanti/tcomb/blob/master/docs/GUIDE.md) (WIP) 219 | 220 | ## v2.5.2 221 | 222 | - **Bug Fix** 223 | - remove the assert checking if the type returned by a union dispatch function is correct (was causing issues with unions of unions or unions of intersections) 224 | 225 | ## v2.5.1 226 | 227 | - **Internal** 228 | - `t.update` should not change the reference when no changes occur, fix #153 229 | 230 | # v2.5.0 231 | 232 | - **New Feature** 233 | - check if the type returned by a union dispatch function is correct, fix #136 (thanks @fcracker79) 234 | - added `refinement` alias to `subtype` (which is deprecated), fix #140 235 | - **Internal** 236 | - optimisations: for identity types return early in production, fix #135 (thanks @fcracker79) 237 | - exposed `getDefaultName` on combinator constructors 238 | 239 | ## v2.4.1 240 | 241 | - **New Feature** 242 | - added struct multiple inheritance, fix #143 243 | 244 | # v2.4.0 245 | 246 | - **New Feature** 247 | - unions 248 | - added `update` function, #127 249 | - the default `dispatch` implementation now handles unions of unions, #126 250 | - show the offended union type in error messages 251 | 252 | # v2.3.0 253 | 254 | - **New Feature** 255 | - Add support for lazy messages in asserts, fix #124 256 | - Better error messages for assert failures, fix #120 257 | 258 | The messages now have the following general form: 259 | 260 | ``` 261 | Invalid value supplied to 262 | ``` 263 | 264 | where context is a slash-separated string with the following properties: 265 | 266 | - the first element is the name of the "root" 267 | - the following elements have the form: `: ` 268 | 269 | Note: for more readable messages remember to give types a name 270 | 271 | Example: 272 | 273 | ```js 274 | var Person = t.struct({ 275 | name: t.String 276 | }, 'Person'); // <- remember to give types a name 277 | 278 | var User = t.struct({ 279 | email: t.String, 280 | profile: Person 281 | }, 'User'); 282 | 283 | var mynumber = t.Number('a'); 284 | // => Invalid value "a" supplied to Number 285 | 286 | var myuser = User({ email: 1 }); 287 | // => Invalid value 1 supplied to User/email: String 288 | 289 | myuser = User({ email: 'email', profile: { name: 2 } }); 290 | // => Invalid value 2 supplied to User/profile: Person/name: String 291 | ``` 292 | 293 | 294 | ## v2.2.1 295 | 296 | - **Experimental** 297 | - pattern matching #121 298 | 299 | # v2.2.0 300 | 301 | - **New Feature** 302 | - added `intersection` combinator fix #111 303 | 304 | **Example** 305 | 306 | ```js 307 | const Min = t.subtype(t.String, function (s) { return s.length > 2; }, 'Min'); 308 | const Max = t.subtype(t.String, function (s) { return s.length < 5; }, 'Max'); 309 | const MinMax = t.intersection([Min, Max], 'MinMax'); 310 | 311 | MinMax.is('abc'); // => true 312 | MinMax.is('a'); // => false 313 | MinMax.is('abcde'); // => false 314 | ``` 315 | 316 | - **Internal** 317 | - optimised the generation of default names for types 318 | 319 | # v2.1.0 320 | 321 | - **New Feature** 322 | - added aliases for pre-defined irreducible types fix #112 323 | - added overridable `stringify` function to handle error messages and improve performances in development (replaces the experimental `options.verbose`) 324 | 325 | ## v2.0.1 326 | 327 | - **Experimental** 328 | - added `options.verbose` (default `true`) to handle messages (set `options.verbose = false` to improve performances in development) 329 | 330 | # v2.0.0 331 | 332 | - **New Feature** 333 | - add support to types defined as ES6 classes #99 334 | - optimized for production code: asserts and freeze only in development mode 335 | - add `is(x, type)` function 336 | - add `isType(x)` function 337 | - add `stringify(x)` function 338 | - **Breaking change** 339 | - numeric types on enums #93 (thanks @m0x72) 340 | - remove asserts when process.env.NODE_ENV === 'production' #100 341 | - do not freeze if process.env.NODE_ENV === 'production' #103 342 | - func without currying #96 (thanks @tmcw) 343 | - remove useless exports #104 344 | - drop bower support #101 345 | - remove useless exports 346 | * Type 347 | * slice 348 | * shallowCopy 349 | * getFunctionName 350 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Contributions are welcome and are greatly appreciated! Every little bit helps, and credit will 4 | always be given. 5 | 6 | ## Issues Guidelines 7 | 8 | Before you submit an issue, check that it meets these guidelines: 9 | 10 | - specify the version of `tcomb` you are using 11 | - if the issue regards a bug, please provide a minimal failing example / test 12 | 13 | ## Pull Request Guidelines 14 | 15 | Before you submit a pull request from your forked repo, check that it meets these guidelines: 16 | 17 | 1. If the pull request fixes a bug, it should include tests that fail without the changes, and pass 18 | with them. 19 | 2. If the pull request adds functionality, the docs should be updated as part of the same PR. 20 | 3. Please rebase and resolve all conflicts before submitting. 21 | 22 | ## Setting up your environment 23 | 24 | After forking tcomb to your own github org, do the following steps to get started: 25 | 26 | ```sh 27 | # clone your fork to your local machine 28 | git clone https://github.com/gcanti/tcomb.git 29 | 30 | # step into local repo 31 | cd tcomb 32 | 33 | # install dependencies 34 | npm install 35 | ``` 36 | 37 | ### Running Tests 38 | 39 | ```sh 40 | npm test 41 | ``` 42 | 43 | ### Style & Linting 44 | 45 | This codebase adheres to the `eslint:recommended` and is 46 | enforced using [ESLint](http://eslint.org/). 47 | 48 | It is recommended that you install an eslint plugin for your editor of choice when working on this 49 | codebase, however you can always check to see if the source code is compliant by running: 50 | 51 | ```sh 52 | npm run lint 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Giulio Canti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build status](https://img.shields.io/travis/gcanti/tcomb/master.svg?style=flat-square)](https://travis-ci.org/gcanti/tcomb) 2 | [![dependency status](https://img.shields.io/david/gcanti/tcomb.svg?style=flat-square)](https://david-dm.org/gcanti/tcomb) 3 | ![npm downloads](https://img.shields.io/npm/dm/tcomb.svg) 4 | 5 | > "Si vis pacem, para bellum" - (Vegetius 5th century) 6 | 7 | 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. 8 | 9 | # TypeScript / Flowtype users 10 | 11 | You may want to check out [io-ts](https://github.com/gcanti/io-ts) 12 | 13 | # IMPORTANT: Running in production 14 | 15 | tcomb is supposed to be used in **development** and is **disabled in production**. 16 | If you want type checks in production you may use 17 | - [tcomb-validation](https://github.com/gcanti/tcomb-validation) 18 | - [io-ts](https://github.com/gcanti/io-ts) 19 | 20 | # Setup 21 | 22 | ```sh 23 | npm install tcomb --save 24 | ``` 25 | 26 | **Code example** 27 | 28 | A type-checked function: 29 | 30 | ```js 31 | import t from 'tcomb'; 32 | 33 | function sum(a, b) { 34 | t.Number(a); 35 | t.Number(b); 36 | return a + b; 37 | } 38 | 39 | sum(1, 's'); // throws '[tcomb] Invalid value "s" supplied to Number' 40 | 41 | // using babel-plugin-tcomb 42 | function sum(a: number, b: number) { 43 | return a + b; 44 | } 45 | ``` 46 | 47 | A user defined type: 48 | 49 | ```js 50 | const Integer = t.refinement(t.Number, (n) => n % 1 === 0, 'Integer'); 51 | ``` 52 | 53 | A type-checked class: 54 | 55 | ```js 56 | const Person = t.struct({ 57 | name: t.String, // required string 58 | surname: t.maybe(t.String), // optional string 59 | age: t.Integer, // required integer 60 | tags: t.list(t.String) // a list of strings 61 | }, 'Person'); 62 | 63 | // methods are defined as usual 64 | Person.prototype.getFullName = function () { 65 | return `${this.name} ${this.surname}`; 66 | }; 67 | 68 | const person = Person({ 69 | surname: 'Canti' 70 | }); // throws '[tcomb] Invalid value undefined supplied to Person/name: String' 71 | ``` 72 | 73 | Chrome DevTools: 74 | 75 | ![throws](docs/images/throws.png) 76 | 77 | # Docs 78 | 79 | - [API](docs/API.md) 80 | - [A little guide to runtime type checking and runtime type introspection](docs/GUIDE.md) (Work in progress) 81 | 82 | # Features 83 | 84 | **Lightweight** 85 | 86 | 3KB gzipped, no dependencies. 87 | 88 | **Type safety** 89 | 90 | All models defined with `tcomb` are type-checked. 91 | 92 | **Note**. Instances *are not boxed*, this means that `tcomb` works great with lodash, Ramda, etc. And you can of course use them as props to React components. 93 | 94 | **Based on set theory** 95 | 96 | - Blog post: [JavaScript, Types and Sets - Part I](https://gcanti.github.io/2014/09/29/javascript-types-and-sets.html) 97 | - Blog post: [JavaScript, Types and Sets - Part II](https://gcanti.github.io/2014/10/07/javascript-types-and-sets-part-II.html) 98 | 99 | **Domain Driven Design** 100 | 101 | Write complex domain models in a breeze and with a small code footprint. Supported types / combinators: 102 | 103 | * user defined types 104 | * structs 105 | * lists 106 | * enums 107 | * refinements 108 | * unions 109 | * intersections 110 | * the option type 111 | * tuples 112 | * dictionaries 113 | * functions 114 | * recursive and mutually recursive types 115 | * interfaces 116 | 117 | **Immutability and immutability helpers** 118 | 119 | Instances are immutable 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: 120 | 121 | ```js 122 | const person2 = Person.update(person, { 123 | name: { $set: 'Guido' } 124 | }); 125 | ``` 126 | 127 | where `spec` is an object containing *commands*. The following commands are compatible with the [Facebook Immutability Helpers](http://facebook.github.io/react/docs/update.html): 128 | 129 | * `$push` 130 | * `$unshift` 131 | * `$splice` 132 | * `$set` 133 | * `$apply` 134 | * `$merge` 135 | 136 | See [Updating immutable instances](docs/API.md#updating-immutable-instances) for details. 137 | 138 | **Speed** 139 | 140 | `Object.freeze` calls and asserts are executed only in development and stripped out in production (using `process.env.NODE_ENV !== 'production'` tests). 141 | 142 | **Runtime type introspection** 143 | 144 | All models are inspectable at runtime. You can read and reuse the information stored in your types (in the `meta` static member). See [The meta object](docs/GUIDE.md#the-meta-object) in the docs for details. 145 | 146 | Libraries exploiting tcomb's RTI: 147 | 148 | - [tcomb-validation](https://github.com/gcanti/tcomb-validation) 149 | - [tcomb-form](https://github.com/gcanti/tcomb-form) 150 | - Blog post: [JSON API Validation In Node.js](https://gcanti.github.io/2014/09/15/json-api-validation-in-node.html) 151 | 152 | **Easy JSON serialization / deserialization** 153 | 154 | Encodes / decodes your domain models to / from JSON for free. 155 | - Blog post: [JSON Deserialization Into An Object Model](https://gcanti.github.io/2014/09/12/json-deserialization-into-an-object-model.html) 156 | 157 | **Debugging with Chrome DevTools** 158 | 159 | You can customize the behavior when an assert fails leveraging the power of Chrome DevTools. 160 | 161 | ```js 162 | // use the default... 163 | t.fail = function fail(message) { 164 | throw new TypeError('[tcomb] ' + message); // set "Pause on exceptions" on the "Sources" panel for a great DX 165 | }; 166 | 167 | // .. or define your own behavior 168 | t.fail = function fail(message) { 169 | console.error(message); 170 | }; 171 | ``` 172 | 173 | **Pattern matching** 174 | 175 | ```js 176 | const result = t.match(1, 177 | t.String, () => 'a string', 178 | t.Number, () => 'a number' 179 | ); 180 | 181 | console.log(result); // => 'a number' 182 | ``` 183 | 184 | **Babel plugin** 185 | 186 | Using [babel-plugin-tcomb](https://github.com/gcanti/babel-plugin-tcomb) you can also write (Flow compatible) type annotations: 187 | 188 | ```js 189 | function sum(a: number, b: number): number { 190 | return a + b; 191 | } 192 | ``` 193 | 194 | **TypeScript definition file** 195 | 196 | [index.d.ts](index.d.ts) 197 | 198 | # Contributors 199 | 200 | - [Giulio Canti](https://github.com/gcanti) maintainer 201 | - [Becky Conning](https://github.com/beckyconning) `func` combinator ideas and documentation 202 | - [Andrea Lattuada](https://github.com/utaal) `declare` combinator 203 | 204 | # How to Build a standalone bundle 205 | 206 | ```sh 207 | git clone git@github.com:gcanti/tcomb.git 208 | cd tcomb 209 | npm install 210 | npm run dist 211 | ``` 212 | 213 | Will output 2 files: 214 | 215 | - `dist/tcomb.js` (development) 216 | - `dist/tcomb.min.js` (production) `Object.freeze` calls and asserts stripped out 217 | 218 | # Related libraries 219 | 220 | * [tcomb-doc](https://github.com/gcanti/tcomb-doc) Documentation tool for tcomb 221 | * [tcomb-validation](https://github.com/gcanti/tcomb-validation) Validation library based on type combinators 222 | * [tcomb-json-schema](https://github.com/gcanti/tcomb-json-schema) Transforms a JSON Schema to a tcomb type 223 | * [reactuate](https://github.com/reactuate/reactuate) React/Redux stack (not a boilerplate kit) 224 | * [tcomb-react](https://github.com/gcanti/tcomb-react) Alternative syntax for PropTypes 225 | * [mongorito-tcomb](https://github.com/xouabita/mongorito-tcomb) Bring schema validation to Mongorito thanks to tcomb 226 | * [tcomb-form](https://github.com/gcanti/tcomb-form) Forms library for react 227 | * [tcomb-form-types](https://github.com/Industrial/tcomb-form-types) Adds Types/Validations to tcomb-form 228 | * [tcomb-form-native](https://github.com/gcanti/tcomb-form-native) Forms library for react-native 229 | * [tcomb-generate](https://github.com/typeetfunc/tcomb-generate) Generates random data by tcomb type for property-based testing 230 | * [tcomb-additional-types](http://github.com/eserozvataf/tcomb-additional-types) Provides additional predefined types for your existing tcomb setup 231 | * [tcomb-builder](https://github.com/cadre/tcomb-builder) An immutable syntax for defining tcomb types and forms 232 | 233 | # Similar projects 234 | 235 | * [typed-immutable](https://github.com/Gozala/typed-immutable) 236 | * [immu](https://github.com/scottcorgan/immu) 237 | * [immutable](https://github.com/facebook/immutable-js) 238 | * [mori](https://github.com/swannodette/mori) 239 | * [seamless-immutable](https://github.com/rtfeldman/seamless-immutable) 240 | * [deep-freeze](https://www.npmjs.com/package/deep-freeze) 241 | * [freezer](https://github.com/arqex/freezer) 242 | * [icedam](https://github.com/winkler1/icedam) 243 | * [ObjectModel](https://github.com/sylvainpolletvillard/ObjectModel) 244 | * [rfx](https://github.com/ericelliott/rfx) 245 | 246 | # License 247 | 248 | The MIT License (MIT) 249 | -------------------------------------------------------------------------------- /docs/images/after_formatting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/after_formatting.png -------------------------------------------------------------------------------- /docs/images/before_formatting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/before_formatting.png -------------------------------------------------------------------------------- /docs/images/chrome-dev-tools-fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/chrome-dev-tools-fail.png -------------------------------------------------------------------------------- /docs/images/chrome-dev-tools-sum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/chrome-dev-tools-sum.png -------------------------------------------------------------------------------- /docs/images/display-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/display-name.png -------------------------------------------------------------------------------- /docs/images/meta-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/meta-object.png -------------------------------------------------------------------------------- /docs/images/struct-deserialisation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/struct-deserialisation.png -------------------------------------------------------------------------------- /docs/images/throws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/throws.png -------------------------------------------------------------------------------- /docs/images/type-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gcanti/tcomb/bef7690a072c056a8604efd376fb4c959a407557/docs/images/type-error.png -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | type Predicate = (x: T) => boolean; 2 | type TypeGuardPredicate = (x: any) => x is T; 3 | 4 | interface Type extends Function { 5 | (value: T): T; 6 | is: TypeGuardPredicate; 7 | displayName: string; 8 | meta: { 9 | kind: string; 10 | name: string; 11 | identity: boolean; 12 | }; 13 | t: T; 14 | } 15 | 16 | // 17 | // irreducible 18 | // 19 | 20 | interface Irreducible extends Type { 21 | meta: { 22 | kind: string; 23 | name: string; 24 | identity: boolean; 25 | predicate: TypeGuardPredicate; 26 | }; 27 | } 28 | 29 | export function irreducible(name: string, predicate: Predicate): Irreducible; 30 | 31 | // 32 | // basic types 33 | // 34 | 35 | export var Any: Irreducible; 36 | export var Nil: Irreducible; 37 | export var String: Irreducible; 38 | export var Number: Irreducible; 39 | export var Integer: Irreducible; 40 | export var Boolean: Irreducible; 41 | export var Array: Irreducible>; 42 | export var Object: Irreducible; // FIXME restrict to POJOs 43 | export var Function: Irreducible; 44 | export var Error: Irreducible; 45 | export var RegExp: Irreducible; 46 | export var Date: Irreducible; 47 | 48 | interface ApplyCommand { $apply: Function; } 49 | interface PushCommand { $push: Array; } 50 | interface RemoveCommand { $remove: Array; } 51 | interface SetCommand { $set: any; } 52 | interface SpliceCommand { $splice: Array>; } 53 | interface SwapCommand { $swap: { from: number; to: number; }; } 54 | interface UnshiftCommand { $unshift: Array; } 55 | interface MergeCommand { $merge: Object; } 56 | type Command = ApplyCommand | PushCommand | RemoveCommand | SetCommand | SpliceCommand | SwapCommand | UnshiftCommand | MergeCommand; 57 | type UpdatePatch = Command | { [key: string]: UpdatePatch }; 58 | type Update = (instance: T, spec: UpdatePatch) => T; 59 | 60 | type Constructor = Type | Function; 61 | 62 | // 63 | // refinement 64 | // 65 | 66 | interface Refinement extends Type { 67 | meta: { 68 | kind: string; 69 | name: string; 70 | identity: boolean; 71 | type: Constructor; 72 | predicate: TypeGuardPredicate; 73 | }; 74 | update: Update; 75 | } 76 | 77 | export function refinement(type: Constructor, predicate: Predicate, name?: string): Refinement; 78 | 79 | // 80 | // struct 81 | // 82 | 83 | type StructProps = { [key: string]: Constructor }; 84 | type StructMixin = StructProps | Struct | Interface; 85 | 86 | interface Struct extends Type { 87 | new(value: T): T; 88 | meta: { 89 | kind: string; 90 | name: string; 91 | identity: boolean; 92 | props: StructProps; 93 | strict: boolean; 94 | defaultProps: object; 95 | }; 96 | update: Update; 97 | extend(mixins: StructMixin | Array, name?: string | StructOptions): Struct; 98 | } 99 | 100 | type StructOptions = { 101 | name?: string, 102 | strict?: boolean, 103 | defaultProps?: object 104 | }; 105 | 106 | export function struct(props: StructProps, name?: string | StructOptions): Struct; 107 | 108 | // 109 | // interface 110 | // 111 | 112 | interface Interface extends Type { 113 | meta: { 114 | kind: string; 115 | name: string; 116 | identity: boolean; 117 | props: StructProps; 118 | strict: boolean; 119 | }; 120 | update: Update; 121 | extend(mixins: StructMixin | Array, name?: string | StructOptions): Struct; 122 | } 123 | 124 | export function interface(props: StructProps, name?: string | StructOptions): Interface; 125 | 126 | // 127 | // list 128 | // 129 | 130 | interface List extends Type> { 131 | meta: { 132 | kind: string; 133 | name: string; 134 | identity: boolean; 135 | type: Constructor; 136 | }; 137 | update: Update>; 138 | } 139 | 140 | export function list(type: Constructor, name?: string): List; 141 | 142 | // 143 | // dict combinator 144 | // 145 | 146 | interface Dict extends Type<{ [key: string]: T; }> { 147 | meta: { 148 | kind: string; 149 | name: string; 150 | identity: boolean; 151 | domain: Constructor; 152 | codomain: T; 153 | }; 154 | update: Update<{ [key: string]: T; }>; 155 | } 156 | 157 | export function dict(domain: Constructor, codomain: Constructor, name?: string): Dict; 158 | 159 | // 160 | // enums combinator 161 | // 162 | 163 | interface Enums extends Type { 164 | meta: { 165 | kind: string; 166 | name: string; 167 | identity: boolean; 168 | map: Object; 169 | }; 170 | } 171 | 172 | interface EnumsFunction extends Function { 173 | (map: Object, name?: string): Enums; 174 | of(enums: string, name?: string): Enums; 175 | of(enums: Array, name?: string): Enums; 176 | } 177 | 178 | export var enums: EnumsFunction; 179 | 180 | // 181 | // maybe combinator 182 | // 183 | 184 | interface Maybe extends Type { 185 | meta: { 186 | kind: string; 187 | name: string; 188 | identity: boolean; 189 | type: Constructor; 190 | }; 191 | update: Update; 192 | } 193 | 194 | export function maybe(type: Constructor, name?: string): Maybe; 195 | 196 | // 197 | // tuple combinator 198 | // 199 | 200 | interface Tuple extends Type { 201 | meta: { 202 | kind: string; 203 | name: string; 204 | identity: boolean; 205 | types: Array>; 206 | }; 207 | update: Update; 208 | } 209 | 210 | export function tuple(types: Array>, name?: string): Tuple; 211 | 212 | // 213 | // union combinator 214 | // 215 | 216 | interface Union extends Type { 217 | meta: { 218 | kind: string; 219 | name: string; 220 | identity: boolean; 221 | types: Array>; 222 | }; 223 | update: Update; 224 | dispatch(x: any): Constructor; 225 | } 226 | 227 | export function union(types: Array>, name?: string): Union; 228 | 229 | // 230 | // intersection combinator 231 | // 232 | 233 | interface Intersection extends Type { 234 | meta: { 235 | kind: string; 236 | name: string; 237 | identity: boolean; 238 | types: Array>; 239 | }; 240 | update: Update; 241 | } 242 | 243 | export function intersection(types: Array>, name?: string): Intersection; 244 | 245 | // 246 | // declare combinator 247 | // 248 | 249 | interface Declare extends Type { 250 | update: Update; 251 | define(type: Type): void; 252 | } 253 | 254 | export function declare(name?: string): Declare; 255 | 256 | // 257 | // other exports 258 | // 259 | 260 | export function is(x: any, type: Constructor): boolean; 261 | type LazyMessage = () => string; 262 | export function assert(guard: boolean, message?: string | LazyMessage): void; 263 | export function fail(message: string): void; 264 | export function isType(x: Constructor): boolean; 265 | export function getTypeName(x: Constructor): string; 266 | export function mixin(target: T, source: S, overwrite?: boolean): T & S; 267 | type Function1 = (x: any) => any; 268 | type Clause = Constructor | Function1; 269 | export function match(x: any, ...clauses: Array): any; // FIXME 270 | export var update: Update; 271 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! @preserve 2 | * 3 | * tcomb.js - Type checking and DDD for JavaScript 4 | * 5 | * The MIT License (MIT) 6 | * 7 | * Copyright (c) 2014-2016 Giulio Canti 8 | * 9 | */ 10 | 11 | // core 12 | var t = require('./lib/assert'); 13 | 14 | // types 15 | t.Any = require('./lib/Any'); 16 | t.Array = require('./lib/Array'); 17 | t.Boolean = require('./lib/Boolean'); 18 | t.Date = require('./lib/Date'); 19 | t.Error = require('./lib/Error'); 20 | t.Function = require('./lib/Function'); 21 | t.Nil = require('./lib/Nil'); 22 | t.Number = require('./lib/Number'); 23 | t.Integer = require('./lib/Integer'); 24 | t.IntegerT = t.Integer; 25 | t.Object = require('./lib/Object'); 26 | t.RegExp = require('./lib/RegExp'); 27 | t.String = require('./lib/String'); 28 | t.Type = require('./lib/Type'); 29 | t.TypeT = t.Type; 30 | 31 | // short alias are deprecated 32 | t.Arr = t.Array; 33 | t.Bool = t.Boolean; 34 | t.Dat = t.Date; 35 | t.Err = t.Error; 36 | t.Func = t.Function; 37 | t.Num = t.Number; 38 | t.Obj = t.Object; 39 | t.Re = t.RegExp; 40 | t.Str = t.String; 41 | 42 | // combinators 43 | t.dict = require('./lib/dict'); 44 | t.declare = require('./lib/declare'); 45 | t.enums = require('./lib/enums'); 46 | t.irreducible = require('./lib/irreducible'); 47 | t.list = require('./lib/list'); 48 | t.maybe = require('./lib/maybe'); 49 | t.refinement = require('./lib/refinement'); 50 | t.struct = require('./lib/struct'); 51 | t.tuple = require('./lib/tuple'); 52 | t.union = require('./lib/union'); 53 | t.func = require('./lib/func'); 54 | t.intersection = require('./lib/intersection'); 55 | t.subtype = t.refinement; 56 | t.inter = require('./lib/interface'); // IE8 alias 57 | t['interface'] = t.inter; 58 | 59 | // functions 60 | t.assert = t; 61 | t.update = require('./lib/update'); 62 | t.mixin = require('./lib/mixin'); 63 | t.isType = require('./lib/isType'); 64 | t.is = require('./lib/is'); 65 | t.getTypeName = require('./lib/getTypeName'); 66 | t.match = require('./lib/match'); 67 | 68 | module.exports = t; 69 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | ### Version 2 | 3 | Tell us which version you are using 4 | 5 | ### Expected behaviour 6 | 7 | Tell us what should happen 8 | 9 | ### Actual behaviour 10 | 11 | Tell us what happens instead 12 | 13 | ### Steps to reproduce 14 | 15 | 1. 16 | 2. 17 | 3. 18 | 19 | ### Stack trace and console log 20 | 21 | Hint: it would help a lot if you enable the debugger ("Pause on exceptions" in the "Source" panel of Chrome dev tools) and spot the place where the error is thrown 22 | 23 | ``` 24 | ``` 25 | -------------------------------------------------------------------------------- /lib/Any.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | 3 | module.exports = irreducible('Any', function () { return true; }); 4 | -------------------------------------------------------------------------------- /lib/Array.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isArray = require('./isArray'); 3 | 4 | module.exports = irreducible('Array', isArray); 5 | -------------------------------------------------------------------------------- /lib/Boolean.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isBoolean = require('./isBoolean'); 3 | 4 | module.exports = irreducible('Boolean', isBoolean); 5 | -------------------------------------------------------------------------------- /lib/Date.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | 3 | module.exports = irreducible('Date', function (x) { return x instanceof Date; }); 4 | -------------------------------------------------------------------------------- /lib/Error.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | 3 | module.exports = irreducible('Error', function (x) { return x instanceof Error; }); 4 | -------------------------------------------------------------------------------- /lib/Function.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isFunction = require('./isFunction'); 3 | 4 | module.exports = irreducible('Function', isFunction); 5 | -------------------------------------------------------------------------------- /lib/Integer.js: -------------------------------------------------------------------------------- 1 | var refinement = require('./refinement'); 2 | var Number = require('./Number'); 3 | 4 | module.exports = refinement(Number, function (x) { return x % 1 === 0; }, 'Integer'); 5 | -------------------------------------------------------------------------------- /lib/Nil.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isNil = require('./isNil'); 3 | 4 | module.exports = irreducible('Nil', isNil); 5 | -------------------------------------------------------------------------------- /lib/Number.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isNumber = require('./isNumber'); 3 | 4 | module.exports = irreducible('Number', isNumber); 5 | -------------------------------------------------------------------------------- /lib/Object.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isObject = require('./isObject'); 3 | 4 | module.exports = irreducible('Object', isObject); 5 | -------------------------------------------------------------------------------- /lib/RegExp.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | 3 | module.exports = irreducible('RegExp', function (x) { return x instanceof RegExp; }); 4 | -------------------------------------------------------------------------------- /lib/String.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isString = require('./isString'); 3 | 4 | module.exports = irreducible('String', isString); 5 | -------------------------------------------------------------------------------- /lib/Type.js: -------------------------------------------------------------------------------- 1 | var irreducible = require('./irreducible'); 2 | var isType = require('./isType'); 3 | 4 | module.exports = irreducible('Type', isType); -------------------------------------------------------------------------------- /lib/assert.js: -------------------------------------------------------------------------------- 1 | var isFunction = require('./isFunction'); 2 | var isNil = require('./isNil'); 3 | var fail = require('./fail'); 4 | var stringify = require('./stringify'); 5 | 6 | function assert(guard, message) { 7 | if (guard !== true) { 8 | if (isFunction(message)) { // handle lazy messages 9 | message = message(); 10 | } 11 | else if (isNil(message)) { // use a default message 12 | message = 'Assert failed (turn on "Pause on exceptions" in your Source panel)'; 13 | } 14 | assert.fail(message); 15 | } 16 | } 17 | 18 | assert.fail = fail; 19 | assert.stringify = stringify; 20 | 21 | module.exports = assert; -------------------------------------------------------------------------------- /lib/assign.js: -------------------------------------------------------------------------------- 1 | function assign(x, y) { 2 | for (var k in y) { 3 | if (y.hasOwnProperty(k)) { 4 | x[k] = y[k]; 5 | } 6 | } 7 | return x; 8 | } 9 | 10 | module.exports = assign; 11 | -------------------------------------------------------------------------------- /lib/create.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | var getFunctionName = require('./getFunctionName'); 3 | var assert = require('./assert'); 4 | var stringify = require('./stringify'); 5 | 6 | // creates an instance of a type, handling the optional new operator 7 | module.exports = function create(type, value, path) { 8 | if (isType(type)) { 9 | return !type.meta.identity && typeof value === 'object' && value !== null ? new type(value, path): type(value, path); 10 | } 11 | 12 | if (process.env.NODE_ENV !== 'production') { 13 | // here type should be a class constructor and value some instance, just check membership and return the value 14 | path = path || [getFunctionName(type)]; 15 | assert(value instanceof type, function () { return 'Invalid value ' + stringify(value) + ' supplied to ' + path.join('/'); }); 16 | } 17 | 18 | return value; 19 | }; -------------------------------------------------------------------------------- /lib/declare.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isType = require('./isType'); 4 | var isNil = require('./isNil'); 5 | var mixin = require('./mixin'); 6 | var getTypeName = require('./getTypeName'); 7 | var isUnion = require('./isUnion'); 8 | 9 | // All the .declare-d types should be clearly different from each other thus they should have 10 | // different names when a name was not explicitly provided. 11 | var nextDeclareUniqueId = 1; 12 | 13 | module.exports = function declare(name) { 14 | if (process.env.NODE_ENV !== 'production') { 15 | assert(isTypeName(name), function () { return 'Invalid argument name ' + name + ' supplied to declare([name]) (expected a string)'; }); 16 | } 17 | 18 | var type; 19 | 20 | function Declare(value, path) { 21 | if (process.env.NODE_ENV !== 'production') { 22 | assert(!isNil(type), function () { return 'Type declared but not defined, don\'t forget to call .define on every declared type'; }); 23 | if (isUnion(type)) { 24 | assert(type.dispatch === Declare.dispatch, function () { return 'Please define the custom ' + name + '.dispatch function before calling ' + name + '.define()'; }); 25 | } 26 | } 27 | return type(value, path); 28 | } 29 | 30 | Declare.define = function (spec) { 31 | if (process.env.NODE_ENV !== 'production') { 32 | assert(isType(spec), function () { return 'Invalid argument type ' + assert.stringify(spec) + ' supplied to define(type) (expected a type)'; }); 33 | assert(isNil(type), function () { return 'Declare.define(type) can only be invoked once'; }); 34 | // assert(isNil(spec.meta.name) && Object.keys(spec.prototype).length === 0, function () { return 'Invalid argument type ' + assert.stringify(spec) + ' supplied to define(type) (expected a fresh, unnamed type)'; }); 35 | } 36 | 37 | if (isUnion(spec) && Declare.hasOwnProperty('dispatch')) { 38 | spec.dispatch = Declare.dispatch; 39 | } 40 | type = spec; 41 | mixin(Declare, type, true); // true because it overwrites Declare.displayName 42 | if (name) { 43 | type.displayName = Declare.displayName = name; 44 | Declare.meta.name = name; 45 | } 46 | Declare.meta.identity = type.meta.identity; 47 | Declare.prototype = type.prototype; 48 | return Declare; 49 | }; 50 | 51 | Declare.displayName = name || ( getTypeName(Declare) + "$" + nextDeclareUniqueId++ ); 52 | // in general I can't say if this type will be an identity, for safety setting to false 53 | Declare.meta = { identity: false }; 54 | Declare.prototype = null; 55 | return Declare; 56 | }; 57 | -------------------------------------------------------------------------------- /lib/decompose.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | 3 | function isRefinement(type) { 4 | return isType(type) && type.meta.kind === 'subtype'; 5 | } 6 | 7 | function getPredicates(type) { 8 | return isRefinement(type) ? 9 | [type.meta.predicate].concat(getPredicates(type.meta.type)) : 10 | []; 11 | } 12 | 13 | function getUnrefinedType(type) { 14 | return isRefinement(type) ? 15 | getUnrefinedType(type.meta.type) : 16 | type; 17 | } 18 | 19 | function decompose(type) { 20 | return { 21 | predicates: getPredicates(type), 22 | unrefinedType: getUnrefinedType(type) 23 | }; 24 | } 25 | 26 | module.exports = decompose; -------------------------------------------------------------------------------- /lib/dict.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var getTypeName = require('./getTypeName'); 5 | var isIdentity = require('./isIdentity'); 6 | var isObject = require('./isObject'); 7 | var create = require('./create'); 8 | var is = require('./is'); 9 | 10 | function getDefaultName(domain, codomain) { 11 | return '{[key: ' + getTypeName(domain) + ']: ' + getTypeName(codomain) + '}'; 12 | } 13 | 14 | function dict(domain, codomain, name) { 15 | 16 | if (process.env.NODE_ENV !== 'production') { 17 | assert(isFunction(domain), function () { return 'Invalid argument domain ' + assert.stringify(domain) + ' supplied to dict(domain, codomain, [name]) combinator (expected a type)'; }); 18 | assert(isFunction(codomain), function () { return 'Invalid argument codomain ' + assert.stringify(codomain) + ' supplied to dict(domain, codomain, [name]) combinator (expected a type)'; }); 19 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to dict(domain, codomain, [name]) combinator (expected a string)'; }); 20 | } 21 | 22 | var displayName = name || getDefaultName(domain, codomain); 23 | var domainNameCache = getTypeName(domain); 24 | var codomainNameCache = getTypeName(codomain); 25 | var identity = isIdentity(domain) && isIdentity(codomain); 26 | 27 | function Dict(value, path) { 28 | 29 | if (process.env.NODE_ENV === 'production') { 30 | if (identity) { 31 | return value; // just trust the input if elements must not be hydrated 32 | } 33 | } 34 | 35 | if (process.env.NODE_ENV !== 'production') { 36 | path = path || [displayName]; 37 | assert(isObject(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); }); 38 | } 39 | 40 | var idempotent = true; // will remain true if I can reutilise the input 41 | var ret = {}; // make a temporary copy, will be discarded if idempotent remains true 42 | for (var k in value) { 43 | if (value.hasOwnProperty(k)) { 44 | k = create(domain, k, ( process.env.NODE_ENV !== 'production' ? path.concat(domainNameCache) : null )); 45 | var actual = value[k]; 46 | var instance = create(codomain, actual, ( process.env.NODE_ENV !== 'production' ? path.concat(k + ': ' + codomainNameCache) : null )); 47 | idempotent = idempotent && ( actual === instance ); 48 | ret[k] = instance; 49 | } 50 | } 51 | 52 | if (idempotent) { // implements idempotency 53 | ret = value; 54 | } 55 | 56 | if (process.env.NODE_ENV !== 'production') { 57 | Object.freeze(ret); 58 | } 59 | 60 | return ret; 61 | } 62 | 63 | Dict.meta = { 64 | kind: 'dict', 65 | domain: domain, 66 | codomain: codomain, 67 | name: name, 68 | identity: identity 69 | }; 70 | 71 | Dict.displayName = displayName; 72 | 73 | Dict.is = function (x) { 74 | if (!isObject(x)) { 75 | return false; 76 | } 77 | for (var k in x) { 78 | if (x.hasOwnProperty(k)) { 79 | if (!is(k, domain) || !is(x[k], codomain)) { 80 | return false; 81 | } 82 | } 83 | } 84 | return true; 85 | }; 86 | 87 | Dict.update = function (instance, patch) { 88 | return Dict(assert.update(instance, patch)); 89 | }; 90 | 91 | return Dict; 92 | } 93 | 94 | dict.getDefaultName = getDefaultName; 95 | module.exports = dict; 96 | -------------------------------------------------------------------------------- /lib/enums.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var forbidNewOperator = require('./forbidNewOperator'); 4 | var isNumber = require('./isNumber'); 5 | var isString = require('./isString'); 6 | var isObject = require('./isObject'); 7 | 8 | function getDefaultName(map) { 9 | return Object.keys(map).map(function (k) { return assert.stringify(k); }).join(' | '); 10 | } 11 | 12 | function enums(map, name) { 13 | 14 | if (process.env.NODE_ENV !== 'production') { 15 | assert(isObject(map), function () { return 'Invalid argument map ' + assert.stringify(map) + ' supplied to enums(map, [name]) combinator (expected a dictionary of String -> String | Number)'; }); 16 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to enums(map, [name]) combinator (expected a string)'; }); 17 | } 18 | 19 | var displayName = name || getDefaultName(map); 20 | 21 | function Enums(value, path) { 22 | 23 | if (process.env.NODE_ENV !== 'production') { 24 | forbidNewOperator(this, Enums); 25 | path = path || [displayName]; 26 | assert(Enums.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected one of ' + assert.stringify(Object.keys(map)) + ')'; }); 27 | } 28 | 29 | return value; 30 | } 31 | 32 | Enums.meta = { 33 | kind: 'enums', 34 | map: map, 35 | name: name, 36 | identity: true 37 | }; 38 | 39 | Enums.displayName = displayName; 40 | 41 | Enums.is = function (x) { 42 | return (isString(x) || isNumber(x)) && map.hasOwnProperty(x); 43 | }; 44 | 45 | return Enums; 46 | } 47 | 48 | enums.of = function (keys, name) { 49 | keys = isString(keys) ? keys.split(' ') : keys; 50 | var value = {}; 51 | keys.forEach(function (k) { 52 | value[k] = k; 53 | }); 54 | return enums(value, name); 55 | }; 56 | 57 | enums.getDefaultName = getDefaultName; 58 | module.exports = enums; 59 | 60 | -------------------------------------------------------------------------------- /lib/extend.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isFunction = require('./isFunction'); 3 | var isArray = require('./isArray'); 4 | var mixin = require('./mixin'); 5 | var isStruct = require('./isStruct'); 6 | var isInterface = require('./isInterface'); 7 | var isObject = require('./isObject'); 8 | var refinement = require('./refinement'); 9 | var decompose = require('./decompose'); 10 | 11 | function compose(predicates, unrefinedType, name) { 12 | var result = predicates.reduce(function (type, predicate) { 13 | return refinement(type, predicate); 14 | }, unrefinedType); 15 | if (name) { 16 | result.displayName = name; 17 | result.meta.name = name; 18 | } 19 | return result; 20 | } 21 | 22 | function getProps(type) { 23 | return isObject(type) ? type : type.meta.props; 24 | } 25 | 26 | function getDefaultProps(type) { 27 | return isObject(type) ? null : type.meta.defaultProps; 28 | } 29 | 30 | function pushAll(arr, elements) { 31 | Array.prototype.push.apply(arr, elements); 32 | } 33 | 34 | function extend(combinator, mixins, options) { 35 | if (process.env.NODE_ENV !== 'production') { 36 | assert(isFunction(combinator), function () { return 'Invalid argument combinator supplied to extend(combinator, mixins, options), expected a function'; }); 37 | assert(isArray(mixins), function () { return 'Invalid argument mixins supplied to extend(combinator, mixins, options), expected an array'; }); 38 | } 39 | var props = {}; 40 | var prototype = {}; 41 | var predicates = []; 42 | var defaultProps = {}; 43 | mixins.forEach(function (x, i) { 44 | var decomposition = decompose(x); 45 | var unrefinedType = decomposition.unrefinedType; 46 | if (process.env.NODE_ENV !== 'production') { 47 | assert(isObject(unrefinedType) || isStruct(unrefinedType) || isInterface(unrefinedType), function () { return 'Invalid argument mixins[' + i + '] supplied to extend(combinator, mixins, options), expected an object, struct, interface or a refinement (of struct or interface)'; }); 48 | } 49 | pushAll(predicates, decomposition.predicates); 50 | mixin(props, getProps(unrefinedType)); 51 | mixin(prototype, unrefinedType.prototype); 52 | mixin(defaultProps, getDefaultProps(unrefinedType), true); 53 | }); 54 | options = combinator.getOptions(options); 55 | options.defaultProps = mixin(defaultProps, options.defaultProps, true); 56 | var result = compose(predicates, combinator(props, { 57 | strict: options.strict, 58 | defaultProps: options.defaultProps 59 | }), options.name); 60 | mixin(result.prototype, prototype); 61 | return result; 62 | } 63 | 64 | module.exports = extend; 65 | -------------------------------------------------------------------------------- /lib/fail.js: -------------------------------------------------------------------------------- 1 | module.exports = function fail(message) { 2 | throw new TypeError('[tcomb] ' + message); 3 | }; -------------------------------------------------------------------------------- /lib/forbidNewOperator.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var getTypeName = require('./getTypeName'); 3 | 4 | module.exports = function forbidNewOperator(x, type) { 5 | assert(!(x instanceof type), function () { return 'Cannot use the new operator to instantiate the type ' + getTypeName(type); }); 6 | }; -------------------------------------------------------------------------------- /lib/fromJSON.d.ts: -------------------------------------------------------------------------------- 1 | import * as t from '../.' 2 | 3 | declare function fromJSON(value: any, type: t.Type): T; 4 | 5 | export default fromJSON -------------------------------------------------------------------------------- /lib/fromJSON.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isFunction = require('./isFunction'); 3 | var isNil = require('./isNil'); 4 | var getTypeName = require('./getTypeName'); 5 | var isObject = require('./isObject'); 6 | var isArray = require('./isArray'); 7 | var isType = require('./isType'); 8 | var create = require('./create'); 9 | var assign = require('./assign'); 10 | 11 | function assignMany(values) { 12 | return values.reduce(function (acc, v) { 13 | return assign(acc, v); 14 | }, {}); 15 | } 16 | 17 | function fromJSON(value, type, path) { 18 | if (process.env.NODE_ENV !== 'production') { 19 | assert(isFunction(type), function () { 20 | return 'Invalid argument type ' + assert.stringify(type) + ' supplied to fromJSON(value, type) (expected a type)'; 21 | }); 22 | path = path || [getTypeName(type)]; 23 | } 24 | 25 | if (isFunction(type.fromJSON)) { 26 | return create(type, type.fromJSON(value), path); 27 | } 28 | 29 | if (!isType(type)) { 30 | return value instanceof type ? value : new type(value); 31 | } 32 | 33 | var kind = type.meta.kind; 34 | var k; 35 | var ret; 36 | 37 | switch (kind) { 38 | 39 | case 'maybe' : 40 | return isNil(value) ? null : fromJSON(value, type.meta.type, path); 41 | 42 | case 'subtype' : // the kind of a refinement is 'subtype' (for legacy reasons) 43 | ret = fromJSON(value, type.meta.type, path); 44 | if (process.env.NODE_ENV !== 'production') { 45 | assert(type.meta.predicate(ret), function () { 46 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected a valid ' + getTypeName(type) + ')'; 47 | }); 48 | } 49 | return ret; 50 | 51 | case 'struct' : 52 | if (process.env.NODE_ENV !== 'production') { 53 | assert(isObject(value), function () { 54 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an object for type ' + getTypeName(type) + ')'; 55 | }); 56 | } 57 | var props = type.meta.props; 58 | var defaultProps = type.meta.defaultProps; 59 | ret = {}; 60 | for (k in props) { 61 | var actual = value[k]; 62 | if (actual === undefined) { 63 | actual = defaultProps[k]; 64 | } 65 | if (props.hasOwnProperty(k)) { 66 | ret[k] = fromJSON(actual, props[k], ( process.env.NODE_ENV !== 'production' ? path.concat(k + ': ' + getTypeName(props[k])) : null )); 67 | } 68 | } 69 | return new type(ret); 70 | 71 | case 'interface' : 72 | if (process.env.NODE_ENV !== 'production') { 73 | assert(isObject(value), function () { 74 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an object)'; 75 | }); 76 | } 77 | var interProps = type.meta.props; 78 | ret = {}; 79 | for (k in interProps) { 80 | if (interProps.hasOwnProperty(k)) { 81 | ret[k] = fromJSON(value[k], interProps[k], ( process.env.NODE_ENV !== 'production' ? path.concat(k + ': ' + getTypeName(interProps[k])) : null )); 82 | } 83 | } 84 | return ret; 85 | 86 | case 'list' : 87 | if (process.env.NODE_ENV !== 'production') { 88 | assert(isArray(value), function () { 89 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an array for type ' + getTypeName(type) + ')'; 90 | }); 91 | } 92 | var elementType = type.meta.type; 93 | var elementTypeName = getTypeName(elementType); 94 | return value.map(function (element, i) { 95 | return fromJSON(element, elementType, ( process.env.NODE_ENV !== 'production' ? path.concat(i + ': ' + elementTypeName) : null )); 96 | }); 97 | 98 | case 'union' : 99 | var actualType = type.dispatch(value); 100 | if (process.env.NODE_ENV !== 'production') { 101 | assert(isFunction(actualType), function () { 102 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (no constructor returned by dispatch of union ' + getTypeName(type) + ')'; 103 | }); 104 | } 105 | return fromJSON(value, actualType, path); 106 | 107 | case 'tuple' : 108 | if (process.env.NODE_ENV !== 'production') { 109 | assert(isArray(value), function () { 110 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an array for type ' + getTypeName(type) + ')'; 111 | }); 112 | } 113 | var types = type.meta.types; 114 | if (process.env.NODE_ENV !== 'production') { 115 | assert(isArray(value) && value.length === types.length, function () { 116 | return 'Invalid value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an array of length ' + types.length + ' for type ' + getTypeName(type) + ')'; 117 | }); 118 | } 119 | return value.map(function (element, i) { 120 | return fromJSON(element, types[i], ( process.env.NODE_ENV !== 'production' ? path.concat(i + ': ' + getTypeName(types[i])) : null )); 121 | }); 122 | 123 | case 'dict' : 124 | if (process.env.NODE_ENV !== 'production') { 125 | assert(isObject(value), function () { 126 | return 'Invalid argument value ' + assert.stringify(value) + ' supplied to fromJSON(value, type) (expected an object for type ' + getTypeName(type) + ')'; 127 | }); 128 | } 129 | var domain = type.meta.domain; 130 | var codomain = type.meta.codomain; 131 | var domainName = getTypeName(domain); 132 | var codomainName = getTypeName(codomain); 133 | ret = {}; 134 | for (k in value) { 135 | if (value.hasOwnProperty(k)) { 136 | ret[domain(k, ( process.env.NODE_ENV !== 'production' ? path.concat(domainName) : null ))] = fromJSON(value[k], codomain, ( process.env.NODE_ENV !== 'production' ? path.concat(k + ': ' + codomainName) : null )); 137 | } 138 | } 139 | return ret; 140 | 141 | case 'intersection' : 142 | var values = type.meta.types.map(function (type, i) { 143 | return fromJSON(value, type, ( process.env.NODE_ENV !== 'production' ? path.concat(i + ': ' + getTypeName(type)) : null )); 144 | }); 145 | return type( 146 | isObject(values[0]) ? assignMany(values) : value, 147 | path 148 | ); 149 | 150 | default : // enums, irreducible 151 | return type(value, path); 152 | } 153 | } 154 | 155 | module.exports = fromJSON; 156 | -------------------------------------------------------------------------------- /lib/func.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var FunctionType = require('./Function'); 4 | var isArray = require('./isArray'); 5 | var list = require('./list'); 6 | var isObject = require('./isObject'); 7 | var create = require('./create'); 8 | var isNil = require('./isNil'); 9 | var isBoolean = require('./isBoolean'); 10 | var tuple = require('./tuple'); 11 | var getFunctionName = require('./getFunctionName'); 12 | var getTypeName = require('./getTypeName'); 13 | var isType = require('./isType'); 14 | 15 | function getDefaultName(domain, codomain) { 16 | return '(' + domain.map(getTypeName).join(', ') + ') => ' + getTypeName(codomain); 17 | } 18 | 19 | function isInstrumented(f) { 20 | return FunctionType.is(f) && isObject(f.instrumentation); 21 | } 22 | 23 | function getOptionalArgumentsIndex(types) { 24 | var end = types.length; 25 | var areAllMaybes = false; 26 | for (var i = end - 1; i >= 0; i--) { 27 | var type = types[i]; 28 | if (!isType(type) || type.meta.kind !== 'maybe') { 29 | return (i + 1); 30 | } else { 31 | areAllMaybes = true; 32 | } 33 | } 34 | return areAllMaybes ? 0 : end; 35 | } 36 | 37 | function func(domain, codomain, name) { 38 | 39 | domain = isArray(domain) ? domain : [domain]; // handle handy syntax for unary functions 40 | 41 | if (process.env.NODE_ENV !== 'production') { 42 | assert(list(FunctionType).is(domain), function () { return 'Invalid argument domain ' + assert.stringify(domain) + ' supplied to func(domain, codomain, [name]) combinator (expected an array of types)'; }); 43 | assert(FunctionType.is(codomain), function () { return 'Invalid argument codomain ' + assert.stringify(codomain) + ' supplied to func(domain, codomain, [name]) combinator (expected a type)'; }); 44 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to func(domain, codomain, [name]) combinator (expected a string)'; }); 45 | } 46 | 47 | var displayName = name || getDefaultName(domain, codomain); 48 | var domainLength = domain.length; 49 | var optionalArgumentsIndex = getOptionalArgumentsIndex(domain); 50 | 51 | function FuncType(value, path) { 52 | 53 | if (!isInstrumented(value)) { // automatically instrument the function 54 | return FuncType.of(value); 55 | } 56 | 57 | if (process.env.NODE_ENV !== 'production') { 58 | path = path || [displayName]; 59 | assert(FuncType.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); }); 60 | } 61 | 62 | return value; 63 | } 64 | 65 | FuncType.meta = { 66 | kind: 'func', 67 | domain: domain, 68 | codomain: codomain, 69 | name: name, 70 | identity: true 71 | }; 72 | 73 | FuncType.displayName = displayName; 74 | 75 | FuncType.is = function (x) { 76 | return isInstrumented(x) && 77 | x.instrumentation.domain.length === domainLength && 78 | x.instrumentation.domain.every(function (type, i) { 79 | return type === domain[i]; 80 | }) && 81 | x.instrumentation.codomain === codomain; 82 | }; 83 | 84 | FuncType.of = function (f, curried) { 85 | 86 | if (process.env.NODE_ENV !== 'production') { 87 | assert(FunctionType.is(f), function () { return 'Invalid argument f supplied to func.of ' + displayName + ' (expected a function)'; }); 88 | assert(isNil(curried) || isBoolean(curried), function () { return 'Invalid argument curried ' + assert.stringify(curried) + ' supplied to func.of ' + displayName + ' (expected a boolean)'; }); 89 | } 90 | 91 | if (FuncType.is(f)) { // makes FuncType.of idempotent 92 | return f; 93 | } 94 | 95 | function fn() { 96 | var args = Array.prototype.slice.call(arguments); 97 | var argsLength = args.length; 98 | 99 | if (process.env.NODE_ENV !== 'production') { 100 | // type-check arguments 101 | var tupleLength = curried ? argsLength : Math.max(argsLength, optionalArgumentsIndex); 102 | tuple(domain.slice(0, tupleLength), 'arguments of function ' + displayName)(args); 103 | } 104 | 105 | if (curried && argsLength < domainLength) { 106 | if (process.env.NODE_ENV !== 'production') { 107 | assert(argsLength > 0, 'Invalid arguments.length = 0 for curried function ' + displayName); 108 | } 109 | var g = Function.prototype.bind.apply(f, [this].concat(args)); 110 | var newDomain = func(domain.slice(argsLength), codomain); 111 | return newDomain.of(g, true); 112 | } 113 | else { 114 | return create(codomain, f.apply(this, args)); 115 | } 116 | } 117 | 118 | fn.instrumentation = { 119 | domain: domain, 120 | codomain: codomain, 121 | f: f 122 | }; 123 | 124 | fn.displayName = getFunctionName(f); 125 | 126 | return fn; 127 | 128 | }; 129 | 130 | return FuncType; 131 | 132 | } 133 | 134 | func.getDefaultName = getDefaultName; 135 | func.getOptionalArgumentsIndex = getOptionalArgumentsIndex; 136 | module.exports = func; 137 | -------------------------------------------------------------------------------- /lib/getDefaultInterfaceName.js: -------------------------------------------------------------------------------- 1 | var getTypeName = require('./getTypeName'); 2 | 3 | function getDefaultInterfaceName(props) { 4 | return '{' + Object.keys(props).map(function (prop) { 5 | return prop + ': ' + getTypeName(props[prop]); 6 | }).join(', ') + '}'; 7 | } 8 | 9 | module.exports = getDefaultInterfaceName; 10 | -------------------------------------------------------------------------------- /lib/getFunctionName.js: -------------------------------------------------------------------------------- 1 | module.exports = function getFunctionName(f) { 2 | return f.displayName || f.name || ''; 3 | }; -------------------------------------------------------------------------------- /lib/getTypeName.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | var getFunctionName = require('./getFunctionName'); 3 | 4 | module.exports = function getTypeName(ctor) { 5 | if (isType(ctor)) { 6 | return ctor.displayName; 7 | } 8 | return getFunctionName(ctor); 9 | }; -------------------------------------------------------------------------------- /lib/installTypeFormatter.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | var getTypeName = require('./getTypeName'); 3 | 4 | var listStyle = {style: 'list-style-type: none; padding: 5px 10px; margin: 0;'}; 5 | var listItemStyle = {style: 'padding: 3px 0;'}; 6 | var typeStyle = {style: 'font-weight: bolder;'}; 7 | var propStyle = {style: 'color: #881391;'}; 8 | var metaStyle = {style: 'color: #777;'}; 9 | 10 | function getKindName(kind) { 11 | return kind === 'subtype' ? 'refinement' : kind; 12 | } 13 | 14 | var TypeFormatter = { 15 | header: function (x) { 16 | if (!isType(x)) { 17 | return null; 18 | } 19 | return ['span', 20 | ['span', typeStyle, getTypeName(x)], 21 | ' (' + getKindName(x.meta.kind) + ')' 22 | ]; 23 | }, 24 | hasBody: function (x) { 25 | return x.meta.kind !== 'irreducible'; 26 | }, 27 | body: function (x) { 28 | if (x.meta.kind === 'struct' || x.meta.kind === 'interface') { 29 | var props = Object.keys(x.meta.props).map(function (prop) { 30 | return ['li', listItemStyle, 31 | ['span', propStyle, prop + ': '], 32 | ['object', { object: x.meta.props[prop] }] 33 | ]; 34 | }); 35 | return ['ol', listStyle].concat(props); 36 | } 37 | if (x.meta.kind === 'dict') { 38 | return ['ol', listStyle, 39 | ['li', listItemStyle, 40 | ['span', metaStyle, 'domain: '], 41 | ['object', { object: x.meta.domain }] 42 | ], 43 | ['li', listItemStyle, 44 | ['span', metaStyle, 'codomain: '], 45 | ['object', { object: x.meta.codomain }] 46 | ] 47 | ]; 48 | } 49 | if (x.meta.kind === 'list' || x.meta.kind === 'subtype' || x.meta.kind === 'maybe') { 50 | return ['ol', listStyle, 51 | ['li', listItemStyle, 52 | ['span', metaStyle, 'type: '], 53 | ['object', { object: x.meta.type }] 54 | ] 55 | ]; 56 | } 57 | if (x.meta.kind === 'enums') { 58 | var enums = Object.keys(x.meta.map).map(function (e) { 59 | return ['li', listItemStyle, 60 | ['span', propStyle, e + ': '], 61 | ['object', { object: x.meta.map[e] }] 62 | ]; 63 | }); 64 | return ['ol', listStyle].concat(enums); 65 | } 66 | if (x.meta.kind === 'union' || x.meta.kind === 'tuple' || x.meta.kind === 'intersection') { 67 | var types = x.meta.types.map(function (type) { 68 | return ['li', listItemStyle, 69 | ['object', { object: type }] 70 | ]; 71 | }); 72 | return ['ol', listStyle].concat(types); 73 | } 74 | } 75 | }; 76 | 77 | function installTypeFormatter() { 78 | if (typeof window !== 'undefined') { 79 | window.devtoolsFormatters = window.devtoolsFormatters || []; 80 | window.devtoolsFormatters.push(TypeFormatter); 81 | } 82 | } 83 | 84 | installTypeFormatter.TypeFormatter = TypeFormatter; 85 | module.exports = installTypeFormatter; 86 | -------------------------------------------------------------------------------- /lib/interface.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var String = require('./String'); 4 | var Function = require('./Function'); 5 | var isBoolean = require('./isBoolean'); 6 | var isObject = require('./isObject'); 7 | var isNil = require('./isNil'); 8 | var create = require('./create'); 9 | var getTypeName = require('./getTypeName'); 10 | var dict = require('./dict'); 11 | var getDefaultInterfaceName = require('./getDefaultInterfaceName'); 12 | var isIdentity = require('./isIdentity'); 13 | var is = require('./is'); 14 | var extend = require('./extend'); 15 | var assign = require('./assign'); 16 | 17 | function extendInterface(mixins, name) { 18 | return extend(inter, mixins, name); 19 | } 20 | 21 | function getOptions(options) { 22 | if (!isObject(options)) { 23 | options = isNil(options) ? {} : { name: options }; 24 | } 25 | if (!options.hasOwnProperty('strict')) { 26 | options.strict = inter.strict; 27 | } 28 | return options; 29 | } 30 | 31 | function inter(props, options) { 32 | 33 | options = getOptions(options); 34 | var name = options.name; 35 | var strict = options.strict; 36 | 37 | if (process.env.NODE_ENV !== 'production') { 38 | assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)'; }); 39 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to interface(props, [options]) combinator (expected a string)'; }); 40 | assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; }); 41 | } 42 | 43 | var displayName = name || getDefaultInterfaceName(props); 44 | var identity = Object.keys(props).map(function (prop) { return props[prop]; }).every(isIdentity); 45 | 46 | function Interface(value, path) { 47 | 48 | if (process.env.NODE_ENV === 'production') { 49 | if (identity) { 50 | return value; // just trust the input if elements must not be hydrated 51 | } 52 | } 53 | 54 | if (process.env.NODE_ENV !== 'production') { 55 | path = path || [displayName]; 56 | assert(!isNil(value), function () { return 'Invalid value ' + value + ' supplied to ' + path.join('/'); }); 57 | // strictness 58 | if (strict) { 59 | for (var k in value) { 60 | assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); }); 61 | } 62 | } 63 | } 64 | 65 | var idempotent = true; 66 | var ret = identity ? {} : assign({}, value); 67 | for (var prop in props) { 68 | var expected = props[prop]; 69 | var actual = value[prop]; 70 | var instance = create(expected, actual, ( process.env.NODE_ENV !== 'production' ? path.concat(prop + ': ' + getTypeName(expected)) : null )); 71 | idempotent = idempotent && ( actual === instance ); 72 | ret[prop] = instance; 73 | } 74 | 75 | if (idempotent) { // implements idempotency 76 | ret = value; 77 | } 78 | 79 | if (process.env.NODE_ENV !== 'production') { 80 | Object.freeze(ret); 81 | } 82 | 83 | return ret; 84 | 85 | } 86 | 87 | Interface.meta = { 88 | kind: 'interface', 89 | props: props, 90 | name: name, 91 | identity: identity, 92 | strict: strict 93 | }; 94 | 95 | Interface.displayName = displayName; 96 | 97 | Interface.is = function (x) { 98 | if (isNil(x)) { 99 | return false; 100 | } 101 | if (strict) { 102 | for (var k in x) { 103 | if (!props.hasOwnProperty(k)) { 104 | return false; 105 | } 106 | } 107 | } 108 | for (var prop in props) { 109 | if (!is(x[prop], props[prop])) { 110 | return false; 111 | } 112 | } 113 | return true; 114 | }; 115 | 116 | Interface.update = function (instance, patch) { 117 | return Interface(assert.update(instance, patch)); 118 | }; 119 | 120 | Interface.extend = function (xs, name) { 121 | return extendInterface([Interface].concat(xs), name); 122 | }; 123 | 124 | return Interface; 125 | } 126 | 127 | inter.strict = false; 128 | inter.getOptions = getOptions; 129 | inter.getDefaultName = getDefaultInterfaceName; 130 | inter.extend = extendInterface; 131 | module.exports = inter; 132 | -------------------------------------------------------------------------------- /lib/intersection.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var isArray = require('./isArray'); 5 | var forbidNewOperator = require('./isIdentity'); 6 | var is = require('./is'); 7 | var getTypeName = require('./getTypeName'); 8 | var isIdentity = require('./isIdentity'); 9 | 10 | function getDefaultName(types) { 11 | return types.map(getTypeName).join(' & '); 12 | } 13 | 14 | function intersection(types, name) { 15 | 16 | if (process.env.NODE_ENV !== 'production') { 17 | assert(isArray(types) && types.every(isFunction) && types.length >= 2, function () { return 'Invalid argument types ' + assert.stringify(types) + ' supplied to intersection(types, [name]) combinator (expected an array of at least 2 types)'; }); 18 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to intersection(types, [name]) combinator (expected a string)'; }); 19 | } 20 | 21 | var displayName = name || getDefaultName(types); 22 | var identity = types.every(isIdentity); 23 | 24 | function Intersection(value, path) { 25 | 26 | if (process.env.NODE_ENV !== 'production') { 27 | if (identity) { 28 | forbidNewOperator(this, Intersection); 29 | } 30 | path = path || [displayName]; 31 | assert(Intersection.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); }); 32 | } 33 | 34 | return value; 35 | } 36 | 37 | Intersection.meta = { 38 | kind: 'intersection', 39 | types: types, 40 | name: name, 41 | identity: identity 42 | }; 43 | 44 | Intersection.displayName = displayName; 45 | 46 | Intersection.is = function (x) { 47 | return types.every(function (type) { 48 | return is(x, type); 49 | }); 50 | }; 51 | 52 | Intersection.update = function (instance, patch) { 53 | return Intersection(assert.update(instance, patch)); 54 | }; 55 | 56 | return Intersection; 57 | } 58 | 59 | intersection.getDefaultName = getDefaultName; 60 | module.exports = intersection; 61 | 62 | -------------------------------------------------------------------------------- /lib/irreducible.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isString = require('./isString'); 3 | var isFunction = require('./isFunction'); 4 | var forbidNewOperator = require('./forbidNewOperator'); 5 | 6 | module.exports = function irreducible(name, predicate) { 7 | 8 | if (process.env.NODE_ENV !== 'production') { 9 | assert(isString(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to irreducible(name, predicate) (expected a string)'; }); 10 | assert(isFunction(predicate), 'Invalid argument predicate ' + assert.stringify(predicate) + ' supplied to irreducible(name, predicate) (expected a function)'); 11 | } 12 | 13 | function Irreducible(value, path) { 14 | 15 | if (process.env.NODE_ENV !== 'production') { 16 | forbidNewOperator(this, Irreducible); 17 | path = path || [name]; 18 | assert(predicate(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); }); 19 | } 20 | 21 | return value; 22 | } 23 | 24 | Irreducible.meta = { 25 | kind: 'irreducible', 26 | name: name, 27 | predicate: predicate, 28 | identity: true 29 | }; 30 | 31 | Irreducible.displayName = name; 32 | 33 | Irreducible.is = predicate; 34 | 35 | return Irreducible; 36 | }; 37 | -------------------------------------------------------------------------------- /lib/is.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | 3 | // returns true if x is an instance of type 4 | module.exports = function is(x, type) { 5 | if (isType(type)) { 6 | return type.is(x); 7 | } 8 | return x instanceof type; // type should be a class constructor 9 | }; 10 | -------------------------------------------------------------------------------- /lib/isArray.js: -------------------------------------------------------------------------------- 1 | module.exports = function isArray(x) { 2 | return Array.isArray ? Array.isArray(x) : x instanceof Array; 3 | }; -------------------------------------------------------------------------------- /lib/isBoolean.js: -------------------------------------------------------------------------------- 1 | module.exports = function isBoolean(x) { 2 | return x === true || x === false; 3 | }; -------------------------------------------------------------------------------- /lib/isFunction.js: -------------------------------------------------------------------------------- 1 | module.exports = function isFunction(x) { 2 | return typeof x === 'function'; 3 | }; -------------------------------------------------------------------------------- /lib/isIdentity.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var Boolean = require('./Boolean'); 3 | var isType = require('./isType'); 4 | var getTypeName = require('./getTypeName'); 5 | 6 | // return true if the type constructor behaves like the identity function 7 | module.exports = function isIdentity(type) { 8 | if (isType(type)) { 9 | if (process.env.NODE_ENV !== 'production') { 10 | assert(Boolean.is(type.meta.identity), function () { return 'Invalid meta identity ' + assert.stringify(type.meta.identity) + ' supplied to type ' + getTypeName(type); }); 11 | } 12 | return type.meta.identity; 13 | } 14 | // for tcomb the other constructors, like ES6 classes, are identity-like 15 | return true; 16 | }; -------------------------------------------------------------------------------- /lib/isInterface.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | 3 | module.exports = function isInterface(x) { 4 | return isType(x) && ( x.meta.kind === 'interface' ); 5 | }; -------------------------------------------------------------------------------- /lib/isMaybe.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | 3 | module.exports = function isMaybe(x) { 4 | return isType(x) && ( x.meta.kind === 'maybe' ); 5 | }; -------------------------------------------------------------------------------- /lib/isNil.js: -------------------------------------------------------------------------------- 1 | module.exports = function isNil(x) { 2 | return x === null || x === void 0; 3 | }; -------------------------------------------------------------------------------- /lib/isNumber.js: -------------------------------------------------------------------------------- 1 | module.exports = function isNumber(x) { 2 | return typeof x === 'number' && isFinite(x) && !isNaN(x); 3 | }; -------------------------------------------------------------------------------- /lib/isObject.js: -------------------------------------------------------------------------------- 1 | var isNil = require('./isNil'); 2 | var isArray = require('./isArray'); 3 | 4 | module.exports = function isObject(x) { 5 | return !isNil(x) && typeof x === 'object' && !isArray(x); 6 | }; -------------------------------------------------------------------------------- /lib/isString.js: -------------------------------------------------------------------------------- 1 | module.exports = function isString(x) { 2 | return typeof x === 'string'; 3 | }; -------------------------------------------------------------------------------- /lib/isStruct.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | 3 | module.exports = function isStruct(x) { 4 | return isType(x) && ( x.meta.kind === 'struct' ); 5 | }; -------------------------------------------------------------------------------- /lib/isSubsetOf.d.ts: -------------------------------------------------------------------------------- 1 | import * as t from '../.' 2 | 3 | declare function isSubsetOf(subset: t.Type, superset: t.Type): boolean; 4 | 5 | export default isSubsetOf -------------------------------------------------------------------------------- /lib/isSubsetOf.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var decompose = require('./decompose'); 3 | var isType = require('./isType'); 4 | var Any = require('./Any'); 5 | var Array = require('./Array'); 6 | var Function = require('./Function'); 7 | var Nil = require('./Nil'); 8 | var tObject = require('./Object'); 9 | 10 | function leqList(As, Bs) { 11 | return ( As.length === Bs.length ) && As.every(function (A, i) { 12 | return recurse(A, Bs[i]); 13 | }); 14 | } 15 | 16 | function leqPredicates(ps1, ps2) { 17 | return ps2.length <= ps1.length && ps2.every(function (p) { 18 | return ps1.indexOf(p) !== -1; 19 | }); 20 | } 21 | 22 | var index = []; 23 | 24 | function find(A, B) { 25 | for (var i = 0, len = index.length ; i < len ; i++) { 26 | var item = index[i]; 27 | if (item.A === A && item.B === B) { 28 | return item; 29 | } 30 | } 31 | } 32 | 33 | function leq(A, B) { 34 | 35 | // Fast results 36 | // (1) if B === t.Any then A <= B for all A 37 | // (2) if B === A then A <= B for all A 38 | if (A === B || B === Any) { 39 | return true; 40 | } 41 | 42 | var kindA = A.meta.kind; 43 | var kindB = B.meta.kind; 44 | 45 | // Reductions 46 | 47 | // (3) if B = maybe(C) and A is not a maybe then A <= B if and only if A === t.Nil or A <= C 48 | if (kindB === 'maybe' && kindA !== 'maybe') { 49 | return ( A === Nil ) || recurse(A, B.meta.type); 50 | } 51 | 52 | function gte(type) { 53 | return recurse(A, type); 54 | } 55 | 56 | // (4) if B is a union then A <= B if exists B' in B.meta.types such that A <= B' 57 | if (kindB === 'union') { 58 | if (B.meta.types.some(gte)) { 59 | return true; 60 | } 61 | } 62 | 63 | // (5) if B is an intersection then A <= B if A <= B' for all B' in B.meta.types 64 | if (kindB === 'intersection') { 65 | if (B.meta.types.every(gte)) { 66 | return true; 67 | } 68 | } 69 | 70 | // Let A be a struct then A <= B if B === t.Object 71 | if (kindA === 'struct') { 72 | return B === tObject; 73 | } 74 | 75 | // Let A be a maybe then A <= B if B is a maybe and A.meta.type <= B.meta.type 76 | else if (kindA === 'maybe') { 77 | return ( kindB === 'maybe' ) && recurse(A.meta.type, B.meta.type); 78 | } 79 | 80 | // Let A be an union then A <= B if A' <= B for all A' in A.meta.types 81 | else if (kindA === 'union') { 82 | return A.meta.types.every(function (T) { 83 | return recurse(T, B); 84 | }); 85 | } 86 | 87 | // Let A be an intersection then A <= B if exists A' in A.meta.types such that A' <= B 88 | else if (kindA === 'intersection') { 89 | return A.meta.types.some(function (T) { 90 | return recurse(T, B); 91 | }); 92 | } 93 | 94 | // Let A be irreducible then A <= B if B is irreducible and A.is === B.is 95 | else if (kindA === 'irreducible') { 96 | return ( kindB === 'irreducible' ) && ( A.meta.predicate === B.meta.predicate ); 97 | } 98 | 99 | // Let A be an enum then A <= B if and only if B.is(a) === true for all a in keys(A.meta.map) 100 | else if (kindA === 'enums') { 101 | return Object.keys(A.meta.map).every(B.is); 102 | } 103 | 104 | // Let A be a refinement then A <= B if decompose(A) <= decompose(B) 105 | else if (kindA === 'subtype') { 106 | var dA = decompose(A); 107 | var dB = decompose(B); 108 | return leqPredicates(dA.predicates, dB.predicates) && recurse(dA.unrefinedType, dB.unrefinedType); 109 | } 110 | 111 | // Let A be a list then A <= B if one of the following holds: 112 | else if (kindA === 'list') { 113 | // B === t.Array 114 | if (B === Array) { 115 | return true; 116 | } 117 | // B is a list and A.meta.type <= B.meta.type 118 | return ( kindB === 'list' ) && recurse(A.meta.type, B.meta.type); 119 | } 120 | 121 | // Let A be a list then A <= B if one of the following holds: 122 | else if (kindA === 'dict') { 123 | // B === t.Object 124 | if (B === tObject) { 125 | return true; 126 | } 127 | // B is a dictionary and [A.meta.domain, A.meta.codomain] <= [B.meta.domain, B.meta.codomain] 128 | return ( kindB === 'dict' ) && recurse(A.meta.domain, B.meta.domain) && recurse(A.meta.codomain, B.meta.codomain); 129 | } 130 | 131 | // Let A be a tuple then A <= B if one of the following holds: 132 | else if (kindA === 'tuple') { 133 | // B === t.Array 134 | if (B === Array) { 135 | return true; 136 | } 137 | // B is a tuple and A.meta.types <= B.meta.types 138 | return ( kindB === 'tuple' ) && leqList(A.meta.types, B.meta.types); 139 | } 140 | 141 | // Let A be a function then then A <= B if one of the following holds: 142 | else if (kindA === 'func') { 143 | // B === t.Function 144 | if (B === Function) { 145 | return true; 146 | } 147 | // B is a function and [A.meta.domain, A.meta.codomain] <= [B.meta.domain, B.meta.codomain] 148 | return ( kindB === 'func' ) && recurse(A.meta.codomain, B.meta.codomain) && leqList(A.meta.domain, B.meta.domain); 149 | } 150 | 151 | // Let A be an interface then A <= B if one of the following holds: 152 | else if (kindA === 'interface') { 153 | // B === t.Object 154 | if (B === tObject) { 155 | return true; 156 | } 157 | if (kindB === 'interface') { 158 | var keysB = Object.keys(B.meta.props); 159 | var compatible = keysB.every(function (k) { 160 | return A.meta.props.hasOwnProperty(k) && recurse(A.meta.props[k], B.meta.props[k]); 161 | }); 162 | // B is an interface, B.meta.strict === false, keys(B.meta.props) <= keys(A.meta.props) and A.meta.props[k] <= B.meta.props[k] for all k in keys(B.meta.props) 163 | if (B.meta.strict === false) { 164 | return compatible; 165 | } 166 | // B is an interface, B.meta.strict === true, A.meta.strict === true, keys(B.meta.props) = keys(A.meta.props) and A.meta.props[k] <= B.meta.props[k] for all k in keys(B.meta.props) 167 | return compatible && ( A.meta.strict === true ) && ( keysB.length === Object.keys(A.meta.props).length ); 168 | } 169 | } 170 | 171 | return false; 172 | } 173 | 174 | function recurse(A, B) { 175 | // handle recursive types 176 | var hit = find(A, B); 177 | if (hit) { 178 | return hit.leq; 179 | } 180 | hit = { A: A, B: B, leq: true }; 181 | index.push(hit); 182 | return ( hit.leq = leq(A, B) ); 183 | } 184 | 185 | function isSubsetOf(A, B) { 186 | if (process.env.NODE_ENV !== 'production') { 187 | assert(isType(A), function () { return 'Invalid argument subset ' + assert.stringify(A) + ' supplied to isSubsetOf(subset, superset) (expected a type)'; }); 188 | assert(isType(B), function () { return 'Invalid argument superset ' + assert.stringify(B) + ' supplied to isSubsetOf(subset, superset) (expected a type)'; }); 189 | } 190 | 191 | return recurse(A, B); 192 | } 193 | 194 | module.exports = isSubsetOf; -------------------------------------------------------------------------------- /lib/isType.js: -------------------------------------------------------------------------------- 1 | var isFunction = require('./isFunction'); 2 | var isObject = require('./isObject'); 3 | 4 | module.exports = function isType(x) { 5 | return isFunction(x) && isObject(x.meta); 6 | }; -------------------------------------------------------------------------------- /lib/isTypeName.js: -------------------------------------------------------------------------------- 1 | var isNil = require('./isNil'); 2 | var isString = require('./isString'); 3 | 4 | module.exports = function isTypeName(name) { 5 | return isNil(name) || isString(name); 6 | }; -------------------------------------------------------------------------------- /lib/isUnion.js: -------------------------------------------------------------------------------- 1 | var isType = require('./isType'); 2 | 3 | module.exports = function isUnion(x) { 4 | return isType(x) && ( x.meta.kind === 'union' ); 5 | }; -------------------------------------------------------------------------------- /lib/list.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var getTypeName = require('./getTypeName'); 5 | var isIdentity = require('./isIdentity'); 6 | var create = require('./create'); 7 | var is = require('./is'); 8 | var isArray = require('./isArray'); 9 | 10 | function getDefaultName(type) { 11 | return 'Array<' + getTypeName(type) + '>'; 12 | } 13 | 14 | function list(type, name) { 15 | 16 | if (process.env.NODE_ENV !== 'production') { 17 | assert(isFunction(type), function () { return 'Invalid argument type ' + assert.stringify(type) + ' supplied to list(type, [name]) combinator (expected a type)'; }); 18 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to list(type, [name]) combinator (expected a string)'; }); 19 | } 20 | 21 | var displayName = name || getDefaultName(type); 22 | var typeNameCache = getTypeName(type); 23 | var identity = isIdentity(type); // the list is identity iif type is identity 24 | 25 | function List(value, path) { 26 | 27 | if (process.env.NODE_ENV === 'production') { 28 | if (identity) { 29 | return value; // just trust the input if elements must not be hydrated 30 | } 31 | } 32 | 33 | if (process.env.NODE_ENV !== 'production') { 34 | path = path || [displayName]; 35 | assert(isArray(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an array of ' + typeNameCache + ')'; }); 36 | } 37 | 38 | var idempotent = true; // will remain true if I can reutilise the input 39 | var ret = []; // make a temporary copy, will be discarded if idempotent remains true 40 | for (var i = 0, len = value.length; i < len; i++ ) { 41 | var actual = value[i]; 42 | var instance = create(type, actual, ( process.env.NODE_ENV !== 'production' ? path.concat(i + ': ' + typeNameCache) : null )); 43 | idempotent = idempotent && ( actual === instance ); 44 | ret.push(instance); 45 | } 46 | 47 | if (idempotent) { // implements idempotency 48 | ret = value; 49 | } 50 | 51 | if (process.env.NODE_ENV !== 'production') { 52 | Object.freeze(ret); 53 | } 54 | 55 | return ret; 56 | } 57 | 58 | List.meta = { 59 | kind: 'list', 60 | type: type, 61 | name: name, 62 | identity: identity 63 | }; 64 | 65 | List.displayName = displayName; 66 | 67 | List.is = function (x) { 68 | return isArray(x) && x.every(function (e) { 69 | return is(e, type); 70 | }); 71 | }; 72 | 73 | List.update = function (instance, patch) { 74 | return List(assert.update(instance, patch)); 75 | }; 76 | 77 | return List; 78 | } 79 | 80 | list.getDefaultName = getDefaultName; 81 | module.exports = list; 82 | -------------------------------------------------------------------------------- /lib/match.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isFunction = require('./isFunction'); 3 | var isType = require('./isType'); 4 | var Any = require('./Any'); 5 | 6 | module.exports = function match(x) { 7 | var type, guard, f, count; 8 | for (var i = 1, len = arguments.length; i < len; ) { 9 | type = arguments[i]; 10 | guard = arguments[i + 1]; 11 | f = arguments[i + 2]; 12 | 13 | if (isFunction(f) && !isType(f)) { 14 | i = i + 3; 15 | } 16 | else { 17 | f = guard; 18 | guard = Any.is; 19 | i = i + 2; 20 | } 21 | 22 | if (process.env.NODE_ENV !== 'production') { 23 | count = (count || 0) + 1; 24 | assert(isType(type), function () { return 'Invalid type in clause #' + count; }); 25 | assert(isFunction(guard), function () { return 'Invalid guard in clause #' + count; }); 26 | assert(isFunction(f), function () { return 'Invalid block in clause #' + count; }); 27 | } 28 | 29 | if (type.is(x) && guard(x)) { 30 | return f(x); 31 | } 32 | } 33 | assert.fail('Match error'); 34 | }; 35 | -------------------------------------------------------------------------------- /lib/maybe.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var isMaybe = require('./isMaybe'); 5 | var isIdentity = require('./isIdentity'); 6 | var Any = require('./Any'); 7 | var create = require('./create'); 8 | var Nil = require('./Nil'); 9 | var forbidNewOperator = require('./forbidNewOperator'); 10 | var is = require('./is'); 11 | var getTypeName = require('./getTypeName'); 12 | 13 | function getDefaultName(type) { 14 | return '?' + getTypeName(type); 15 | } 16 | 17 | function maybe(type, name) { 18 | 19 | if (isMaybe(type) || type === Any || type === Nil) { // makes the combinator idempotent and handle Any, Nil 20 | return type; 21 | } 22 | 23 | if (process.env.NODE_ENV !== 'production') { 24 | assert(isFunction(type), function () { return 'Invalid argument type ' + assert.stringify(type) + ' supplied to maybe(type, [name]) combinator (expected a type)'; }); 25 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to maybe(type, [name]) combinator (expected a string)'; }); 26 | } 27 | 28 | var displayName = name || getDefaultName(type); 29 | var identity = isIdentity(type); 30 | 31 | function Maybe(value, path) { 32 | if (process.env.NODE_ENV !== 'production') { 33 | if (identity) { 34 | forbidNewOperator(this, Maybe); 35 | } 36 | } 37 | return Nil.is(value) ? value : create(type, value, path); 38 | } 39 | 40 | Maybe.meta = { 41 | kind: 'maybe', 42 | type: type, 43 | name: name, 44 | identity: identity 45 | }; 46 | 47 | Maybe.displayName = displayName; 48 | 49 | Maybe.is = function (x) { 50 | return Nil.is(x) || is(x, type); 51 | }; 52 | 53 | return Maybe; 54 | } 55 | 56 | maybe.getDefaultName = getDefaultName; 57 | module.exports = maybe; 58 | -------------------------------------------------------------------------------- /lib/mixin.js: -------------------------------------------------------------------------------- 1 | var isNil = require('./isNil'); 2 | var assert = require('./assert'); 3 | 4 | // safe mixin, cannot override props unless specified 5 | module.exports = function mixin(target, source, overwrite) { 6 | if (isNil(source)) { return target; } 7 | for (var k in source) { 8 | if (source.hasOwnProperty(k)) { 9 | if (overwrite !== true) { 10 | if (process.env.NODE_ENV !== 'production') { 11 | assert(!target.hasOwnProperty(k) || target[k] === source[k], function () { return 'Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "' + k + '" of target object'; }); 12 | } 13 | } 14 | target[k] = source[k]; 15 | } 16 | } 17 | return target; 18 | }; -------------------------------------------------------------------------------- /lib/refinement.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var forbidNewOperator = require('./forbidNewOperator'); 5 | var isIdentity = require('./isIdentity'); 6 | var create = require('./create'); 7 | var is = require('./is'); 8 | var getTypeName = require('./getTypeName'); 9 | var getFunctionName = require('./getFunctionName'); 10 | 11 | function getDefaultName(type, predicate) { 12 | return '{' + getTypeName(type) + ' | ' + getFunctionName(predicate) + '}'; 13 | } 14 | 15 | function refinement(type, predicate, name) { 16 | 17 | if (process.env.NODE_ENV !== 'production') { 18 | assert(isFunction(type), function () { return 'Invalid argument type ' + assert.stringify(type) + ' supplied to refinement(type, predicate, [name]) combinator (expected a type)'; }); 19 | assert(isFunction(predicate), function () { return 'Invalid argument predicate supplied to refinement(type, predicate, [name]) combinator (expected a function)'; }); 20 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to refinement(type, predicate, [name]) combinator (expected a string)'; }); 21 | } 22 | 23 | var displayName = name || getDefaultName(type, predicate); 24 | var identity = isIdentity(type); 25 | 26 | function Refinement(value, path) { 27 | 28 | if (process.env.NODE_ENV !== 'production') { 29 | if (identity) { 30 | forbidNewOperator(this, Refinement); 31 | } 32 | path = path || [displayName]; 33 | } 34 | 35 | var x = create(type, value, path); 36 | 37 | if (process.env.NODE_ENV !== 'production') { 38 | assert(predicate(x), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); }); 39 | } 40 | 41 | return x; 42 | } 43 | 44 | Refinement.meta = { 45 | kind: 'subtype', 46 | type: type, 47 | predicate: predicate, 48 | name: name, 49 | identity: identity 50 | }; 51 | 52 | Refinement.displayName = displayName; 53 | 54 | Refinement.is = function (x) { 55 | return is(x, type) && predicate(x); 56 | }; 57 | 58 | Refinement.update = function (instance, patch) { 59 | return Refinement(assert.update(instance, patch)); 60 | }; 61 | 62 | return Refinement; 63 | } 64 | 65 | refinement.getDefaultName = getDefaultName; 66 | module.exports = refinement; 67 | -------------------------------------------------------------------------------- /lib/stringify.js: -------------------------------------------------------------------------------- 1 | var getFunctionName = require('./getFunctionName'); 2 | 3 | function replacer(key, value) { 4 | if (typeof value === 'function') { 5 | return getFunctionName(value); 6 | } 7 | return value; 8 | } 9 | 10 | module.exports = function stringify(x) { 11 | try { // handle "Converting circular structure to JSON" error 12 | return JSON.stringify(x, replacer, 2); 13 | } 14 | catch (e) { 15 | return String(x); 16 | } 17 | }; -------------------------------------------------------------------------------- /lib/struct.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var String = require('./String'); 4 | var Function = require('./Function'); 5 | var isBoolean = require('./isBoolean'); 6 | var isObject = require('./isObject'); 7 | var isNil = require('./isNil'); 8 | var create = require('./create'); 9 | var getTypeName = require('./getTypeName'); 10 | var dict = require('./dict'); 11 | var getDefaultInterfaceName = require('./getDefaultInterfaceName'); 12 | var extend = require('./extend'); 13 | 14 | function getDefaultName(props) { 15 | return 'Struct' + getDefaultInterfaceName(props); 16 | } 17 | 18 | function extendStruct(mixins, name) { 19 | return extend(struct, mixins, name); 20 | } 21 | 22 | function getOptions(options) { 23 | if (!isObject(options)) { 24 | options = isNil(options) ? {} : { name: options }; 25 | } 26 | if (!options.hasOwnProperty('strict')) { 27 | options.strict = struct.strict; 28 | } 29 | if (!options.hasOwnProperty('defaultProps')) { 30 | options.defaultProps = {}; 31 | } 32 | return options; 33 | } 34 | 35 | function struct(props, options) { 36 | 37 | options = getOptions(options); 38 | var name = options.name; 39 | var strict = options.strict; 40 | var defaultProps = options.defaultProps; 41 | 42 | if (process.env.NODE_ENV !== 'production') { 43 | assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)'; }); 44 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to struct(props, [options]) combinator (expected a string)'; }); 45 | assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; }); 46 | assert(isObject(defaultProps), function () { return 'Invalid argument defaultProps ' + assert.stringify(defaultProps) + ' supplied to struct(props, [options]) combinator (expected an object)'; }); 47 | } 48 | 49 | var displayName = name || getDefaultName(props); 50 | 51 | function Struct(value, path) { 52 | 53 | if (Struct.is(value)) { // implements idempotency 54 | return value; 55 | } 56 | 57 | if (process.env.NODE_ENV !== 'production') { 58 | path = path || [displayName]; 59 | assert(isObject(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an object)'; }); 60 | // strictness 61 | if (strict) { 62 | for (k in value) { 63 | if (value.hasOwnProperty(k)) { 64 | assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); }); 65 | } 66 | } 67 | } 68 | } 69 | 70 | if (!(this instanceof Struct)) { // `new` is optional 71 | return new Struct(value, path); 72 | } 73 | 74 | for (var k in props) { 75 | if (props.hasOwnProperty(k)) { 76 | var expected = props[k]; 77 | var actual = value[k]; 78 | // apply defaults 79 | if (actual === undefined) { 80 | actual = defaultProps[k]; 81 | } 82 | this[k] = create(expected, actual, ( process.env.NODE_ENV !== 'production' ? path.concat(k + ': ' + getTypeName(expected)) : null )); 83 | } 84 | } 85 | 86 | if (process.env.NODE_ENV !== 'production') { 87 | Object.freeze(this); 88 | } 89 | 90 | } 91 | 92 | Struct.meta = { 93 | kind: 'struct', 94 | props: props, 95 | name: name, 96 | identity: false, 97 | strict: strict, 98 | defaultProps: defaultProps 99 | }; 100 | 101 | Struct.displayName = displayName; 102 | 103 | Struct.is = function (x) { 104 | return x instanceof Struct; 105 | }; 106 | 107 | Struct.update = function (instance, patch) { 108 | return new Struct(assert.update(instance, patch)); 109 | }; 110 | 111 | Struct.extend = function (xs, name) { 112 | return extendStruct([Struct].concat(xs), name); 113 | }; 114 | 115 | return Struct; 116 | } 117 | 118 | struct.strict = false; 119 | struct.getOptions = getOptions; 120 | struct.getDefaultName = getDefaultName; 121 | struct.extend = extendStruct; 122 | module.exports = struct; 123 | -------------------------------------------------------------------------------- /lib/tuple.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var getTypeName = require('./getTypeName'); 5 | var isIdentity = require('./isIdentity'); 6 | var isArray = require('./isArray'); 7 | var create = require('./create'); 8 | var is = require('./is'); 9 | 10 | function getDefaultName(types) { 11 | return '[' + types.map(getTypeName).join(', ') + ']'; 12 | } 13 | 14 | function tuple(types, name) { 15 | 16 | if (process.env.NODE_ENV !== 'production') { 17 | assert(isArray(types) && types.every(isFunction), function () { return 'Invalid argument types ' + assert.stringify(types) + ' supplied to tuple(types, [name]) combinator (expected an array of types)'; }); 18 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to tuple(types, [name]) combinator (expected a string)'; }); 19 | } 20 | 21 | var displayName = name || getDefaultName(types); 22 | var identity = types.every(isIdentity); 23 | 24 | function Tuple(value, path) { 25 | 26 | if (process.env.NODE_ENV === 'production') { 27 | if (identity) { 28 | return value; 29 | } 30 | } 31 | 32 | if (process.env.NODE_ENV !== 'production') { 33 | path = path || [displayName]; 34 | assert(isArray(value) && value.length === types.length, function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an array of length ' + types.length + ')'; }); 35 | } 36 | 37 | var idempotent = true; 38 | var ret = []; 39 | for (var i = 0, len = types.length; i < len; i++) { 40 | var expected = types[i]; 41 | var actual = value[i]; 42 | var instance = create(expected, actual, ( process.env.NODE_ENV !== 'production' ? path.concat(i + ': ' + getTypeName(expected)) : null )); 43 | idempotent = idempotent && ( actual === instance ); 44 | ret.push(instance); 45 | } 46 | 47 | if (idempotent) { // implements idempotency 48 | ret = value; 49 | } 50 | 51 | if (process.env.NODE_ENV !== 'production') { 52 | Object.freeze(ret); 53 | } 54 | 55 | return ret; 56 | } 57 | 58 | Tuple.meta = { 59 | kind: 'tuple', 60 | types: types, 61 | name: name, 62 | identity: identity 63 | }; 64 | 65 | Tuple.displayName = displayName; 66 | 67 | Tuple.is = function (x) { 68 | return isArray(x) && 69 | x.length === types.length && 70 | types.every(function (type, i) { 71 | return is(x[i], type); 72 | }); 73 | }; 74 | 75 | Tuple.update = function (instance, patch) { 76 | return Tuple(assert.update(instance, patch)); 77 | }; 78 | 79 | return Tuple; 80 | } 81 | 82 | tuple.getDefaultName = getDefaultName; 83 | module.exports = tuple; -------------------------------------------------------------------------------- /lib/union.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isTypeName = require('./isTypeName'); 3 | var isFunction = require('./isFunction'); 4 | var getTypeName = require('./getTypeName'); 5 | var isIdentity = require('./isIdentity'); 6 | var isArray = require('./isArray'); 7 | var create = require('./create'); 8 | var is = require('./is'); 9 | var forbidNewOperator = require('./forbidNewOperator'); 10 | var isUnion = require('./isUnion'); 11 | var isNil = require('./isNil'); 12 | 13 | function getDefaultName(types) { 14 | return types.map(getTypeName).join(' | '); 15 | } 16 | 17 | function union(types, name) { 18 | 19 | if (process.env.NODE_ENV !== 'production') { 20 | assert(isArray(types) && types.every(isFunction) && types.length >= 2, function () { return 'Invalid argument types ' + assert.stringify(types) + ' supplied to union(types, [name]) combinator (expected an array of at least 2 types)'; }); 21 | assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to union(types, [name]) combinator (expected a string)'; }); 22 | } 23 | 24 | var displayName = name || getDefaultName(types); 25 | var identity = types.every(isIdentity); 26 | 27 | function Union(value, path) { 28 | 29 | if (process.env.NODE_ENV === 'production') { 30 | if (identity) { 31 | return value; 32 | } 33 | } 34 | 35 | var type = Union.dispatch(value); 36 | if (!type && Union.is(value)) { 37 | return value; 38 | } 39 | 40 | if (process.env.NODE_ENV !== 'production') { 41 | if (identity) { 42 | forbidNewOperator(this, Union); 43 | } 44 | path = path || [displayName]; 45 | assert(isFunction(type), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (no constructor returned by dispatch)'; }); 46 | path[path.length - 1] += '(' + getTypeName(type) + ')'; 47 | } 48 | 49 | return create(type, value, path); 50 | } 51 | 52 | Union.meta = { 53 | kind: 'union', 54 | types: types, 55 | name: name, 56 | identity: identity 57 | }; 58 | 59 | Union.displayName = displayName; 60 | 61 | Union.is = function (x) { 62 | return types.some(function (type) { 63 | return is(x, type); 64 | }); 65 | }; 66 | 67 | Union.dispatch = function (x) { // default dispatch implementation 68 | for (var i = 0, len = types.length; i < len; i++ ) { 69 | var type = types[i]; 70 | if (isUnion(type)) { // handle union of unions 71 | var t = type.dispatch(x); 72 | if (!isNil(t)) { 73 | return t; 74 | } 75 | } 76 | else if (is(x, type)) { 77 | return type; 78 | } 79 | } 80 | }; 81 | 82 | Union.update = function (instance, patch) { 83 | return Union(assert.update(instance, patch)); 84 | }; 85 | 86 | return Union; 87 | } 88 | 89 | union.getDefaultName = getDefaultName; 90 | module.exports = union; 91 | 92 | -------------------------------------------------------------------------------- /lib/update.js: -------------------------------------------------------------------------------- 1 | var assert = require('./assert'); 2 | var isObject = require('./isObject'); 3 | var isFunction = require('./isFunction'); 4 | var isArray = require('./isArray'); 5 | var isNumber = require('./isNumber'); 6 | var assign = require('./assign'); 7 | 8 | function getShallowCopy(x) { 9 | if (isObject(x)) { 10 | if (x instanceof Date || x instanceof RegExp) { 11 | return x; 12 | } 13 | return assign({}, x); 14 | } 15 | if (isArray(x)) { 16 | return x.concat(); 17 | } 18 | return x; 19 | } 20 | 21 | function isCommand(k) { 22 | return update.commands.hasOwnProperty(k); 23 | } 24 | 25 | function getCommand(k) { 26 | return update.commands[k]; 27 | } 28 | 29 | function update(instance, patch) { 30 | 31 | if (process.env.NODE_ENV !== 'production') { 32 | assert(isObject(patch), function () { return 'Invalid argument patch ' + assert.stringify(patch) + ' supplied to function update(instance, patch): expected an object containing commands'; }); 33 | } 34 | 35 | var value = instance; 36 | var isChanged = false; 37 | var newValue; 38 | for (var k in patch) { 39 | if (patch.hasOwnProperty(k)) { 40 | if (isCommand(k)) { 41 | newValue = getCommand(k)(patch[k], value); 42 | if (newValue !== instance) { 43 | isChanged = true; 44 | value = newValue; 45 | } else { 46 | value = instance; 47 | } 48 | } 49 | else { 50 | if (value === instance) { 51 | value = getShallowCopy(instance); 52 | } 53 | newValue = update(value[k], patch[k]); 54 | isChanged = isChanged || ( newValue !== value[k] ); 55 | value[k] = newValue; 56 | } 57 | } 58 | } 59 | return isChanged ? value : instance; 60 | } 61 | 62 | // built-in commands 63 | 64 | function $apply(f, value) { 65 | if (process.env.NODE_ENV !== 'production') { 66 | assert(isFunction(f), 'Invalid argument f supplied to immutability helper { $apply: f } (expected a function)'); 67 | } 68 | return f(value); 69 | } 70 | 71 | function $push(elements, arr) { 72 | if (process.env.NODE_ENV !== 'production') { 73 | assert(isArray(elements), 'Invalid argument elements supplied to immutability helper { $push: elements } (expected an array)'); 74 | assert(isArray(arr), 'Invalid value supplied to immutability helper $push (expected an array)'); 75 | } 76 | if (elements.length > 0) { 77 | return arr.concat(elements); 78 | } 79 | return arr; 80 | } 81 | 82 | function $remove(keys, obj) { 83 | if (process.env.NODE_ENV !== 'production') { 84 | assert(isArray(keys), 'Invalid argument keys supplied to immutability helper { $remove: keys } (expected an array)'); 85 | assert(isObject(obj), 'Invalid value supplied to immutability helper $remove (expected an object)'); 86 | } 87 | if (keys.length > 0) { 88 | obj = getShallowCopy(obj); 89 | for (var i = 0, len = keys.length; i < len; i++ ) { 90 | delete obj[keys[i]]; 91 | } 92 | } 93 | return obj; 94 | } 95 | 96 | function $set(value) { 97 | return value; 98 | } 99 | 100 | function $splice(splices, arr) { 101 | if (process.env.NODE_ENV !== 'production') { 102 | assert(isArray(splices) && splices.every(isArray), 'Invalid argument splices supplied to immutability helper { $splice: splices } (expected an array of arrays)'); 103 | assert(isArray(arr), 'Invalid value supplied to immutability helper $splice (expected an array)'); 104 | } 105 | if (splices.length > 0) { 106 | arr = getShallowCopy(arr); 107 | return splices.reduce(function (acc, splice) { 108 | acc.splice.apply(acc, splice); 109 | return acc; 110 | }, arr); 111 | } 112 | return arr; 113 | } 114 | 115 | function $swap(config, arr) { 116 | if (process.env.NODE_ENV !== 'production') { 117 | assert(isObject(config), 'Invalid argument config supplied to immutability helper { $swap: config } (expected an object)'); 118 | assert(isNumber(config.from), 'Invalid argument config.from supplied to immutability helper { $swap: config } (expected a number)'); 119 | assert(isNumber(config.to), 'Invalid argument config.to supplied to immutability helper { $swap: config } (expected a number)'); 120 | assert(isArray(arr), 'Invalid value supplied to immutability helper $swap (expected an array)'); 121 | } 122 | if (config.from !== config.to) { 123 | arr = getShallowCopy(arr); 124 | var element = arr[config.to]; 125 | arr[config.to] = arr[config.from]; 126 | arr[config.from] = element; 127 | } 128 | return arr; 129 | } 130 | 131 | function $unshift(elements, arr) { 132 | if (process.env.NODE_ENV !== 'production') { 133 | assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {$unshift: elements} (expected an array)'); 134 | assert(isArray(arr), 'Invalid value supplied to immutability helper $unshift (expected an array)'); 135 | } 136 | if (elements.length > 0) { 137 | return elements.concat(arr); 138 | } 139 | return arr; 140 | } 141 | 142 | function $merge(whatToMerge, value) { 143 | var isChanged = false; 144 | var result = getShallowCopy(value); 145 | for (var k in whatToMerge) { 146 | if (whatToMerge.hasOwnProperty(k)) { 147 | result[k] = whatToMerge[k]; 148 | isChanged = isChanged || ( result[k] !== value[k] ); 149 | } 150 | } 151 | return isChanged ? result : value; 152 | } 153 | 154 | update.commands = { 155 | $apply: $apply, 156 | $push: $push, 157 | $remove: $remove, 158 | $set: $set, 159 | $splice: $splice, 160 | $swap: $swap, 161 | $unshift: $unshift, 162 | $merge: $merge 163 | }; 164 | 165 | module.exports = update; 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcomb", 3 | "version": "3.2.29", 4 | "description": "Type checking and DDD for JavaScript", 5 | "main": "index.js", 6 | "typings": "index.d.ts", 7 | "files": [ 8 | "index.js", 9 | "lib", 10 | "index.d.ts" 11 | ], 12 | "scripts": { 13 | "lint": "eslint index.js lib test", 14 | "test": "npm run lint && mocha && npm run typescript", 15 | "typescript": "tsc --noEmit tcomb-tests.ts", 16 | "dist": "webpack", 17 | "perf": "node ./perf/perf" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/gcanti/tcomb.git" 22 | }, 23 | "author": "Giulio Canti ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/gcanti/tcomb/issues" 27 | }, 28 | "homepage": "https://github.com/gcanti/tcomb", 29 | "devDependencies": { 30 | "benchmark": "2.1.0", 31 | "eslint": "1.10.3", 32 | "mocha": "2.3.4", 33 | "typescript": "3.1.6", 34 | "webpack": "1.12.14" 35 | }, 36 | "tags": [ 37 | "type", 38 | "combinators", 39 | "checking", 40 | "safety", 41 | "model", 42 | "domain", 43 | "debugging", 44 | "immutable", 45 | "DDD", 46 | "JSON", 47 | "store" 48 | ], 49 | "keywords": [ 50 | "type", 51 | "combinators", 52 | "checking", 53 | "safety", 54 | "model", 55 | "domain", 56 | "debugging", 57 | "immutable", 58 | "DDD", 59 | "JSON", 60 | "store" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /perf/perf.js: -------------------------------------------------------------------------------- 1 | // 2 | // ! remember to run `npm dist` before ! 3 | // 4 | 5 | var Benchmark = require('benchmark'); 6 | var t = require('../dist/tcomb.min'); 7 | 8 | function getHz(bench) { 9 | var result = 1 / (bench.stats.mean + bench.stats.moe); 10 | return isFinite(result) ? result : 0; 11 | } 12 | 13 | function onComplete() { 14 | this.forEach(function (bench) { 15 | console.log(bench.name + ' ' + getHz(bench) + ' ops/sec'); // eslint-disable-line no-console 16 | }); 17 | } 18 | 19 | var map = {}; 20 | 21 | for (var i = 0; i < 10 ; i++) { 22 | map['a' + i] = i; 23 | } 24 | 25 | var spec = { a1: { $set: 2 } }; 26 | 27 | function updateTest() { 28 | var obj = t.update(map, spec); 29 | obj.a1; 30 | } 31 | 32 | Benchmark.Suite({ }) 33 | .add('updateTest', updateTest) 34 | .on('complete', onComplete) 35 | .run({ async: true }); 36 | -------------------------------------------------------------------------------- /tcomb-tests.ts: -------------------------------------------------------------------------------- 1 | import * as t from './index' 2 | 3 | // 4 | // basic types 5 | // 6 | 7 | // * Any * 8 | t.Any('s'); 9 | t.Any(1); 10 | t.Any(true); 11 | 12 | // static members 13 | t.Any.displayName; 14 | 15 | // meta object 16 | t.Any.is(1); 17 | t.Any.meta.kind; 18 | t.Any.meta.name; 19 | t.Any.meta.identity; 20 | t.Any.meta.predicate; 21 | 22 | // * Nil * 23 | t.Nil(null); 24 | t.Nil(undefined); 25 | 26 | // * String * 27 | t.String('s'); 28 | 29 | // * Number * 30 | t.Number(1); 31 | 32 | // * Boolean * 33 | t.Boolean(true); 34 | t.Boolean(false); 35 | 36 | // * Array * 37 | t.Array([]) 38 | t.Array([1, 2, 's']) 39 | 40 | // * Object * 41 | t.Object({}) 42 | t.Object({a: 1, b: 's'}) 43 | 44 | // * Function * 45 | t.Function(() => {}) 46 | 47 | // * Error * 48 | t.Error(new Error()) 49 | 50 | // * RegExp * 51 | t.RegExp(/a/) 52 | 53 | // * Date * 54 | t.Date(new Date()) 55 | 56 | // test type guard 57 | function testTypeGuardBuiltIn(x: string | number): string { 58 | if (t.String.is(x)) { 59 | return x; 60 | } 61 | return String(x); 62 | } 63 | 64 | // 65 | // irreducible combinator 66 | // 67 | 68 | // * based on string * 69 | const Url = t.irreducible('Url', (s) => t.String.is(s) && s.indexOf('http') === 0); 70 | Url('s') 71 | 72 | // static members 73 | Url.displayName; 74 | 75 | // meta object 76 | Url.meta.kind; 77 | Url.meta.name; 78 | Url.meta.identity; 79 | Url.meta.predicate; 80 | 81 | // using a tcomb type as type annotation 82 | function f(url: typeof Url.t) {} 83 | 84 | // * based on Date * 85 | const PastDate = t.irreducible('PastDate', (date) => t.Date.is(date) && date.getTime() < new Date().getTime()); 86 | PastDate(new Date(1973, 10, 30)) 87 | 88 | // 89 | // refinement combinator 90 | // 91 | 92 | // * a refinement based on a basic type * 93 | const Email = t.refinement(t.String, (s) => s.indexOf('@') !== -1); 94 | Email('s') 95 | 96 | // static members 97 | Email.displayName; 98 | 99 | // meta object 100 | Email.meta.kind; 101 | Email.meta.name; 102 | Email.meta.identity; 103 | Email.meta.type; 104 | Email.meta.predicate; 105 | 106 | // * a refinement based on a Class * 107 | class A {} 108 | const ARefinement = t.refinement(A, () => true); 109 | 110 | ARefinement(new A()) 111 | 112 | // 113 | // interface combinator 114 | // 115 | 116 | const MyCoolType = t.interface( 117 | { isCool: t.Boolean }, 118 | { name: 'My cool type', strict: false } 119 | ); 120 | 121 | const MySuperCoolType = MyCoolType.extend( 122 | { isEvenCooler: t.Boolean }, 123 | { name: 'My super cool type', strict: false } 124 | ); 125 | 126 | // 127 | // struct combinator 128 | // 129 | 130 | interface Person { 131 | name: string; 132 | age: number; 133 | } 134 | 135 | const Person = t.struct({ 136 | name: t.String, 137 | age: t.Number 138 | }, 'Person'); 139 | 140 | const person1 = new Person({ name: 'Giulio', age: 42 }) 141 | const person2 = Person({ name: 'Giulio', age: 42 }) 142 | 143 | // static members 144 | Person.displayName; 145 | 146 | // extend function 147 | interface Person2 extends Person { 148 | surname: string; 149 | } 150 | const Person2 = Person.extend({ surname: t.String }) 151 | 152 | const Person2b = Person.extend( 153 | { surname: t.String }, 154 | { name: 'Person 2b', strict: false } 155 | ) 156 | 157 | // update function 158 | const person3 = Person.update(person1, { 159 | name: {$set: 'Guido'} 160 | }); 161 | 162 | // meta object 163 | Person.meta.kind; 164 | Person.meta.name; 165 | Person.meta.identity; 166 | Person.meta.props; 167 | 168 | // 169 | // list combinator 170 | // 171 | 172 | const Tags = t.list(t.String, 'Tags'); 173 | 174 | const list1 = Tags(['a', 'b']); 175 | 176 | // static members 177 | Tags.displayName; 178 | 179 | // update function 180 | const list2 = Tags.update(list1, { 181 | 0: {$set: 's'} 182 | }); 183 | 184 | // meta object 185 | Tags.meta.kind; 186 | Tags.meta.name; 187 | Tags.meta.identity; 188 | Tags.meta.type; 189 | 190 | // 191 | // dict combinator 192 | // 193 | 194 | const Phones = t.dict(t.String, t.Number, 'Phones'); 195 | 196 | const dict1 = Phones({a: 1, b: 2}); 197 | 198 | // static members 199 | Phones.displayName; 200 | 201 | // update function 202 | const dict2 = Phones.update(dict1, { 203 | $remove: ['a'] 204 | }); 205 | 206 | // meta object 207 | Phones.meta.kind; 208 | Phones.meta.name; 209 | Phones.meta.identity; 210 | Phones.meta.domain; 211 | Phones.meta.codomain; 212 | 213 | // 214 | // enums combinator 215 | // 216 | 217 | const Country = t.enums({ 218 | IT: 'Italy', 219 | US: 'United States' 220 | }, 'Country'); 221 | 222 | const country = Country('IT'); 223 | 224 | // static members 225 | Country.displayName; 226 | 227 | // meta object 228 | Country.meta.kind; 229 | Country.meta.name; 230 | Country.meta.identity; 231 | Country.meta.map; 232 | 233 | // of 234 | const Country2 = t.enums.of(['IT', 'US'], 'Country2'); 235 | const Country3 = t.enums.of('IT US', 'Country3'); 236 | 237 | // 238 | // maybe combinator 239 | // 240 | 241 | const MaybeString = t.maybe(t.String, 'MaybeString'); 242 | 243 | MaybeString('s'); 244 | MaybeString(null); 245 | MaybeString(undefined); 246 | 247 | // static members 248 | MaybeString.displayName; 249 | 250 | // meta object 251 | MaybeString.meta.kind; 252 | MaybeString.meta.name; 253 | MaybeString.meta.identity; 254 | MaybeString.meta.type; 255 | 256 | // 257 | // tuple combinator 258 | // 259 | 260 | const Size = t.tuple<[number, number]>([t.Number, t.Number], 'Size'); 261 | type Size = typeof Size.t; 262 | 263 | const size1 = Size([100, 200]); 264 | 265 | // static members 266 | Size.displayName; 267 | 268 | // meta object 269 | Size.meta.kind; 270 | Size.meta.name; 271 | Size.meta.identity; 272 | Size.meta.types; 273 | 274 | // update function 275 | const size2 = Size.update(size1, { 276 | 0: { $set: 150 } 277 | }); 278 | 279 | // 280 | // union combinator 281 | // 282 | 283 | const Union = t.union([Person, Size], 'Union'); 284 | Union.dispatch = function (x) { 285 | return t.Array.is(x) ? Size : Person; 286 | }; 287 | 288 | const union1 = Union({ name: 'Giulio', age: 42 }); 289 | 290 | // static members 291 | Union.displayName; 292 | Union.dispatch({ name: 'Giulio', age: 42 }); 293 | 294 | // meta object 295 | Union.meta.kind; 296 | Union.meta.name; 297 | Union.meta.identity; 298 | Union.meta.types; 299 | 300 | // update function 301 | const union2 = Union.update(union1, { 302 | name: { $set: 'Guido' } 303 | }); 304 | 305 | // 306 | // union combinator 307 | // 308 | 309 | const Min = t.refinement(t.String, (s) => s.length > 2); 310 | const Max = t.refinement(t.String, (s) => s.length < 5); 311 | const MinMax = t.intersection([Min, Max], 'MinMax'); 312 | 313 | const minmax1 = MinMax('s'); 314 | 315 | // static members 316 | MinMax.displayName; 317 | 318 | // meta object 319 | MinMax.meta.kind; 320 | MinMax.meta.name; 321 | MinMax.meta.identity; 322 | MinMax.meta.types; 323 | 324 | // update function 325 | const minmax2 = MinMax.update(minmax1, { $set: 'ss' }); 326 | 327 | // 328 | // declare combinator 329 | // 330 | type Tree = { 331 | value: number; 332 | left?: Tree; 333 | right?: Tree; 334 | }; 335 | const Tree = t.declare('Tree'); 336 | 337 | Tree.define(t.struct({ 338 | value: t.Number, 339 | left: t.maybe(Tree), 340 | right: t.maybe(Tree) 341 | })); 342 | 343 | const bst = Tree({ 344 | value: 5, 345 | left: { 346 | value: 2 347 | }, 348 | right: { 349 | left: { 350 | value: 6 351 | }, 352 | value: 7 353 | } 354 | }); 355 | 356 | // 357 | // is 358 | // 359 | t.is(1, t.Number); 360 | 361 | // 362 | // assert 363 | // 364 | t.assert(true, 'a message'); 365 | t.assert(true, () => 'a lazy message'); 366 | 367 | // 368 | // fail 369 | // 370 | t.fail('a message'); 371 | 372 | // 373 | // isType 374 | // 375 | t.isType(t.String); 376 | t.isType(A); 377 | 378 | // 379 | // getTypeName 380 | // 381 | t.getTypeName(t.String); 382 | t.getTypeName(A); 383 | 384 | // 385 | // mixin 386 | // 387 | t.mixin({a: 1}, {b: 2}).a; 388 | t.mixin({a: 1}, {b: 2}).b; 389 | 390 | // 391 | // match 392 | // 393 | t.match(1, 394 | t.String, (s) => 'a string', 395 | t.Number, (n) => n > 2, (n) => 'a number gt 2', // case with a guard (optional) 396 | t.Number, (n) => 'a number lte 2', 397 | A, (a) => 'an instance of A', 398 | t.Any, (x) => 'other...' // catch all 399 | ); 400 | 401 | // 402 | // update 403 | // 404 | t.update({}, { a: { $set: 1 } }); 405 | 406 | -------------------------------------------------------------------------------- /test/assert.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | describe('t.assert(guard, [message])', function () { 7 | 8 | it('should nor throw when guard is true', function () { 9 | t.assert(true); 10 | }); 11 | 12 | it('should throw a default message', function () { 13 | throwsWithMessage(function () { 14 | t.assert(1 === 2); // eslint-disable-line 15 | }, '[tcomb] Assert failed (turn on "Pause on exceptions" in your Source panel)'); 16 | }); 17 | 18 | it('should throw the specified message', function () { 19 | throwsWithMessage(function () { 20 | t.assert(1 === 2, 'my message'); // eslint-disable-line 21 | }, '[tcomb] my message'); 22 | }); 23 | 24 | it('should handle lazy messages', function () { 25 | throwsWithMessage(function () { 26 | t.assert(1 === 2, function () { return 'lazy'; }); // eslint-disable-line 27 | }, '[tcomb] lazy'); 28 | }); 29 | 30 | it('should handle custom fail behaviour', function () { 31 | var fail = t.fail; 32 | t.fail = function (message) { 33 | try { 34 | throw new Error(message); 35 | } catch (e) { 36 | assert.strictEqual(e.message, 'report error'); 37 | } 38 | }; 39 | assert.doesNotThrow(function () { 40 | t.assert(1 === 2, 'report error'); // eslint-disable-line 41 | }); 42 | t.fail = fail; 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/declare.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | var create = require('../lib/create'); 6 | 7 | var A = t.declare('A'); 8 | 9 | var B = t.struct({ 10 | a: t.maybe(A) 11 | }); 12 | 13 | A.define(t.struct({ 14 | b: t.maybe(B) 15 | })); 16 | 17 | describe('t.declare([name])', function () { 18 | 19 | describe('combinator', function () { 20 | 21 | it('should throw if used with wrong arguments', function () { 22 | 23 | assert.throws(function () { 24 | t.declare(t.Number); 25 | }, function(err) { 26 | assert.strictEqual(err instanceof Error, true); 27 | assert.ok(/\[tcomb\] Invalid argument name function (.|\n)* supplied to declare\(\[name\]\) \(expected a string\)/m.test(err.message)); 28 | return true; 29 | }); 30 | 31 | throwsWithMessage(function () { 32 | t.declare('D') 33 | .define('not a type'); 34 | }, '[tcomb] Invalid argument type "not a type" supplied to define(type) (expected a type)'); 35 | 36 | }); 37 | 38 | it('should throw if define-d multiple times', function () { 39 | throwsWithMessage(function () { 40 | t.declare('D') 41 | .define(t.list(t.Any)) 42 | .define(t.list(t.Any)); 43 | }, '[tcomb] Declare.define(type) can only be invoked once'); 44 | }); 45 | 46 | it('should have a fresh name for different declares when not explicitly provided', function() { 47 | var Nameless1 = t.declare(); 48 | Nameless1.define(t.struct({ 49 | thing: Nameless1 50 | })); 51 | assert.throws(function() { 52 | Nameless1({}); 53 | }, function(err) { 54 | assert.strictEqual(err instanceof Error, true); 55 | assert.ok(/\[tcomb\] Invalid value .+ supplied to Struct{thing: Declare\$[0-9]+}\/thing: Struct{thing: Declare\$[0-9]+} \(expected an object\)/m.test(err.message)); 56 | return true; 57 | }); 58 | var Nameless2 = t.declare(); 59 | assert.ok(t.getTypeName(Nameless1) !== t.getTypeName(Nameless2)); 60 | }); 61 | 62 | it('an instance of the declared type should satisfy instanceof, if the concrete type is a struct', function() { 63 | var Struct = t.declare('Struct') 64 | .define(t.struct({})); 65 | var actual = new Struct({}); 66 | assert.ok(actual instanceof Struct); 67 | }); 68 | 69 | it('should have the expected names', function() { 70 | var Named = t.declare('Named'); 71 | Named.define(t.list(t.Any)); 72 | assert.strictEqual(Named.displayName, 'Named'); 73 | assert.strictEqual(Named.meta.name, 'Named'); 74 | 75 | var Nameless = t.declare(); 76 | assert.strictEqual(Nameless.displayName, 'Declare$3'); 77 | Nameless.define(t.list(t.Any)); 78 | assert.strictEqual(Nameless.displayName, 'Array'); 79 | assert.strictEqual(Nameless.meta.name, undefined); 80 | }); 81 | 82 | it('should support adding functions to the prototype, when allowed by the concrete type', function() { 83 | function getValue() { return this.value; } 84 | var Struct = t.declare('Struct') 85 | .define(t.struct({ 86 | value: t.Number 87 | })); 88 | Struct.prototype.getValue = getValue; 89 | assert.equal(42, Struct({value: 42}).getValue()); 90 | }); 91 | 92 | // it('should throw when defined with a non-fresh type', function() { 93 | // throwsWithMessage(function () { 94 | // var ANum = t.declare(); 95 | // ANum.define(t.Number); 96 | // }, '[tcomb] Invalid argument type "Number" supplied to define(type) (expected a fresh, unnamed type)'); 97 | // }); 98 | 99 | it('should play well with identity', function () { 100 | var Tuple = t.declare('Tuple'); 101 | var Result = t.list(Tuple); 102 | assert.equal(Tuple.meta && Tuple.meta.identity, false); 103 | assert.equal(Result.meta && Result.meta.identity, false); 104 | Tuple.define(t.tuple([t.String])); 105 | assert.equal(Tuple.meta && Tuple.meta.identity, true); 106 | assert.equal(Result.meta && Result.meta.identity, false); 107 | 108 | Tuple = t.declare('Tuple'); 109 | assert.equal(Tuple.meta && Tuple.meta.identity, false); 110 | assert.equal(Result.meta && Result.meta.identity, false); 111 | Result = t.list(Tuple); 112 | Tuple.define(t.struct({})); 113 | assert.equal(Tuple.meta && Tuple.meta.identity, false); 114 | assert.equal(Result.meta && Result.meta.identity, false); 115 | }); 116 | 117 | it('should play well with enums', function () { 118 | var A = t.declare('A'); 119 | A.define(t.enums.of(['a'])); 120 | assert.strictEqual(create(A, 'a'), 'a'); 121 | 122 | var B = t.declare('B'); 123 | B.define(t.refinement(t.String, function () { return true; })); 124 | assert.strictEqual(create(B, 'a string'), 'a string'); 125 | }); 126 | 127 | it('should play well with custom dispatch', function () { 128 | function dispatch(x) { 129 | return t.String.is(x) ? t.String : U; 130 | } 131 | var U = t.declare('U'); 132 | U.dispatch = dispatch; 133 | var UU = t.union([t.String, t.list(U)]); 134 | U.define(UU); 135 | assert.strictEqual(U.dispatch, dispatch); 136 | assert.strictEqual(UU.dispatch, dispatch); 137 | 138 | var U2 = t.declare('U'); 139 | U2.define(t.union([t.String, t.list(U)])); 140 | U2.dispatch = dispatch; 141 | throwsWithMessage(function () { 142 | U2('a'); 143 | }, '[tcomb] Please define the custom U.dispatch function before calling U.define()'); 144 | }); 145 | 146 | }); 147 | 148 | describe('constructor', function () { 149 | 150 | it('should be idempotent', function () { 151 | var p1 = A({ 152 | b: { 153 | a: { 154 | b: null 155 | } 156 | } 157 | }); 158 | var p2 = A(p1); 159 | assert.deepEqual(p2 === p1, true); 160 | }); 161 | 162 | it('should accept only valid values', function () { 163 | throwsWithMessage(function () { 164 | A({b: 12}); 165 | }, '[tcomb] Invalid value 12 supplied to Struct{b: ?Struct{a: ?A}}/b: ?Struct{a: ?A} (expected an object)'); 166 | throwsWithMessage(function () { 167 | A({b: B({ a: 13 }) }); 168 | }, '[tcomb] Invalid value 13 supplied to Struct{a: ?A}/a: ?A (expected an object)'); 169 | }); 170 | 171 | it('should throw if the type was not defined', function () { 172 | throwsWithMessage(function () { 173 | var D = t.declare('D'); 174 | D({a: A({}) }); 175 | }, '[tcomb] Type declared but not defined, don\'t forget to call .define on every declared type'); 176 | }); 177 | 178 | }); 179 | 180 | describe('#is(x)', function () { 181 | 182 | it('should return true when x is an instance of the type', function () { 183 | var a = new A({ 184 | b: { 185 | a: { 186 | b: null 187 | } 188 | } 189 | }); 190 | assert.ok(A.is(a)); 191 | }); 192 | 193 | }); 194 | 195 | }); 196 | -------------------------------------------------------------------------------- /test/dict.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | describe('t.dict(domain, codomain, [name])', function () { 7 | 8 | describe('combinator', function () { 9 | 10 | it('should throw if used with wrong arguments', function () { 11 | 12 | util.throwsWithMessage(function () { 13 | t.dict(); 14 | }, '[tcomb] Invalid argument domain undefined supplied to dict(domain, codomain, [name]) combinator (expected a type)'); 15 | 16 | util.throwsWithMessage(function () { 17 | t.dict(t.String); 18 | }, '[tcomb] Invalid argument codomain undefined supplied to dict(domain, codomain, [name]) combinator (expected a type)'); 19 | 20 | util.throwsWithMessage(function () { 21 | t.dict(t.String, t.String, 1); 22 | }, '[tcomb] Invalid argument name 1 supplied to dict(domain, codomain, [name]) combinator (expected a string)'); 23 | 24 | }); 25 | 26 | }); 27 | 28 | describe('constructor', function () { 29 | 30 | var Domain = t.subtype(t.String, function (x) { 31 | return x !== 'forbidden'; 32 | }, 'Domain'); 33 | var Codomain = t.struct({name: t.String}, 'Codomain'); 34 | var Dictionary = t.dict(Domain, Codomain, 'Dictionary'); 35 | 36 | it('should throw with a contextual error message if used with wrong arguments', function () { 37 | 38 | util.throwsWithMessage(function () { 39 | Dictionary(1); 40 | }, '[tcomb] Invalid value 1 supplied to Dictionary'); 41 | 42 | util.throwsWithMessage(function () { 43 | Dictionary({a: 1}); 44 | }, '[tcomb] Invalid value 1 supplied to Dictionary/a: Codomain (expected an object)'); 45 | 46 | util.throwsWithMessage(function () { 47 | Dictionary({forbidden: {}}); 48 | }, '[tcomb] Invalid value "forbidden" supplied to Dictionary/Domain'); 49 | 50 | }); 51 | 52 | it('should hydrate the values of the dictionary', function () { 53 | var instance = Dictionary({a: {name: 'Giulio'}}); 54 | assert.ok(Codomain.is(instance.a)); 55 | }); 56 | 57 | it('should hydrate the values of the dictionary in production', util.production(function () { 58 | var instance = Dictionary({a: {name: 'Giulio'}}); 59 | assert.ok(Codomain.is(instance.a)); 60 | })); 61 | 62 | it('should be idempotent', function () { 63 | var d0 = {a: {name: 'Giulio'}, b: {name: 'Guido'}}; 64 | var d1 = Dictionary(d0); 65 | var d2 = Dictionary(d1); 66 | assert.equal(d0 === d1, false); 67 | assert.equal(d1 === d2, true); 68 | }); 69 | 70 | it('should be idempotent in production', util.production(function () { 71 | var d0 = {a: {name: 'Giulio'}, b: {name: 'Guido'}}; 72 | var d1 = Dictionary(d0); 73 | var d2 = Dictionary(d1); 74 | assert.equal(d0 === d1, false); 75 | assert.equal(d1 === d2, true); 76 | })); 77 | 78 | it('should freeze the instance', function () { 79 | var instance = Dictionary({}); 80 | assert.equal(Object.isFrozen(instance), true); 81 | }); 82 | 83 | it('should not freeze the instance in production', util.production(function () { 84 | var instance = Dictionary({}); 85 | assert.equal(Object.isFrozen(instance), false); 86 | })); 87 | 88 | }); 89 | 90 | describe('is(x)', function () { 91 | 92 | var Point = t.struct({ 93 | x: t.Number, 94 | y: t.Number 95 | }); 96 | 97 | var T = t.dict(t.String, Point); 98 | var p1 = new Point({x: 0, y: 0}); 99 | var p2 = new Point({x: 1, y: 1}); 100 | 101 | it('should return true when x is a dictionary', function () { 102 | assert.equal(T.is({a: p1, b: p2}), true); 103 | assert.equal(T.is({a: {x: 0, y: 0}, b: {x: 1, y: 1}}), false); 104 | }); 105 | 106 | it('should be used as a predicate', function () { 107 | assert.ok([{a: p1, b: p2}].every(T.is)); 108 | }); 109 | 110 | }); 111 | 112 | describe('update(instance, patch)', function () { 113 | 114 | it('should return a new instance', function () { 115 | var Dictionary = t.dict(t.String, t.String); 116 | var instance = Dictionary({a: 'a', b: 'b'}); 117 | var newInstance = Dictionary.update(instance, {b: {$set: 'c'}}); 118 | assert.ok(Dictionary.is(newInstance)); 119 | assert.equal(newInstance !== instance, true); 120 | assert.deepEqual(newInstance, {a: 'a', b: 'c'}); 121 | }); 122 | 123 | }); 124 | 125 | }); 126 | -------------------------------------------------------------------------------- /test/enums.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | describe('t.enums(map, [name])', function () { 7 | 8 | describe('combinator', function () { 9 | 10 | it('should throw if used with wrong arguments', function () { 11 | 12 | throwsWithMessage(function () { 13 | t.enums(); 14 | }, '[tcomb] Invalid argument map undefined supplied to enums(map, [name]) combinator (expected a dictionary of String -> String | Number)'); 15 | 16 | throwsWithMessage(function () { 17 | t.enums({}, 1); 18 | }, '[tcomb] Invalid argument name 1 supplied to enums(map, [name]) combinator (expected a string)'); 19 | 20 | }); 21 | 22 | }); 23 | 24 | describe('constructor', function () { 25 | 26 | var T = t.enums({a: 0}, 'T'); 27 | 28 | it('should throw if used with new', function () { 29 | throwsWithMessage(function () { 30 | var x = new T('a'); // eslint-disable-line 31 | }, '[tcomb] Cannot use the new operator to instantiate the type T'); 32 | }); 33 | 34 | it('should accept only valid values', function () { 35 | assert.deepEqual(T('a'), 'a'); 36 | throwsWithMessage(function () { 37 | T('b'); 38 | }, '[tcomb] Invalid value "b" supplied to T (expected one of [\n "a"\n])'); 39 | }); 40 | 41 | }); 42 | 43 | describe('#is(x)', function () { 44 | 45 | var Direction = t.enums({ 46 | North: 0, 47 | East: 1, 48 | South: 2, 49 | West: 3, 50 | 1: 'North-East', 51 | 2.5: 'South-East' 52 | }); 53 | 54 | it('should return true when x is an instance of the enum', function () { 55 | assert.ok(Direction.is('North')); 56 | assert.ok(Direction.is(1)); 57 | assert.ok(Direction.is('1')); 58 | assert.ok(Direction.is(2.5)); 59 | }); 60 | 61 | it('should return false when x is not an instance of the enum', function () { 62 | assert.strictEqual(Direction.is('North-East'), false); 63 | assert.strictEqual(Direction.is(2), false); 64 | assert.strictEqual(Direction.is(['North']), false); 65 | }); 66 | 67 | }); 68 | 69 | describe('#of(keys)', function () { 70 | 71 | it('should return an enum', function () { 72 | var Size = t.enums.of(['large', 'small', 1, 10.9]); 73 | assert.ok(Size.meta.map.large === 'large'); 74 | assert.ok(Size.meta.map.small === 'small'); 75 | assert.ok(Size.meta.map['1'] === 1); 76 | assert.ok(Size.meta.map[10.9] === 10.9); 77 | }); 78 | 79 | it('should handle a string', function () { 80 | var Size = t.enums.of('large small 10'); 81 | assert.ok(Size.meta.map.large === 'large'); 82 | assert.ok(Size.meta.map.small === 'small'); 83 | assert.ok(Size.meta.map['10'] === '10'); 84 | assert.ok(Size.meta.map[10] === '10'); 85 | }); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /test/es6.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | describe('ES6 classes', function () { 7 | 8 | function Class(a) { 9 | this.a = a; 10 | } 11 | 12 | var c = new Class('a'); 13 | 14 | it('should be handled by subtype', function () { 15 | var T = t.subtype(Class, function isA(x) { 16 | return x.a === 'a'; 17 | }); 18 | assert.deepEqual(T.is(c), true); 19 | throwsWithMessage(function () { 20 | T(new Class('b')); 21 | }, '[tcomb] Invalid value {\n "a": "b"\n} supplied to {Class | isA}'); 22 | }); 23 | 24 | it('should be handled by struct', function () { 25 | var T = t.struct({ 26 | c: Class 27 | }, 'T'); 28 | assert.deepEqual(T.is(new T({c: c})), true); 29 | throwsWithMessage(function () { 30 | T({c: 1}); 31 | }, '[tcomb] Invalid value 1 supplied to T/c: Class'); 32 | }); 33 | 34 | it('should be handled by maybe', function () { 35 | var T = t.maybe(Class); 36 | assert.deepEqual(T.is(null), true); 37 | assert.deepEqual(T.is(c), true); 38 | throwsWithMessage(function () { 39 | T(1); 40 | }, '[tcomb] Invalid value 1 supplied to Class'); 41 | }); 42 | 43 | it('should be handled by tuple', function () { 44 | var T = t.tuple([Class]); 45 | assert.deepEqual(T.is([c]), true); 46 | throwsWithMessage(function () { 47 | T([1]); 48 | }, '[tcomb] Invalid value 1 supplied to [Class]/0: Class'); 49 | }); 50 | 51 | it('should be handled by list', function () { 52 | var T = t.list(Class); 53 | assert.deepEqual(T.is([c]), true); 54 | throwsWithMessage(function () { 55 | T([1]); 56 | }, '[tcomb] Invalid value 1 supplied to Array/0: Class'); 57 | }); 58 | 59 | it('should be handled by dict', function () { 60 | var T = t.dict(t.String, Class); 61 | assert.deepEqual(T.is({a: c}), true); 62 | throwsWithMessage(function () { 63 | T({a: 1}); 64 | }, '[tcomb] Invalid value 1 supplied to {[key: String]: Class}/a: Class'); 65 | }); 66 | 67 | it('should be handled by union', function () { 68 | var T = t.union([t.String, Class]); 69 | assert.deepEqual(T.is(c), true); 70 | throwsWithMessage(function () { 71 | T(1); 72 | }, '[tcomb] Invalid value 1 supplied to String | Class (no constructor returned by dispatch)'); 73 | }); 74 | 75 | it('should be handled by func', function () { 76 | var T = t.func(Class, t.String); 77 | var f = T.of(function (c) { 78 | return c.constructor.name; 79 | }); 80 | assert.deepEqual(f(c), 'Class'); 81 | throwsWithMessage(function () { 82 | f(1); 83 | }, '[tcomb] Invalid value 1 supplied to arguments of function (Class) => String/0: Class'); 84 | }); 85 | 86 | }); 87 | -------------------------------------------------------------------------------- /test/fromJSON.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var fromJSON = require('../lib/fromJSON'); 5 | var util = require('./util'); 6 | 7 | describe('fromJSON', function () { 8 | 9 | // don't pollute the MyDate type 10 | var MyDate = t.refinement(t.Date, function () { return true; }, 'MyDate'); 11 | var date = new Date(1973, 10, 30); 12 | function jsonify(x) { 13 | return JSON.parse(JSON.stringify(x)); 14 | } 15 | 16 | MyDate.fromJSON = function (s) { 17 | t.assert(t.String.is(s)); 18 | return new Date(s); 19 | }; 20 | 21 | it('should throw with a bad type argument', function () { 22 | util.throwsWithMessage(function () { 23 | fromJSON(); 24 | }, '[tcomb] Invalid argument type undefined supplied to fromJSON(value, type) (expected a type)'); 25 | }); 26 | 27 | it('should handle a static fromJSON function attached to the type', function () { 28 | var MyType = t.refinement(t.String, function () { return true; }); 29 | MyType.fromJSON = function (s) { 30 | return s + 'ok'; 31 | }; 32 | assert.equal(fromJSON('aaa', MyType), 'aaaok'); 33 | }); 34 | 35 | it('should handle class constructors', function () { 36 | var expected = '1973-11-30T00:00:00.000Z'; 37 | assert.strictEqual(fromJSON('1973-11-30T00:00:00.000Z', Date).toISOString(), expected); 38 | 39 | var actual = new RegExp('a'); 40 | assert.strictEqual(fromJSON(actual, RegExp), actual); 41 | 42 | var NonEmptyString = t.refinement(t.String, function (s) { return s.length > 0; }, 'NonEmptyString'); 43 | NonEmptyString.fromJSON = function (s) { return s.trim(); }; 44 | util.throwsWithMessage(function () { 45 | fromJSON(' ', NonEmptyString); 46 | }, '[tcomb] Invalid value "" supplied to NonEmptyString'); 47 | 48 | Date.fromJSON = function (s) { 49 | return new Date(s); 50 | }; 51 | assert.strictEqual(fromJSON('1973-11-30T00:00:00.000Z', Date).toISOString(), expected); 52 | delete Date.fromJSON; 53 | }); 54 | 55 | it('should handle maybe', function () { 56 | var MyType = t.maybe(MyDate); 57 | assert.strictEqual(fromJSON(null, MyType), null); 58 | assert.deepEqual(fromJSON(jsonify(date), MyType), date); 59 | util.throwsWithMessage(function () { 60 | fromJSON(1, t.maybe(t.String)); 61 | }, '[tcomb] Invalid value 1 supplied to ?String'); 62 | }); 63 | 64 | it('should handle refinement', function () { 65 | var MyType = t.refinement(MyDate, function (d) { 66 | return d.getTime() >= date.getTime(); 67 | }, 'MyType'); 68 | assert.equal(fromJSON(jsonify(date), MyType).getTime(), date.getTime()); 69 | util.throwsWithMessage(function () { 70 | fromJSON(jsonify(new Date(123375600000)), MyType); 71 | }, '[tcomb] Invalid argument value "1973-11-28T23:00:00.000Z" supplied to fromJSON(value, type) (expected a valid MyType)'); 72 | util.throwsWithMessage(function () { 73 | fromJSON(1, t.refinement(t.String, function (s) { return s.length > 2; })); 74 | }, '[tcomb] Invalid value 1 supplied to {String | }'); 75 | }); 76 | 77 | it('should handle struct', function () { 78 | var MyType = t.struct({ 79 | name: t.String, 80 | birthDate: MyDate 81 | }, 'MyType'); 82 | 83 | util.throwsWithMessage(function () { 84 | fromJSON(null, MyType); 85 | }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (expected an object for type MyType)'); 86 | 87 | var source = { 88 | name: 'Giulio', 89 | birthDate: date 90 | }; 91 | var json = jsonify(source); 92 | var actual = fromJSON(json, MyType); 93 | assert.ok(actual instanceof MyType); 94 | assert.deepEqual(actual, source); 95 | 96 | util.throwsWithMessage(function () { 97 | fromJSON({}, MyType); 98 | }, '[tcomb] Invalid value undefined supplied to MyType/name: String'); 99 | }); 100 | 101 | it('should handle struct with options.defaultProps', function () { 102 | var MyType = t.struct({ 103 | surname: t.String, 104 | number: t.Number 105 | }, { 106 | name: 'MyType', 107 | defaultProps: { 108 | surname: 'Canti', 109 | get number() { 110 | return 2; 111 | } 112 | } 113 | }); 114 | 115 | var source = {}; 116 | var expected = { 117 | surname: 'Canti', 118 | number: 2 119 | }; 120 | var json = jsonify(source); 121 | var actual = fromJSON(json, MyType); 122 | assert.ok(actual instanceof MyType); 123 | assert.deepEqual(actual, expected); 124 | }); 125 | 126 | it('should handle interface', function () { 127 | var MyType = t.interface({ 128 | name: t.String, 129 | birthDate: MyDate 130 | }, 'MyType'); 131 | 132 | util.throwsWithMessage(function () { 133 | fromJSON(null, MyType); 134 | }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (expected an object)'); 135 | 136 | var source = { 137 | name: 'Giulio', 138 | birthDate: date 139 | }; 140 | var json = jsonify(source); 141 | assert.deepEqual(fromJSON(json, MyType), source); 142 | 143 | util.throwsWithMessage(function () { 144 | fromJSON({}, MyType); 145 | }, '[tcomb] Invalid value undefined supplied to MyType/name: String'); 146 | }); 147 | 148 | it('should handle list', function () { 149 | var MyType = t.list(MyDate, 'MyType'); 150 | 151 | util.throwsWithMessage(function () { 152 | fromJSON(null, MyType); 153 | }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (expected an array for type MyType)'); 154 | 155 | var source = [new Date(2016, 10, 30), new Date(2073, 10, 30)]; 156 | var json = jsonify(source); 157 | assert.deepEqual(fromJSON(json, MyType), source); 158 | 159 | util.throwsWithMessage(function () { 160 | fromJSON([1], t.list(t.String)); 161 | }, '[tcomb] Invalid value 1 supplied to Array/0: String'); 162 | }); 163 | 164 | it('should handle union', function () { 165 | var MyType = t.union([t.Number, MyDate], 'MyType'); 166 | 167 | MyType.dispatch = function (x) { 168 | if (t.Number.is(x)) { return t.Number; } 169 | if (MyDate.is(x) || t.String.is(x)) { return MyDate; } 170 | }; 171 | 172 | util.throwsWithMessage(function () { 173 | fromJSON(null, MyType); 174 | }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (no constructor returned by dispatch of union MyType)'); 175 | 176 | var source = new Date(2016, 10, 30); 177 | var json = jsonify(source); 178 | assert.deepEqual(fromJSON(json, MyType), source); 179 | source = 1; 180 | json = jsonify(source); 181 | assert.deepEqual(fromJSON(json, MyType), source); 182 | }); 183 | 184 | it('should handle tuple', function () { 185 | var MyType = t.tuple([t.String, MyDate], 'MyType'); 186 | 187 | util.throwsWithMessage(function () { 188 | fromJSON(null, MyType); 189 | }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (expected an array for type MyType)'); 190 | 191 | var source = ['s', new Date(2016, 10, 30)]; 192 | var json = jsonify(source); 193 | assert.deepEqual(fromJSON(json, MyType), source); 194 | 195 | util.throwsWithMessage(function () { 196 | fromJSON([1, 1], MyType); 197 | }, '[tcomb] Invalid value 1 supplied to MyType/0: String'); 198 | }); 199 | 200 | it('should handle dict', function () { 201 | var MyType = t.dict(t.String, MyDate, 'MyType'); 202 | 203 | util.throwsWithMessage(function () { 204 | fromJSON(null, MyType); 205 | }, '[tcomb] Invalid argument value null supplied to fromJSON(value, type) (expected an object for type MyType)'); 206 | 207 | var source = {a: new Date(2016, 10, 30)}; 208 | var json = jsonify(source); 209 | assert.deepEqual(fromJSON(json, MyType), source); 210 | 211 | util.throwsWithMessage(function () { 212 | fromJSON({a: 'a'}, t.dict(t.refinement(t.String, function (s) { s.length > 2; }, 'Gt2'), t.String)); 213 | }, '[tcomb] Invalid value "a" supplied to {[key: Gt2]: String}/Gt2'); 214 | util.throwsWithMessage(function () { 215 | fromJSON({a: 1}, t.dict(t.String, t.String)); 216 | }, '[tcomb] Invalid value 1 supplied to {[key: String]: String}/a: String'); 217 | }); 218 | 219 | it('should handle intersections', function () { 220 | var A = t.inter({ foo: t.String }); 221 | var B = t.inter({ bar: Date }); 222 | var AB = t.intersection([A, B]); 223 | var json = { 224 | foo: 'asd', 225 | bar: '2016-10-27T00:00:00.000+00:00' 226 | }; 227 | assert.ok(fromJSON(json, AB).bar instanceof Date); 228 | var I2 = t.intersection([t.Number, t.Number]); 229 | assert.ok(typeof fromJSON(1, I2) === 'number'); 230 | }); 231 | 232 | }); 233 | -------------------------------------------------------------------------------- /test/func.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | describe('t.func(domain, codomain, [name])', function () { 7 | 8 | it('should handle an empty domain', function () { 9 | var T = t.func([], t.String); 10 | assert.deepEqual(T.meta.domain.length, 0); 11 | var getGreeting = T.of(function () { return 'Hi'; }); 12 | assert.deepEqual(getGreeting(), 'Hi'); 13 | }); 14 | 15 | it('should handle a domain with single type', function () { 16 | var T = t.func(t.Number, t.Number); 17 | assert.deepEqual(T.meta.domain.length, 1); 18 | assert.ok(T.meta.domain[0] === t.Number); 19 | }); 20 | 21 | it('should automatically instrument a function', function () { 22 | var T = t.func(t.Number, t.Number); 23 | var f = function () { return 'hi'; }; 24 | assert.ok(T.is(T(f))); 25 | }); 26 | 27 | it('should throw if arguments are wrong', function () { 28 | var T1 = t.func(t.Number, t.Number); 29 | var T2 = t.func(t.Number, t.String); 30 | var f = T2.of(function () { return 'hi'; }); 31 | util.throwsWithMessage(function () { 32 | T1(f); 33 | }, '[tcomb] Invalid value "" supplied to (Number) => Number'); 34 | }); 35 | 36 | describe('of', function () { 37 | 38 | it('should check the arguments', function () { 39 | 40 | var T = t.func([t.Number, t.Number], t.Number); 41 | var sum = T.of(function (a, b) { 42 | return a + b; 43 | }); 44 | assert.deepEqual(sum(1, 2), 3); 45 | 46 | util.throwsWithMessage(function () { 47 | sum(1, 2, 3); 48 | }, '[tcomb] Invalid value [\n 1,\n 2,\n 3\n] supplied to arguments of function (Number, Number) => Number (expected an array of length 2)'); 49 | 50 | util.throwsWithMessage(function () { 51 | sum('a', 2); 52 | }, '[tcomb] Invalid value "a" supplied to arguments of function (Number, Number) => Number/0: Number'); 53 | 54 | }); 55 | 56 | it('should handle optional arguments', function () { 57 | function Class(a) { 58 | this.a = a; 59 | } 60 | assert.equal(t.func.getOptionalArgumentsIndex([t.Number, t.Number]), 2); 61 | assert.equal(t.func.getOptionalArgumentsIndex([t.Number, t.maybe(t.Number)]), 1); 62 | assert.equal(t.func.getOptionalArgumentsIndex([t.maybe(t.Number)]), 0); 63 | assert.equal(t.func.getOptionalArgumentsIndex([t.Number, t.maybe(t.Number), t.Number]), 3); 64 | assert.equal(t.func.getOptionalArgumentsIndex([]), 0); 65 | assert.equal(t.func.getOptionalArgumentsIndex([Class]), 1); 66 | assert.equal(t.func.getOptionalArgumentsIndex([Class, t.maybe(t.Number)]), 1); 67 | 68 | var T = t.func([t.Number, t.maybe(t.Number)], t.Number); 69 | var sum = T.of(function (a, b) { 70 | if (t.Nil.is(b)) { 71 | b = 2; 72 | } 73 | return a + b; 74 | }); 75 | assert.equal(sum(1), 3); 76 | assert.equal(sum(1, 2), 3); 77 | util.throwsWithMessage(function () { 78 | sum(1, 'a'); 79 | }, '[tcomb] Invalid value "a" supplied to arguments of function (Number, ?Number) => Number/1: ?Number'); 80 | }); 81 | 82 | it('should check the return value', function () { 83 | 84 | var T = t.func([t.Number, t.Number], t.Number); 85 | var sum = T.of(function () { 86 | return 'a'; 87 | }); 88 | 89 | util.throwsWithMessage(function () { 90 | sum(1, 2); 91 | }, '[tcomb] Invalid value "a" supplied to Number'); 92 | 93 | }); 94 | 95 | it('should preserve `this`', function () { 96 | var o = {name: 'giulio'}; 97 | o.getTypeName = t.func([], t.String).of(function () { 98 | return this.name; 99 | }); 100 | assert.deepEqual(o.getTypeName(), 'giulio'); 101 | }); 102 | 103 | it('should handle function types', function () { 104 | var A = t.func([t.String], t.String); 105 | var B = t.func([t.String, A], t.String); 106 | 107 | var f = A.of(function (s) { 108 | return s + '!'; 109 | }); 110 | var g = B.of(function (str, strAction) { 111 | return strAction(str); 112 | }); 113 | 114 | assert.deepEqual(g('hello', f), 'hello!'); 115 | }); 116 | 117 | it('should be idempotent', function () { 118 | var f = function (s) { return s; }; 119 | var g = t.func([t.String], t.String).of(f); 120 | var h = t.func([t.String], t.String).of(g); 121 | assert.ok(h === g); 122 | }); 123 | 124 | }); 125 | 126 | describe('currying', function () { 127 | 128 | it('should throw if no arguments are passed in', function () { 129 | var Type = t.func([t.Number, t.Number, t.Number], t.Number); 130 | var sum = Type.of(function (a, b, c) { 131 | return a + b + c; 132 | }, true); 133 | util.throwsWithMessage(function () { 134 | sum(); 135 | }, '[tcomb] Invalid arguments.length = 0 for curried function (Number, Number, Number) => Number'); 136 | }); 137 | 138 | it('should curry functions', function () { 139 | var Type = t.func([t.Number, t.Number, t.Number], t.Number); 140 | var sum = Type.of(function (a, b, c) { 141 | return a + b + c; 142 | }, true); 143 | assert.deepEqual(sum(1, 2, 3), 6); 144 | assert.deepEqual(sum(1, 2)(3), 6); 145 | assert.deepEqual(sum(1)(2, 3), 6); 146 | assert.deepEqual(sum(1)(2)(3), 6); 147 | 148 | // important: the curried function must be of the correct type 149 | var CurriedType = t.func([t.Number, t.Number], t.Number); 150 | var sum1 = sum(1); 151 | assert.deepEqual(sum1(2, 3), 6); 152 | assert.deepEqual(sum1(2)(3), 6); 153 | assert.ok(CurriedType.is(sum1)); 154 | }); 155 | 156 | it('should throw if partial arguments are wrong', function () { 157 | 158 | var T = t.func([t.Number, t.Number], t.Number); 159 | var sum = T.of(function (a, b) { 160 | return a + b; 161 | }, true); 162 | 163 | util.throwsWithMessage(function () { 164 | sum('a'); 165 | }, '[tcomb] Invalid value "a" supplied to arguments of function (Number, Number) => Number/0: Number'); 166 | 167 | util.throwsWithMessage(function () { 168 | var sum1 = sum(1); 169 | sum1('a'); 170 | }, '[tcomb] Invalid value "a" supplied to arguments of function (Number) => Number/0: Number'); 171 | 172 | }); 173 | 174 | }); 175 | 176 | describe('uncurried', function () { 177 | 178 | it('should not curry functions', function () { 179 | var Type = t.func([t.Number, t.Number, t.Number], t.Number); 180 | var sum = Type.of(function (a, b, c) { 181 | return a + b + c; 182 | }); 183 | assert.deepEqual(sum(1, 2, 3), 6); 184 | util.throwsWithMessage(function () { 185 | sum(1, 2); 186 | }, '[tcomb] Invalid value [\n 1,\n 2\n] supplied to arguments of function (Number, Number, Number) => Number (expected an array of length 3)'); 187 | }); 188 | 189 | }); 190 | 191 | }); 192 | -------------------------------------------------------------------------------- /test/getTypeName.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | 5 | describe('t.getTypeName(type)', function () { 6 | 7 | var NamelessStruct = t.struct({}); 8 | var NamedStruct = t.struct({}, 'NamedStruct'); 9 | var NamelessUnion = t.union([t.String, t.Number]); 10 | var NamedUnion = t.union([t.String, t.Number], 'NamedUnion'); 11 | var NamelessMaybe = t.maybe(t.String); 12 | var NamedMaybe = t.maybe(t.String, 'NamedMaybe'); 13 | var NamelessEnums = t.enums({a: 'A', b: 'B'}); 14 | var NamedEnums = t.enums({}, 'NamedEnums'); 15 | var NamelessTuple = t.tuple([t.String, t.Number]); 16 | var NamedTuple = t.tuple([t.String, t.Number], 'NamedTuple'); 17 | var NamelessSubtype = t.subtype(t.String, function notEmpty(x) { return x !== ''; }); 18 | var NamedSubtype = t.subtype(t.String, function (x) { return x !== ''; }, 'NamedSubtype'); 19 | var NamelessList = t.list(t.String); 20 | var NamedList = t.list(t.String, 'NamedList'); 21 | var NamelessDict = t.dict(t.String, t.String); 22 | var NamedDict = t.dict(t.String, t.String, 'NamedDict'); 23 | var NamelessFunc = t.func(t.String, t.String); 24 | var NamedFunc = t.func(t.String, t.String, 'NamedFunc'); 25 | var NamelessIntersection = t.intersection([t.String, t.Number]); 26 | var NamedIntersection = t.intersection([t.String, t.Number], 'NamedIntersection'); 27 | var NamelessInterface = t.inter({a: t.String, b: t.Number}); 28 | var NamedInterface = t.inter({a: t.String, b: t.Number}, 'NamedInterface'); 29 | 30 | it('should return the name of a function', function () { 31 | assert.deepEqual(t.getTypeName(function myname(){}), 'myname'); 32 | }); 33 | 34 | it('should return the name of a named type', function () { 35 | assert.deepEqual(t.getTypeName(NamedStruct), 'NamedStruct'); 36 | assert.deepEqual(t.getTypeName(NamedUnion), 'NamedUnion'); 37 | assert.deepEqual(t.getTypeName(NamedMaybe), 'NamedMaybe'); 38 | assert.deepEqual(t.getTypeName(NamedEnums), 'NamedEnums'); 39 | assert.deepEqual(t.getTypeName(NamedTuple), 'NamedTuple'); 40 | assert.deepEqual(t.getTypeName(NamedSubtype), 'NamedSubtype'); 41 | assert.deepEqual(t.getTypeName(NamedList), 'NamedList'); 42 | assert.deepEqual(t.getTypeName(NamedDict), 'NamedDict'); 43 | assert.deepEqual(t.getTypeName(NamedFunc), 'NamedFunc'); 44 | assert.deepEqual(t.getTypeName(NamedIntersection), 'NamedIntersection'); 45 | assert.deepEqual(t.getTypeName(NamedInterface), 'NamedInterface'); 46 | }); 47 | 48 | it('should return a meaningful name of a Nameless type', function () { 49 | assert.deepEqual(t.getTypeName(NamelessStruct), 'Struct{}'); 50 | assert.deepEqual(t.getTypeName(NamelessUnion), 'String | Number'); 51 | assert.deepEqual(t.getTypeName(NamelessMaybe), '?String'); 52 | assert.deepEqual(t.getTypeName(NamelessEnums), '"a" | "b"'); 53 | assert.deepEqual(t.getTypeName(NamelessTuple), '[String, Number]'); 54 | assert.deepEqual(t.getTypeName(NamelessSubtype), '{String | notEmpty}'); 55 | assert.deepEqual(t.getTypeName(NamelessList), 'Array'); 56 | assert.deepEqual(t.getTypeName(NamelessDict), '{[key: String]: String}'); 57 | assert.deepEqual(t.getTypeName(NamelessFunc), '(String) => String'); 58 | assert.deepEqual(t.getTypeName(NamelessIntersection), 'String & Number'); 59 | assert.deepEqual(t.getTypeName(NamelessInterface), '{a: String, b: Number}'); 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /test/installTypeFormatter.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../'); 4 | var TypeFormatter = require('../lib/installTypeFormatter').TypeFormatter; 5 | 6 | describe('TypeFormatter', function () { 7 | 8 | it('should format irreducibles', function () { 9 | var T = t.String; 10 | var header = TypeFormatter.header(T); 11 | assert.deepEqual(header, [ 'span', 12 | [ 'span', { style: 'font-weight: bolder;' }, 'String' ], 13 | ' (irreducible)' 14 | ]); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /test/interface.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | var Point = t.struct({ 7 | x: t.Number, 8 | y: t.Number 9 | }); 10 | 11 | var PointInterface = t.inter({ 12 | x: t.Number, 13 | y: t.Number 14 | }); 15 | 16 | var ToStringable = t.inter({ 17 | toString: t.Function 18 | }); 19 | 20 | var HydrateInterface = t.inter({point: Point}); 21 | 22 | describe('t.interface(props, [name])', function () { 23 | 24 | describe('combinator', function () { 25 | 26 | it('should throw if used with wrong arguments', function () { 27 | 28 | throwsWithMessage(function () { 29 | t.inter(); 30 | }, '[tcomb] Invalid argument props undefined supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)'); 31 | 32 | throwsWithMessage(function () { 33 | t.inter({a: null}); 34 | }, '[tcomb] Invalid argument props {\n "a": null\n} supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)'); 35 | 36 | throwsWithMessage(function () { 37 | t.inter({}, 1); 38 | }, '[tcomb] Invalid argument name 1 supplied to interface(props, [options]) combinator (expected a string)'); 39 | 40 | throwsWithMessage(function () { 41 | t.inter({}, {strict: 1}); 42 | }, '[tcomb] Invalid argument strict 1 supplied to struct(props, [options]) combinator (expected a boolean)'); 43 | 44 | }); 45 | 46 | }); 47 | 48 | describe('inter.getOptions', function () { 49 | 50 | it('should handle options', function () { 51 | assert.deepEqual(t.inter.getOptions(), { strict: false }); 52 | assert.deepEqual(t.inter.getOptions({}), { strict: false }); 53 | assert.deepEqual(t.inter.getOptions('Person'), { strict: false, name: 'Person' }); 54 | assert.deepEqual(t.inter.getOptions({ strict: false }), { strict: false }); 55 | assert.deepEqual(t.inter.getOptions({ strict: true }), { strict: true }); 56 | t.inter.strict = true; 57 | assert.deepEqual(t.inter.getOptions(), { strict: true }); 58 | assert.deepEqual(t.inter.getOptions({}), { strict: true }); 59 | assert.deepEqual(t.inter.getOptions('Person'), { strict: true, name: 'Person' }); 60 | assert.deepEqual(t.inter.getOptions({ strict: false }), { strict: false }); 61 | assert.deepEqual(t.inter.getOptions({ strict: true }), { strict: true }); 62 | t.inter.strict = false; 63 | }); 64 | 65 | }); 66 | 67 | describe('constructor', function () { 68 | 69 | it('should be idempotent', function () { 70 | var p1 = PointInterface({x: 0, y: 0}); 71 | var p2 = PointInterface(p1); 72 | assert.deepEqual(Object.isFrozen(p1), true); 73 | assert.deepEqual(Object.isFrozen(p2), true); 74 | assert.deepEqual(p2 === p1, true); 75 | }); 76 | 77 | it('should accept only valid values', function () { 78 | throwsWithMessage(function () { 79 | PointInterface(1); 80 | }, '[tcomb] Invalid value undefined supplied to {x: Number, y: Number}/x: Number'); 81 | throwsWithMessage(function () { 82 | PointInterface({}); 83 | }, '[tcomb] Invalid value undefined supplied to {x: Number, y: Number}/x: Number'); 84 | throwsWithMessage(function () { 85 | PointInterface(null); 86 | }, '[tcomb] Invalid value null supplied to {x: Number, y: Number}'); 87 | throwsWithMessage(function () { 88 | PointInterface(undefined); 89 | }, '[tcomb] Invalid value undefined supplied to {x: Number, y: Number}'); 90 | }); 91 | 92 | it('should have meta.identity = true if contains a type with identity = true', function () { 93 | assert.equal(PointInterface.meta.identity, true); 94 | assert.equal(HydrateInterface.meta.identity, false); 95 | }); 96 | 97 | it('should hydrate fields', function () { 98 | var hi = HydrateInterface({ point: { x: 0, y: 1} }); 99 | assert.equal(Point.is(hi.point), true); 100 | var A = t.struct({ x: t.Number }); 101 | var B = t.inter({ a: A }); 102 | var actual = B({ 103 | a: { x: 1 }, 104 | extra: 3 105 | }); 106 | assert.equal(actual.a instanceof A, true); 107 | assert.deepEqual(actual, { 108 | a: { x: 1 }, 109 | extra: 3 110 | }); 111 | }); 112 | 113 | it('should handle strict option', function () { 114 | var Person = t.inter({ 115 | name: t.String, 116 | surname: t.maybe(t.String) 117 | }, { name: 'Person', strict: true }); 118 | 119 | assert.strictEqual(Person.meta.name, 'Person'); 120 | assert.strictEqual(Person.meta.strict, true); 121 | 122 | throwsWithMessage(function () { 123 | Person({ name: 'Giulio', age: 42 }); 124 | }, '[tcomb] Invalid additional prop "age" supplied to Person'); 125 | 126 | throwsWithMessage(function () { 127 | // simulating a typo on a maybe prop 128 | Person({ name: 'Giulio', sur: 'Canti' }); 129 | }, '[tcomb] Invalid additional prop "sur" supplied to Person'); 130 | 131 | function Input(name) { 132 | this.name = name; 133 | } 134 | Input.prototype.method = function () {}; 135 | throwsWithMessage(function () { 136 | Person(new Input('Giulio')); 137 | }, '[tcomb] Invalid additional prop "method" supplied to Person'); 138 | 139 | var InputInterface = t.inter({ 140 | name: t.String, 141 | method: t.Function 142 | }, { name: 'InputInterface', strict: true }); 143 | assert.doesNotThrow(function () { 144 | InputInterface(new Input('Giulio')); 145 | }); 146 | }); 147 | 148 | it('should handle global strict option', function () { 149 | t.inter.strict = true; 150 | var Person = t.inter({ 151 | name: t.String, 152 | surname: t.maybe(t.String) 153 | }, 'Person'); 154 | 155 | throwsWithMessage(function () { 156 | Person({ name: 'Giulio', age: 42 }); 157 | t.inter.strict = false; 158 | }, '[tcomb] Invalid additional prop "age" supplied to Person'); 159 | t.inter.strict = false; 160 | }); 161 | 162 | }); 163 | 164 | describe('#is(x)', function () { 165 | 166 | it('should return true when x is an instance of the interface', function () { 167 | assert.equal(PointInterface.is({ x: 1, y: 2 }), true); 168 | assert.equal(PointInterface.is(Point({ x: 1, y: 2 })), true); 169 | }); 170 | 171 | it('should check types', function () { 172 | assert.equal(PointInterface.is({ x: 1, y: 'a' }), false); 173 | assert.equal(PointInterface.is(1), false); 174 | assert.equal(ToStringable.is(1), true); 175 | assert.equal(ToStringable.is({}), true); 176 | assert.equal(PointInterface.is(null), false); 177 | assert.equal(PointInterface.is(undefined), false); 178 | }); 179 | 180 | it('should allow additional props', function () { 181 | assert.equal(PointInterface.is({ x: 1, y: 2, z: 3 }), true); 182 | }); 183 | 184 | it('shouldn\'t allow missing props', function () { 185 | assert.equal(PointInterface.is({ x: 1 }), false); 186 | }); 187 | 188 | it('should allow prototype methods', function () { 189 | var Serializable = t.inter({ 190 | serialize: t.Function 191 | }); 192 | Point.prototype.serialize = function () {}; 193 | assert.equal(Serializable.is(Point({ x: 1, y: 2 })), true); 194 | delete Point.prototype.serialize; 195 | }); 196 | 197 | it('should handle strict option', function () { 198 | var Person = t.inter({ 199 | name: t.String, 200 | surname: t.maybe(t.String) 201 | }, { name: 'Person', strict: true }); 202 | 203 | assert.equal(Person.is({ name: 'Giulio' }), true); 204 | assert.equal(Person.is({ name: 'Giulio', age: 42 }), false); 205 | assert.equal(Person.is({ name: 'Giulio', sur: 'Canti' }), false); 206 | 207 | function Input(name) { 208 | this.name = name; 209 | } 210 | Input.prototype.method = function () {}; 211 | assert.equal(Person.is(new Input('Giulio')), false); 212 | 213 | var InputInterface = t.inter({ 214 | name: t.String, 215 | method: t.Function 216 | }, { name: 'InputInterface', strict: true }); 217 | assert.equal(InputInterface.is(new Input('Giulio')), true); 218 | }); 219 | 220 | }); 221 | 222 | describe('#update()', function () { 223 | 224 | var instance = PointInterface({x: 0, y: 0}); 225 | 226 | it('should return a new instance', function () { 227 | var newInstance = PointInterface.update(instance, {x: {$set: 1}}); 228 | assert.ok(PointInterface.is(newInstance)); 229 | assert.deepEqual(instance.x, 0); 230 | assert.deepEqual(newInstance.x, 1); 231 | assert.deepEqual(newInstance.y, 0); 232 | }); 233 | 234 | }); 235 | 236 | }); 237 | -------------------------------------------------------------------------------- /test/intersection.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | describe('t.intersection(types, [name])', function () { 7 | 8 | var Min = t.subtype(t.String, function (s) { return s.length > 2; }, 'Min'); 9 | var Max = t.subtype(t.String, function (s) { return s.length < 5; }, 'Max'); 10 | var MinMax = t.intersection([Min, Max], 'MinMax'); 11 | 12 | describe('combinator', function () { 13 | 14 | it('should throw if used with wrong arguments', function () { 15 | 16 | util.throwsWithMessage(function () { 17 | t.intersection(); 18 | }, '[tcomb] Invalid argument types undefined supplied to intersection(types, [name]) combinator (expected an array of at least 2 types)'); 19 | 20 | util.throwsWithMessage(function () { 21 | t.intersection([]); 22 | }, '[tcomb] Invalid argument types [] supplied to intersection(types, [name]) combinator (expected an array of at least 2 types)'); 23 | 24 | util.throwsWithMessage(function () { 25 | t.intersection([1]); 26 | }, '[tcomb] Invalid argument types [\n 1\n] supplied to intersection(types, [name]) combinator (expected an array of at least 2 types)'); 27 | 28 | util.throwsWithMessage(function () { 29 | t.intersection([Min, Max], 1); 30 | }, '[tcomb] Invalid argument name 1 supplied to intersection(types, [name]) combinator (expected a string)'); 31 | 32 | }); 33 | 34 | }); 35 | 36 | describe('constructor', function () { 37 | 38 | it('should throw with a contextual error message if used with wrong arguments', function () { 39 | 40 | util.throwsWithMessage(function () { 41 | MinMax('a'); 42 | }, '[tcomb] Invalid value "a" supplied to MinMax'); 43 | 44 | util.throwsWithMessage(function () { 45 | MinMax('a', ['root']); 46 | }, '[tcomb] Invalid value "a" supplied to root'); 47 | 48 | }); 49 | 50 | }); 51 | 52 | describe('is(x)', function () { 53 | 54 | it('should return true when x is an instance of the intersection', function () { 55 | assert.strictEqual(MinMax.is('123'), true); 56 | assert.strictEqual(MinMax.is('12'), false); 57 | assert.strictEqual(MinMax.is('12345'), false); 58 | }); 59 | 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /test/irreducible.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var t = require('../index'); 3 | var throwsWithMessage = require('./util').throwsWithMessage; 4 | 5 | describe('irreducible(name, predicate)', function () { 6 | 7 | it('should throw if used with wrong arguments', function () { 8 | 9 | throwsWithMessage(function () { 10 | t.irreducible(null, function () { return true; }); 11 | }, '[tcomb] Invalid argument name null supplied to irreducible(name, predicate) (expected a string)'); 12 | 13 | throwsWithMessage(function () { 14 | t.irreducible('MyType'); 15 | }, '[tcomb] Invalid argument predicate undefined supplied to irreducible(name, predicate) (expected a function)'); 16 | 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /test/irreducibles.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | var noop = function () {}; 6 | var ko = function (x, message) { assert.strictEqual(x, false, message); }; 7 | 8 | describe('t.Any', function () { 9 | 10 | var T = t.Any; 11 | 12 | describe('constructor', function () { 13 | 14 | it('should behave like identity', function () { 15 | var value = {}; 16 | assert.strictEqual(t.Any(value), value); 17 | }); 18 | 19 | it('should throw if used with new', function () { 20 | throwsWithMessage(function () { 21 | var x = new T(); // eslint-disable-line 22 | }, '[tcomb] Cannot use the new operator to instantiate the type Any'); 23 | }); 24 | 25 | }); 26 | 27 | describe('#is(x)', function () { 28 | 29 | it('should always return true', function () { 30 | assert.ok(T.is(null)); 31 | assert.ok(T.is(undefined)); 32 | assert.ok(T.is(0)); 33 | assert.ok(T.is(true)); 34 | assert.ok(T.is('')); 35 | assert.ok(T.is([])); 36 | assert.ok(T.is({})); 37 | assert.ok(T.is(noop)); 38 | assert.ok(T.is(/a/)); 39 | assert.ok(T.is(new RegExp('a'))); 40 | assert.ok(T.is(new Error())); 41 | }); 42 | 43 | }); 44 | 45 | }); 46 | 47 | // 48 | // irreducible types 49 | // 50 | 51 | describe('irreducibles types', function () { 52 | 53 | [ 54 | {T: t.Nil, x: null}, 55 | {T: t.String, x: 'a'}, 56 | {T: t.Number, x: 1}, 57 | {T: t.Boolean, x: true}, 58 | {T: t.Array, x: []}, 59 | {T: t.Object, x: {}}, 60 | {T: t.Function, x: noop}, 61 | {T: t.Error, x: new Error()}, 62 | {T: t.RegExp, x: /a/}, 63 | {T: t.Date, x: new Date()} 64 | ].forEach(function (o) { 65 | 66 | var T = o.T; 67 | var x = o.x; 68 | 69 | it('should accept only valid values', function () { 70 | assert.deepEqual(T(x), x); 71 | }); 72 | 73 | it('should throw if used with new', function () { 74 | throwsWithMessage(function () { 75 | var x = new T(); // eslint-disable-line 76 | }, '[tcomb] Cannot use the new operator to instantiate the type ' + t.getTypeName(T)); 77 | }); 78 | 79 | }); 80 | 81 | }); 82 | 83 | describe('t.Nil', function () { 84 | 85 | describe('#is(x)', function () { 86 | 87 | it('should return true when x is null or undefined', function () { 88 | assert.ok(t.Nil.is(null)); 89 | assert.ok(t.Nil.is(undefined)); 90 | }); 91 | 92 | it('should return false when x is neither null nor undefined', function () { 93 | ko(t.Nil.is(0)); 94 | ko(t.Nil.is(true)); 95 | ko(t.Nil.is('')); 96 | ko(t.Nil.is([])); 97 | ko(t.Nil.is({})); 98 | ko(t.Nil.is(noop)); 99 | ko(t.Nil.is(new Error())); 100 | ko(t.Nil.is(new Date())); 101 | ko(t.Nil.is(/a/)); 102 | ko(t.Nil.is(new RegExp('a'))); 103 | }); 104 | 105 | }); 106 | 107 | }); 108 | 109 | describe('t.Boolean', function () { 110 | 111 | describe('#is(x)', function () { 112 | 113 | it('should return true when x is true or false', function () { 114 | assert.ok(t.Boolean.is(true)); 115 | assert.ok(t.Boolean.is(false)); 116 | }); 117 | 118 | it('should return false when x is neither true nor false', function () { 119 | ko(t.Boolean.is(null)); 120 | ko(t.Boolean.is(undefined)); 121 | ko(t.Boolean.is(0)); 122 | ko(t.Boolean.is('')); 123 | ko(t.Boolean.is([])); 124 | ko(t.Boolean.is({})); 125 | ko(t.Boolean.is(noop)); 126 | ko(t.Boolean.is(/a/)); 127 | ko(t.Boolean.is(new RegExp('a'))); 128 | ko(t.Boolean.is(new Error())); 129 | ko(t.Boolean.is(new Date())); 130 | }); 131 | 132 | }); 133 | 134 | }); 135 | 136 | describe('t.Number', function () { 137 | 138 | describe('#is(x)', function () { 139 | 140 | it('should return true when x is a number', function () { 141 | assert.ok(t.Number.is(0)); 142 | assert.ok(t.Number.is(1)); 143 | ko(t.Number.is(new Number(1))); // eslint-disable-line 144 | }); 145 | 146 | it('should return false when x is not a number', function () { 147 | ko(t.Number.is(NaN)); 148 | ko(t.Number.is(Infinity)); 149 | ko(t.Number.is(-Infinity)); 150 | ko(t.Number.is(null)); 151 | ko(t.Number.is(undefined)); 152 | ko(t.Number.is(true)); 153 | ko(t.Number.is('')); 154 | ko(t.Number.is([])); 155 | ko(t.Number.is({})); 156 | ko(t.Number.is(noop)); 157 | ko(t.Number.is(/a/)); 158 | ko(t.Number.is(new RegExp('a'))); 159 | ko(t.Number.is(new Error())); 160 | ko(t.Number.is(new Date())); 161 | }); 162 | 163 | }); 164 | 165 | }); 166 | 167 | describe('t.String', function () { 168 | 169 | describe('#is(x)', function () { 170 | 171 | it('should return true when x is a string', function () { 172 | assert.ok(t.String.is('')); 173 | assert.ok(t.String.is('a')); 174 | /* jshint ignore:start */ 175 | ko(t.String.is(new String('a'))); // eslint-disable-line 176 | /* jshint ignore:end */ 177 | }); 178 | 179 | it('should return false when x is not a string', function () { 180 | ko(t.String.is(NaN)); 181 | ko(t.String.is(Infinity)); 182 | ko(t.String.is(-Infinity)); 183 | ko(t.String.is(null)); 184 | ko(t.String.is(undefined)); 185 | ko(t.String.is(true)); 186 | ko(t.String.is(1)); 187 | ko(t.String.is([])); 188 | ko(t.String.is({})); 189 | ko(t.String.is(noop)); 190 | ko(t.String.is(/a/)); 191 | ko(t.String.is(new RegExp('a'))); 192 | ko(t.String.is(new Error())); 193 | ko(t.String.is(new Date())); 194 | }); 195 | 196 | }); 197 | 198 | }); 199 | 200 | describe('t.Array', function () { 201 | 202 | describe('#is(x)', function () { 203 | 204 | it('should return true when x is an array', function () { 205 | assert.ok(t.Array.is([])); 206 | }); 207 | 208 | it('should return false when x is not an array', function () { 209 | ko(t.Array.is(NaN)); 210 | ko(t.Array.is(Infinity)); 211 | ko(t.Array.is(-Infinity)); 212 | ko(t.Array.is(null)); 213 | ko(t.Array.is(undefined)); 214 | ko(t.Array.is(true)); 215 | ko(t.Array.is(1)); 216 | ko(t.Array.is('a')); 217 | ko(t.Array.is({})); 218 | ko(t.Array.is(noop)); 219 | ko(t.Array.is(/a/)); 220 | ko(t.Array.is(new RegExp('a'))); 221 | ko(t.Array.is(new Error())); 222 | ko(t.Array.is(new Date())); 223 | }); 224 | 225 | }); 226 | 227 | }); 228 | 229 | describe('t.Object', function () { 230 | 231 | describe('#is(x)', function () { 232 | 233 | it('should return true when x is an object', function () { 234 | function A() {} 235 | assert.ok(t.Object.is({})); 236 | assert.ok(t.Object.is(new A())); 237 | }); 238 | 239 | it('should return false when x is not an object', function () { 240 | ko(t.Object.is(null)); 241 | ko(t.Object.is(undefined)); 242 | ko(t.Object.is(0)); 243 | ko(t.Object.is('')); 244 | ko(t.Object.is([])); 245 | ko(t.Object.is(noop)); 246 | }); 247 | 248 | }); 249 | 250 | }); 251 | 252 | describe('t.Function', function () { 253 | 254 | describe('#is(x)', function () { 255 | 256 | it('should return true when x is a function', function () { 257 | assert.ok(t.Function.is(noop)); 258 | assert.ok(t.Function.is(new Function())); // eslint-disable-line 259 | }); 260 | 261 | it('should return false when x is not a function', function () { 262 | ko(t.Function.is(null)); 263 | ko(t.Function.is(undefined)); 264 | ko(t.Function.is(0)); 265 | ko(t.Function.is('')); 266 | ko(t.Function.is([])); 267 | ko(t.Function.is({})); 268 | ko(t.Function.is(new String('1'))); // eslint-disable-line 269 | ko(t.Function.is(new Number(1))); // eslint-disable-line 270 | ko(t.Function.is(new Boolean())); // eslint-disable-line 271 | ko(t.Function.is(/a/)); 272 | ko(t.Function.is(new RegExp('a'))); 273 | ko(t.Function.is(new Error())); 274 | ko(t.Function.is(new Date())); 275 | }); 276 | 277 | }); 278 | 279 | }); 280 | 281 | describe('t.Error', function () { 282 | 283 | describe('#is(x)', function () { 284 | 285 | it('should return true when x is an error', function () { 286 | assert.ok(t.Error.is(new Error())); 287 | }); 288 | 289 | it('should return false when x is not an error', function () { 290 | ko(t.Error.is(null)); 291 | ko(t.Error.is(undefined)); 292 | ko(t.Error.is(0)); 293 | ko(t.Error.is('')); 294 | ko(t.Error.is([])); 295 | ko(t.Error.is(new String('1'))); // eslint-disable-line 296 | ko(t.Error.is(new Number(1))); // eslint-disable-line 297 | ko(t.Error.is(new Boolean())); // eslint-disable-line 298 | ko(t.Error.is(/a/)); 299 | ko(t.Error.is(new RegExp('a'))); 300 | ko(t.Error.is(new Date())); 301 | }); 302 | 303 | }); 304 | 305 | }); 306 | 307 | describe('t.RegExp', function () { 308 | 309 | describe('#is(x)', function () { 310 | 311 | it('should return true when x is a regexp', function () { 312 | assert.ok(t.RegExp.is(/a/)); 313 | assert.ok(t.RegExp.is(new RegExp('a'))); 314 | }); 315 | 316 | it('should return false when x is not a regexp', function () { 317 | ko(t.RegExp.is(null)); 318 | ko(t.RegExp.is(undefined)); 319 | ko(t.RegExp.is(0)); 320 | ko(t.RegExp.is('')); 321 | ko(t.RegExp.is([])); 322 | ko(t.RegExp.is(new String('1'))); // eslint-disable-line 323 | ko(t.RegExp.is(new Number(1))); // eslint-disable-line 324 | ko(t.RegExp.is(new Boolean())); // eslint-disable-line 325 | ko(t.RegExp.is(new Error())); 326 | ko(t.RegExp.is(new Date())); 327 | }); 328 | 329 | }); 330 | 331 | }); 332 | 333 | describe('t.Date', function () { 334 | 335 | describe('#is(x)', function () { 336 | 337 | it('should return true when x is a Dat', function () { 338 | assert.ok(t.Date.is(new Date())); 339 | }); 340 | 341 | it('should return false when x is not a Dat', function () { 342 | ko(t.Date.is(null)); 343 | ko(t.Date.is(undefined)); 344 | ko(t.Date.is(0)); 345 | ko(t.Date.is('')); 346 | ko(t.Date.is([])); 347 | ko(t.Date.is(new String('1'))); // eslint-disable-line 348 | ko(t.Date.is(new Number(1))); // eslint-disable-line 349 | ko(t.Date.is(new Boolean())); // eslint-disable-line 350 | ko(t.Date.is(new Error())); 351 | ko(t.Date.is(/a/)); 352 | ko(t.Date.is(new RegExp('a'))); 353 | }); 354 | 355 | }); 356 | 357 | }); 358 | 359 | describe('t.Type', function () { 360 | 361 | describe('#is(x)', function () { 362 | 363 | it('should return true if x is a tcomb type', function () { 364 | assert.equal(t.Type.is(t.String), true); 365 | assert.equal(t.Type.is(1), false); 366 | }); 367 | 368 | }); 369 | 370 | }); -------------------------------------------------------------------------------- /test/list.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var vm = require('vm'); 4 | var t = require('../index'); 5 | var util = require('./util'); 6 | 7 | var Point = t.struct({ 8 | x: t.Number, 9 | y: t.Number 10 | }); 11 | 12 | describe('t.list(type, [name])', function () { 13 | 14 | describe('combinator', function () { 15 | 16 | it('should throw if used with wrong arguments', function () { 17 | 18 | util.throwsWithMessage(function () { 19 | t.list(); 20 | }, '[tcomb] Invalid argument type undefined supplied to list(type, [name]) combinator (expected a type)'); 21 | 22 | util.throwsWithMessage(function () { 23 | t.list(Point, 1); 24 | }, '[tcomb] Invalid argument name 1 supplied to list(type, [name]) combinator (expected a string)'); 25 | 26 | }); 27 | 28 | }); 29 | 30 | describe('constructor', function () { 31 | 32 | var MyElement = t.struct({}, 'MyElement'); 33 | var MyList = t.list(MyElement, 'MyList'); 34 | var ListOfNumbers = t.list(t.Number, 'ListOfNumbers'); 35 | var Path = t.list(Point, 'Path'); 36 | 37 | it('should throw with a contextual error message if used with wrong arguments', function () { 38 | 39 | util.throwsWithMessage(function () { 40 | ListOfNumbers(); 41 | }, '[tcomb] Invalid value undefined supplied to ListOfNumbers (expected an array of Number)'); 42 | 43 | util.throwsWithMessage(function () { 44 | ListOfNumbers(['a']); 45 | }, '[tcomb] Invalid value "a" supplied to ListOfNumbers/0: Number'); 46 | 47 | util.throwsWithMessage(function () { 48 | ListOfNumbers(1, ['root']); 49 | }, '[tcomb] Invalid value 1 supplied to root (expected an array of Number)'); 50 | 51 | }); 52 | 53 | it('should hydrate the elements of the list', function () { 54 | var instance = MyList([{}]); 55 | assert.equal(MyElement.is(instance[0]), true); 56 | }); 57 | 58 | it('should hydrate the elements of the list in production', util.production(function () { 59 | var instance = MyList([{}]); 60 | assert.equal(MyElement.is(instance[0]), true); 61 | })); 62 | 63 | it('should be idempotent', function () { 64 | var numbers0 = [1, 2]; 65 | var numbers1 = ListOfNumbers(numbers0); 66 | var numbers2 = ListOfNumbers(numbers1); 67 | assert.equal(numbers0 === numbers1, true); 68 | assert.equal(numbers1 === numbers2, true); 69 | 70 | var path0 = [{x: 0, y: 0}, {x: 1, y: 1}]; 71 | var path1 = Path(path0); 72 | var path2 = Path(path1); 73 | assert.equal(path0 === path1, false); 74 | assert.equal(path1 === path2, true); 75 | }); 76 | 77 | it('should be idempotent in production', util.production(function () { 78 | var numbers0 = [1, 2]; 79 | var numbers1 = ListOfNumbers(numbers0); 80 | var numbers2 = ListOfNumbers(numbers1); 81 | assert.equal(numbers0 === numbers1, true); 82 | assert.equal(numbers1 === numbers2, true); 83 | 84 | var path0 = [{x: 0, y: 0}, {x: 1, y: 1}]; 85 | var path1 = Path(path0); 86 | var path2 = Path(path1); 87 | assert.equal(path0 === path1, false); 88 | assert.equal(path1 === path2, true); 89 | })); 90 | 91 | it('should freeze the instance', function () { 92 | var instance = ListOfNumbers([1, 2]); 93 | assert.equal(Object.isFrozen(instance), true); 94 | }); 95 | 96 | it('should not freeze the instance in production', util.production(function () { 97 | var instance = ListOfNumbers([1, 2]); 98 | assert.equal(Object.isFrozen(instance), false); 99 | })); 100 | 101 | }); 102 | 103 | describe('is(x)', function () { 104 | 105 | var Path = t.list(Point); 106 | var p1 = new Point({x: 0, y: 0}); 107 | var p2 = new Point({x: 1, y: 1}); 108 | 109 | it('should return true when x is a list of type instances', function () { 110 | assert.equal(Path.is([]), true); 111 | assert.equal(Path.is([p1, p2]), true); 112 | assert.equal(Path.is(1), false); 113 | assert.equal(Path.is([1]), false); 114 | }); 115 | 116 | it('should return true when x is a list of type instances returned from vm', function () { 117 | assert.equal(Path.is(vm.runInNewContext('[]', { Array: Array })), true); 118 | assert.equal(Path.is(vm.runInNewContext('[p1, p2]', { Array: Array, p1: p1, p2: p2 })), true); 119 | assert.equal(Path.is(vm.runInNewContext('1', { Array: Array })), false); 120 | assert.equal(Path.is(vm.runInNewContext('[1]', { Array: Array })), false); 121 | }); 122 | 123 | it('should be used as a predicate', function () { 124 | assert.equal([[p1, p2]].every(Path.is), true); 125 | }); 126 | 127 | }); 128 | 129 | describe('update(instance, patch)', function () { 130 | 131 | it('should return a new instance', function () { 132 | var ListOfStrings = t.list(t.String); 133 | var instance = ['a', 'b']; 134 | var newInstance = ListOfStrings.update(instance, {'$push': ['c']}); 135 | assert.equal(newInstance !== instance, true); 136 | assert.deepEqual(newInstance, ['a', 'b', 'c']); 137 | }); 138 | 139 | }); 140 | 141 | }); 142 | -------------------------------------------------------------------------------- /test/match.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | describe('match', function () { 7 | 8 | it('should match on type constructors', function () { 9 | assert.deepEqual(t.match(1, 10 | t.String, function () { return 'a string'; }, 11 | t.Number, function (n) { return 2 * n; } 12 | ), 2); 13 | }); 14 | 15 | it('should handle an optional guard', function () { 16 | assert.deepEqual(t.match(1, 17 | t.String, function () { return 'a string'; }, 18 | t.Number, function () { return false; }, function (n) { return 2 * n; }, 19 | t.Number, function (n) { return 3 * n; } 20 | ), 3); 21 | }); 22 | 23 | it('should throw if no match is found', function () { 24 | throwsWithMessage(function () { 25 | t.match(true, 26 | t.String, function () { return 'a string'; }, 27 | t.Number, function (n) { return 2 * n; } 28 | ); 29 | }, '[tcomb] Match error'); 30 | }); 31 | 32 | it('should throw if cases are misplaced', function () { 33 | throwsWithMessage(function () { 34 | t.match(true, 35 | t.String, function () { return 'a string'; }, 36 | t.Number 37 | ); 38 | }, '[tcomb] Invalid block in clause #2'); 39 | }); 40 | 41 | it('should handle unions of unions', function () { 42 | var A = t.subtype(t.String, function (s) { return s === 'A'; }); 43 | var B = t.subtype(t.String, function (s) { return s === 'B'; }); 44 | var C = t.subtype(t.String, function (s) { return s === 'C'; }); 45 | var D = t.subtype(t.String, function (s) { return s === 'D'; }); 46 | var E = t.subtype(t.String, function (s) { return s === 'E'; }); 47 | var F = t.subtype(t.String, function (s) { return s === 'F'; }); 48 | var G = t.subtype(t.String, function (s) { return s === 'G'; }); 49 | var H = t.subtype(t.String, function (s) { return s === 'H'; }); 50 | var U1 = t.union([A, B]); 51 | var U2 = t.union([C, D]); 52 | var U3 = t.union([E, F]); 53 | var U4 = t.union([G, H]); 54 | var UU1 = t.union([U1, U2]); 55 | var UU2 = t.union([U3, U4]); 56 | assert.deepEqual(t.match('F', 57 | UU1, function () { return '1'; }, 58 | UU2, function () { return '2'; } 59 | ), '2'); 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /test/maybe.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | var Point = t.struct({ 7 | x: t.Number, 8 | y: t.Number 9 | }); 10 | 11 | describe('t.maybe(type, [name])', function () { 12 | 13 | describe('combinator', function () { 14 | 15 | it('should throw if used with wrong arguments', function () { 16 | 17 | util.throwsWithMessage(function () { 18 | t.maybe(); 19 | }, '[tcomb] Invalid argument type undefined supplied to maybe(type, [name]) combinator (expected a type)'); 20 | 21 | util.throwsWithMessage(function () { 22 | t.maybe(Point, 1); 23 | }, '[tcomb] Invalid argument name 1 supplied to maybe(type, [name]) combinator (expected a string)'); 24 | 25 | }); 26 | 27 | it('should be idempotent', function () { 28 | var MaybeStr = t.maybe(t.String); 29 | assert.ok(t.maybe(MaybeStr) === MaybeStr); 30 | }); 31 | 32 | it('should be idempotent in production', util.production(function () { 33 | var MaybeStr = t.maybe(t.String); 34 | assert.ok(t.maybe(MaybeStr) === MaybeStr); 35 | })); 36 | 37 | it('should be noop with Any', function () { 38 | assert.ok(t.maybe(t.Any) === t.Any); 39 | }); 40 | 41 | it('should be noop with Nil', function () { 42 | assert.ok(t.maybe(t.Nil) === t.Nil); 43 | }); 44 | 45 | }); 46 | 47 | describe('constructor', function () { 48 | 49 | it('should throw if used with new', function () { 50 | util.throwsWithMessage(function () { 51 | var T = t.maybe(t.String, 'T'); 52 | var x = new T(); // eslint-disable-line 53 | }, '[tcomb] Cannot use the new operator to instantiate the type T'); 54 | }); 55 | 56 | it('should hydrate the elements of the maybe', function () { 57 | var T = t.maybe(Point); 58 | assert.strictEqual(T(null), null); 59 | assert.strictEqual(T(undefined), undefined); 60 | assert.ok(Point.is(T({x: 0, y: 0}))); 61 | }); 62 | 63 | it('should hydrate the elements of the maybe in production', util.production(function () { 64 | var T = t.maybe(Point); 65 | assert.deepEqual(T(null), null); 66 | assert.ok(typeof T(undefined) === 'undefined'); 67 | assert.ok(Point.is(T({x: 0, y: 0}))); 68 | })); 69 | 70 | it('should be idempotent', function () { 71 | var T = t.maybe(Point); 72 | var p0 = {x: 0, y: 0}; 73 | var p1 = T(); 74 | var p2 = T(p1); 75 | assert.equal(p0 === p1, false); 76 | assert.equal(p1 === p2, true); 77 | }); 78 | 79 | it('should be idempotent in production', util.production(function () { 80 | var T = t.maybe(Point); 81 | var p0 = {x: 0, y: 0}; 82 | var p1 = T(); 83 | var p2 = T(p1); 84 | assert.equal(p0 === p1, false); 85 | assert.equal(p1 === p2, true); 86 | })); 87 | 88 | it('should be idempotent on Nil values', function () { 89 | var T = t.maybe(t.String, 'T'); 90 | assert.deepEqual(T(null), null); 91 | assert.ok(typeof T(undefined) === 'undefined'); 92 | }); 93 | 94 | it('should play well with JSON.stringify (#227)', function () { 95 | var thing = t.struct({ foo: t.maybe(t.struct({ bar: t.String })) }); 96 | assert.strictEqual(JSON.stringify(thing({foo: null})), '{"foo":null}'); 97 | assert.strictEqual(JSON.stringify(thing({foo: undefined})), '{}'); 98 | }); 99 | 100 | }); 101 | 102 | describe('is(x)', function () { 103 | 104 | it('should return true when x is an instance of the maybe', function () { 105 | var Radio = t.maybe(t.String); 106 | assert.strictEqual(Radio.is('a'), true); 107 | assert.strictEqual(Radio.is(null), true); 108 | assert.strictEqual(Radio.is(undefined), true); 109 | }); 110 | 111 | }); 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /test/mixin.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | describe('t.mixin(x, y, [overwrite])', function () { 7 | 8 | it('should mix two objects', function () { 9 | var o1 = {a: 1}; 10 | var o2 = {b: 2}; 11 | var o3 = t.mixin(o1, o2); 12 | assert.strictEqual(o3, o1); 13 | assert.deepEqual(o3.a, 1); 14 | assert.deepEqual(o3.b, 2); 15 | }); 16 | 17 | it('should throw if a property already exists', function () { 18 | throwsWithMessage(function () { 19 | var o1 = {a: 1}; 20 | var o2 = {a: 2, b: 2}; 21 | t.mixin(o1, o2); 22 | }, '[tcomb] Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "a" of target object'); 23 | }); 24 | 25 | it('should not throw if a property already exists but overwrite = true', function () { 26 | var o1 = {a: 1}; 27 | var o2 = {a: 2, b: 2}; 28 | var o3 = t.mixin(o1, o2, true); 29 | assert.deepEqual(o3.a, 2); 30 | assert.deepEqual(o3.b, 2); 31 | }); 32 | 33 | it('should not mix prototype properties', function () { 34 | function F() {} 35 | F.prototype.method = function () {}; 36 | var source = new F(); 37 | var target = {}; 38 | t.mixin(target, source); 39 | assert.deepEqual(target.method, undefined); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/refinement.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | var Point = t.struct({ 7 | x: t.Number, 8 | y: t.Number 9 | }); 10 | 11 | describe('t.refinement(type, predicate, [name])', function () { 12 | 13 | var True = function () { return true; }; 14 | 15 | describe('combinator', function () { 16 | 17 | it('should throw if used with wrong arguments', function () { 18 | 19 | util.throwsWithMessage(function () { 20 | t.refinement(); 21 | }, '[tcomb] Invalid argument type undefined supplied to refinement(type, predicate, [name]) combinator (expected a type)'); 22 | 23 | util.throwsWithMessage(function () { 24 | t.refinement(Point, null); 25 | }, '[tcomb] Invalid argument predicate supplied to refinement(type, predicate, [name]) combinator (expected a function)'); 26 | 27 | util.throwsWithMessage(function () { 28 | t.refinement(Point, True, 1); 29 | }, '[tcomb] Invalid argument name 1 supplied to refinement(type, predicate, [name]) combinator (expected a string)'); 30 | 31 | }); 32 | 33 | }); 34 | 35 | describe('constructor', function () { 36 | 37 | it('should throw if used with new and a type that is not instantiable with new', function () { 38 | util.throwsWithMessage(function () { 39 | var T = t.refinement(t.String, function () { return true; }, 'T'); 40 | var x = new T(); // eslint-disable-line 41 | }, '[tcomb] Cannot use the new operator to instantiate the type T'); 42 | }); 43 | 44 | it('should throw with a contextual error message if used with wrong arguments', function () { 45 | var predicate = function (p) { return p.x > 0; }; 46 | var T = t.refinement(Point, predicate, 'T'); 47 | util.throwsWithMessage(function () { 48 | T({x: 0, y: 0}); 49 | }, '[tcomb] Invalid value {\n "x": 0,\n "y": 0\n} supplied to T'); 50 | }); 51 | 52 | it('should hydrate the elements of the refinement', function () { 53 | var T = t.refinement(Point, function () { return true; }); 54 | var p = T({x: 0, y: 0}); 55 | assert.ok(Point.is(p)); 56 | }); 57 | 58 | it('should hydrate the elements of the refinement in production', util.production(function () { 59 | var T = t.refinement(Point, function () { return true; }); 60 | var p = T({x: 0, y: 0}); 61 | assert.ok(Point.is(p)); 62 | })); 63 | 64 | it('should be idempotent', function () { 65 | var Supertype = t.dict(t.String, t.Number); 66 | var Refinement = t.refinement(Supertype, function () { return true; }); 67 | var t0 = {}; 68 | var t1 = Refinement(t0); 69 | var t2 = Refinement(t1); 70 | assert.equal(t0 === t1, true); 71 | assert.equal(t1 === t2, true); 72 | }); 73 | 74 | it('should be idempotent in production', util.production(function () { 75 | var Supertype = t.dict(t.String, t.Number); 76 | var Refinement = t.refinement(Supertype, function () { return true; }); 77 | var t0 = {}; 78 | var t1 = Refinement(t0); 79 | var t2 = Refinement(t1); 80 | assert.equal(t0 === t1, true); 81 | assert.equal(t1 === t2, true); 82 | })); 83 | 84 | }); 85 | 86 | describe('is(x)', function () { 87 | 88 | var Positive = t.refinement(t.Number, function (n) { 89 | return n >= 0; 90 | }); 91 | 92 | it('should return true when x is a refinement', function () { 93 | assert.ok(Positive.is(1)); 94 | }); 95 | 96 | it('should return false when x is not a refinement', function () { 97 | assert.strictEqual(Positive.is(-1), false); 98 | }); 99 | 100 | }); 101 | 102 | describe('update(instance, patch)', function () { 103 | 104 | var Type = t.refinement(t.String, function (s) { return s.length > 2; }); 105 | var instance = Type('abc'); 106 | 107 | it('should return a new instance', function () { 108 | var newInstance = Type.update(instance, {'$set': 'bca'}); 109 | assert(Type.is(newInstance)); 110 | assert.deepEqual(newInstance, 'bca'); 111 | }); 112 | 113 | }); 114 | 115 | describe('Integer type', function () { 116 | 117 | it('should type check integers', function () { 118 | assert.equal(t.Integer.is(0), true); 119 | assert.equal(t.Integer.is(-0), true); 120 | assert.equal(t.Integer.is(0.0), true); 121 | assert.equal(t.Integer.is(10), true); 122 | assert.equal(t.Integer.is(-10), true); 123 | assert.equal(t.Integer.is(0.5), false); 124 | assert.equal(t.Integer.is(-0.5), false); 125 | }); 126 | 127 | }); 128 | 129 | }); 130 | -------------------------------------------------------------------------------- /test/struct.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var throwsWithMessage = require('./util').throwsWithMessage; 5 | 6 | var Point = t.struct({ 7 | x: t.Number, 8 | y: t.Number 9 | }); 10 | 11 | describe('t.struct(props, [name])', function () { 12 | 13 | describe('combinator', function () { 14 | 15 | it('should throw if used with wrong arguments', function () { 16 | 17 | throwsWithMessage(function () { 18 | t.struct(); 19 | }, '[tcomb] Invalid argument props undefined supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)'); 20 | 21 | throwsWithMessage(function () { 22 | t.struct({a: null}); 23 | }, '[tcomb] Invalid argument props {\n "a": null\n} supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)'); 24 | 25 | throwsWithMessage(function () { 26 | t.struct({}, 1); 27 | }, '[tcomb] Invalid argument name 1 supplied to struct(props, [options]) combinator (expected a string)'); 28 | 29 | throwsWithMessage(function () { 30 | t.struct({}, {strict: 1}); 31 | }, '[tcomb] Invalid argument strict 1 supplied to struct(props, [options]) combinator (expected a boolean)'); 32 | 33 | }); 34 | 35 | }); 36 | 37 | describe('struct.getOptions', function () { 38 | 39 | it('should handle options', function () { 40 | assert.deepEqual(t.struct.getOptions(), { strict: false, defaultProps: {} }); 41 | assert.deepEqual(t.struct.getOptions({}), { strict: false, defaultProps: {} }); 42 | assert.deepEqual(t.struct.getOptions('Person'), { strict: false, name: 'Person', defaultProps: {} }); 43 | assert.deepEqual(t.struct.getOptions({ strict: false }), { strict: false, defaultProps: {} }); 44 | assert.deepEqual(t.struct.getOptions({ strict: true }), { strict: true, defaultProps: {} }); 45 | t.struct.strict = true; 46 | assert.deepEqual(t.struct.getOptions(), { strict: true, defaultProps: {} }); 47 | assert.deepEqual(t.struct.getOptions({}), { strict: true, defaultProps: {} }); 48 | assert.deepEqual(t.struct.getOptions('Person'), { strict: true, name: 'Person', defaultProps: {} }); 49 | assert.deepEqual(t.struct.getOptions({ strict: false }), { strict: false, defaultProps: {} }); 50 | assert.deepEqual(t.struct.getOptions({ strict: true }), { strict: true, defaultProps: {} }); 51 | t.struct.strict = false; 52 | }); 53 | 54 | }); 55 | 56 | describe('constructor', function () { 57 | 58 | it('should be idempotent', function () { 59 | var T = Point; 60 | var p1 = T({x: 0, y: 0}); 61 | var p2 = T(p1); 62 | assert.deepEqual(Object.isFrozen(p1), true); 63 | assert.deepEqual(Object.isFrozen(p2), true); 64 | assert.deepEqual(p2 === p1, true); 65 | }); 66 | 67 | it('should accept only valid values', function () { 68 | throwsWithMessage(function () { 69 | Point(1); 70 | }, '[tcomb] Invalid value 1 supplied to Struct{x: Number, y: Number} (expected an object)'); 71 | throwsWithMessage(function () { 72 | Point({}); 73 | }, '[tcomb] Invalid value undefined supplied to Struct{x: Number, y: Number}/x: Number'); 74 | }); 75 | 76 | it('should handle strict option', function () { 77 | var Person = t.struct({ 78 | name: t.String, 79 | surname: t.maybe(t.String) 80 | }, { name: 'Person', strict: true }); 81 | 82 | assert.strictEqual(Person.meta.name, 'Person'); 83 | assert.strictEqual(Person.meta.strict, true); 84 | 85 | throwsWithMessage(function () { 86 | new Person({ name: 'Giulio', age: 42 }); 87 | }, '[tcomb] Invalid additional prop "age" supplied to Person'); 88 | 89 | throwsWithMessage(function () { 90 | // simulating a typo on a maybe prop 91 | new Person({ name: 'Giulio', sur: 'Canti' }); 92 | }, '[tcomb] Invalid additional prop "sur" supplied to Person'); 93 | 94 | function Input(name) { 95 | this.name = name; 96 | } 97 | Input.prototype.method = function () {}; 98 | assert.doesNotThrow(function () { 99 | new Person(new Input('Giulio')); 100 | }); 101 | }); 102 | 103 | it('should handle global strict option', function () { 104 | t.struct.strict = true; 105 | var Person = t.struct({ 106 | name: t.String, 107 | surname: t.maybe(t.String) 108 | }, 'Person'); 109 | 110 | throwsWithMessage(function () { 111 | new Person({ name: 'Giulio', age: 42 }); 112 | t.struct.strict = false; 113 | }, '[tcomb] Invalid additional prop "age" supplied to Person'); 114 | t.struct.strict = false; 115 | }); 116 | 117 | it('how to handle unions of structs when global strict is true', function () { 118 | var T1 = t.struct({}, { strict: false }); 119 | var T2 = t.struct({}, { strict: false }); 120 | var U = t.union([T1, T2]); 121 | U.dispatch = function (x) { 122 | switch (x.type) { 123 | case '1': return T1; 124 | case '2': return T2; 125 | } 126 | }; 127 | var x = U({type: '2'}); 128 | assert.strictEqual(x instanceof T2, true); 129 | }); 130 | 131 | }); 132 | 133 | describe('#is(x)', function () { 134 | 135 | it('should return true when x is an instance of the struct', function () { 136 | var p = new Point({ x: 1, y: 2 }); 137 | assert.ok(Point.is(p)); 138 | }); 139 | 140 | }); 141 | 142 | describe('#update()', function () { 143 | 144 | var Type = t.struct({name: t.String}); 145 | var instance = new Type({name: 'Giulio'}); 146 | 147 | it('should return a new instance', function () { 148 | var newInstance = Type.update(instance, {name: {$set: 'Canti'}}); 149 | assert.ok(Type.is(newInstance)); 150 | assert.deepEqual(instance.name, 'Giulio'); 151 | assert.deepEqual(newInstance.name, 'Canti'); 152 | }); 153 | 154 | }); 155 | 156 | describe('default props', function () { 157 | 158 | it('should throw if used with wrong arguments', function () { 159 | throwsWithMessage(function () { 160 | t.struct({}, { defaultProps: 1 }); 161 | }, '[tcomb] Invalid argument defaultProps 1 supplied to struct(props, [options]) combinator (expected an object)'); 162 | }); 163 | 164 | it('should handle the defaultProps option', function () { 165 | var T = t.struct({ 166 | name: t.String, 167 | surname: t.String 168 | }, { defaultProps: { surname: 'Canti' } }); 169 | assert.doesNotThrow(function () { 170 | T({ name: 'Giulio' }); 171 | }); 172 | }); 173 | 174 | it('should apply defaults if a props is undefined', function () { 175 | var T = t.struct({ 176 | name: t.maybe(t.String) 177 | }, { defaultProps: { name: 'Giulio' } }); 178 | assert.strictEqual(T({}).name, 'Giulio'); 179 | assert.strictEqual(T({ name: undefined }).name, 'Giulio'); 180 | assert.strictEqual(T({ name: null }).name, null); 181 | }); 182 | 183 | it('should apply the default after an update', function () { 184 | var T = t.struct({ 185 | name: t.String, 186 | surname: t.String 187 | }, { defaultProps: { surname: 'Canti' } }); 188 | var p1 = T({ name: 'Giulio' }); 189 | var p2 = T.update(p1, { 190 | surname: { $set: undefined } 191 | }); 192 | assert.strictEqual(p2.name, 'Giulio'); 193 | }); 194 | 195 | }); 196 | 197 | }); 198 | -------------------------------------------------------------------------------- /test/tuple.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | var Point = t.struct({ 7 | x: t.Number, 8 | y: t.Number 9 | }); 10 | 11 | describe('t.tuple(types, [name])', function () { 12 | 13 | var Area = t.tuple([t.Number, t.Number], 'Area'); 14 | 15 | describe('combinator', function () { 16 | 17 | it('should throw if used with wrong arguments', function () { 18 | 19 | util.throwsWithMessage(function () { 20 | t.tuple(); 21 | }, '[tcomb] Invalid argument types undefined supplied to tuple(types, [name]) combinator (expected an array of types)'); 22 | 23 | util.throwsWithMessage(function () { 24 | t.tuple([1]); 25 | }, '[tcomb] Invalid argument types [\n 1\n] supplied to tuple(types, [name]) combinator (expected an array of types)'); 26 | 27 | util.throwsWithMessage(function () { 28 | t.tuple([Point, Point], 1); 29 | }, '[tcomb] Invalid argument name 1 supplied to tuple(types, [name]) combinator (expected a string)'); 30 | 31 | }); 32 | 33 | }); 34 | 35 | describe('constructor', function () { 36 | 37 | var S = t.struct({}, 'S'); 38 | var T = t.tuple([S, S], 'T'); 39 | 40 | it('should throw with a contextual error message if used with wrong arguments', function () { 41 | 42 | var T = t.tuple([t.Number, t.Number], 'T'); 43 | 44 | util.throwsWithMessage(function () { 45 | T(); 46 | }, '[tcomb] Invalid value undefined supplied to T (expected an array of length 2)'); 47 | 48 | util.throwsWithMessage(function () { 49 | T(['a']); 50 | }, '[tcomb] Invalid value [\n "a"\n] supplied to T (expected an array of length 2)'); 51 | 52 | }); 53 | 54 | it('should hydrate the elements of the list', function () { 55 | var instance = T([{}, {}]); 56 | assert.equal(S.is(instance[0]), true); 57 | }); 58 | 59 | it('should hydrate the elements of the list in production', util.production(function () { 60 | var instance = T([{}, {}]); 61 | assert.equal(S.is(instance[0]), true); 62 | })); 63 | 64 | it('should be idempotent', function () { 65 | var t0 = [{}, {}]; 66 | var t1 = T(t0); 67 | var t2 = T(t1); 68 | assert.equal(t0 === t1, false); 69 | assert.equal(t1 === t2, true); 70 | }); 71 | 72 | it('should be idempotent in production', util.production(function () { 73 | var t0 = [{}, {}]; 74 | var t1 = T(t0); 75 | var t2 = T(t1); 76 | assert.equal(t0 === t1, false); 77 | assert.equal(t1 === t2, true); 78 | })); 79 | 80 | }); 81 | 82 | describe('is(x)', function () { 83 | 84 | it('should return true when x is an instance of the tuple', function () { 85 | assert.ok(Area.is([1, 2])); 86 | }); 87 | 88 | it('should return false when x is not an instance of the tuple', function () { 89 | assert.strictEqual(Area.is([1]), false); 90 | assert.strictEqual(Area.is([1, 2, 3]), false); 91 | assert.strictEqual(Area.is([1, 'a']), false); 92 | }); 93 | 94 | it('should not depend on `this`', function () { 95 | assert.ok([[1, 2]].every(Area.is)); 96 | }); 97 | 98 | }); 99 | 100 | describe('update(instance, patch)', function () { 101 | 102 | it('should return a new instance', function () { 103 | var Tuple = t.tuple([t.String, t.Number]); 104 | var instance = Tuple(['a', 1]); 105 | var newInstance = Tuple.update(instance, {0: { $set: 'b' }}); 106 | assert.equal(Tuple.is(newInstance), true); 107 | assert.equal(newInstance !== instance, true); 108 | assert.equal(newInstance[0], 'b'); 109 | }); 110 | 111 | }); 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /test/union.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | var assert = require('assert'); 3 | var t = require('../index'); 4 | var util = require('./util'); 5 | 6 | var Point = t.struct({ 7 | x: t.Number, 8 | y: t.Number 9 | }); 10 | 11 | var Circle = t.struct({ 12 | center: Point, 13 | radius: t.Number 14 | }, 'Circle'); 15 | 16 | var Rectangle = t.struct({ 17 | a: Point, 18 | b: Point 19 | }); 20 | 21 | var Shape = t.union([Circle, Rectangle], 'Shape'); 22 | 23 | Shape.dispatch = function (values) { 24 | values = t.Object(values); 25 | return values.hasOwnProperty('center') ? 26 | Circle : 27 | Rectangle; 28 | }; 29 | 30 | describe('t.union(types, [name])', function () { 31 | 32 | describe('combinator', function () { 33 | 34 | it('should throw if used with wrong arguments', function () { 35 | 36 | util.throwsWithMessage(function () { 37 | t.union(); 38 | }, '[tcomb] Invalid argument types undefined supplied to union(types, [name]) combinator (expected an array of at least 2 types)'); 39 | 40 | util.throwsWithMessage(function () { 41 | t.union([]); 42 | }, '[tcomb] Invalid argument types [] supplied to union(types, [name]) combinator (expected an array of at least 2 types)'); 43 | 44 | util.throwsWithMessage(function () { 45 | t.union([1]); 46 | }, '[tcomb] Invalid argument types [\n 1\n] supplied to union(types, [name]) combinator (expected an array of at least 2 types)'); 47 | 48 | util.throwsWithMessage(function () { 49 | t.union([Circle, Point], 1); 50 | }, '[tcomb] Invalid argument name 1 supplied to union(types, [name]) combinator (expected a string)'); 51 | 52 | }); 53 | 54 | }); 55 | 56 | describe('constructor', function () { 57 | 58 | it('should hydrate the input', function () { 59 | var circle = Shape({center: {x: 0, y: 0}, radius: 10}); 60 | assert.equal(Circle.is(circle), true); 61 | }); 62 | 63 | it('should hydrate the input in production', util.production(function () { 64 | var circle = Shape({center: {x: 0, y: 0}, radius: 10}); 65 | assert.equal(Circle.is(circle), true); 66 | })); 67 | 68 | it('should freeze the instance', function () { 69 | var circle = Shape({center: {x: 0, y: 0}, radius: 10}); 70 | assert.equal(Object.isFrozen(circle), true); 71 | }); 72 | 73 | it('should not freeze the instance in production', util.production(function () { 74 | var circle = Shape({center: {x: 0, y: 0}, radius: 10}); 75 | assert.equal(Object.isFrozen(circle), false); 76 | })); 77 | 78 | it('should throw if used with new', function () { 79 | 80 | assert.doesNotThrow(function () { 81 | Shape({center: {x: 0, y: 0}, radius: 10}); 82 | }); 83 | 84 | util.throwsWithMessage(function () { 85 | var T = t.union([t.String, t.Number], 'T'); 86 | var x = new T('a'); // eslint-disable-line 87 | }, '[tcomb] Cannot use the new operator to instantiate the type T'); 88 | 89 | }); 90 | 91 | it('should show the offended union type in error messages', function () { 92 | util.throwsWithMessage(function () { 93 | Shape({center: {x: 0, y: 0}}); 94 | }, '[tcomb] Invalid value undefined supplied to Shape(Circle)/radius: Number'); 95 | }); 96 | 97 | it('should be idempotent', function () { 98 | var s0 = {center: {x: 0, y: 0}, radius: 10}; 99 | var s1 = Shape(s0); 100 | var s2 = Shape(s1); 101 | assert.equal(s1 === s0, false); 102 | assert.equal(s2 === s1, true); 103 | }); 104 | 105 | it('should be idempotent in production', util.production(function () { 106 | var s0 = {center: {x: 0, y: 0}, radius: 10}; 107 | var s1 = Shape(s0); 108 | var s2 = Shape(s1); 109 | assert.equal(s1 === s0, false); 110 | assert.equal(s2 === s1, true); 111 | })); 112 | 113 | it('should handle incomplete dispatch functions', function () { 114 | var T1 = t.struct({}); 115 | var T2 = t.struct({}); 116 | var U = t.union([T1, T2]); 117 | U.dispatch = function (x) { 118 | switch (x.type) { 119 | case '1': return T1; 120 | case '2': return T2; 121 | } 122 | }; 123 | var x = U({type: '2'}); 124 | assert.strictEqual(x instanceof T2, true); 125 | x = U(x); 126 | assert.strictEqual(x instanceof T2, true); 127 | }); 128 | 129 | }); 130 | 131 | describe('is(x)', function () { 132 | 133 | it('should return true when x is an instance of the union', function () { 134 | var p = new Circle({center: {x: 0, y: 0}, radius: 10}); 135 | assert.equal(Shape.is(p), true); 136 | assert.equal(Shape.is(1), false); 137 | }); 138 | 139 | }); 140 | 141 | describe('dispatch(x)', function () { 142 | 143 | it('should have a default implementation', function () { 144 | var U = t.union([t.String, t.Number], 'T'); 145 | assert.equal(U(1), 1); 146 | }); 147 | 148 | it('should handle union of unions', function () { 149 | var U1 = t.union([t.String, t.Number], 'U1'); 150 | var U2 = t.union([t.Boolean, t.Object], 'U2'); 151 | var U = t.union([U1, U2, t.Array], 'U'); 152 | assert.equal(U.dispatch(1), t.Number); 153 | assert.equal(U.dispatch({foo: "bar"}), t.Object); 154 | assert.equal(U.dispatch([]), t.Array); 155 | }); 156 | 157 | it('should throw if does not return a valid type', function () { 158 | 159 | util.throwsWithMessage(function () { 160 | var U = t.union([t.String, t.Number], 'T'); 161 | U(true); 162 | }, '[tcomb] Invalid value true supplied to T (no constructor returned by dispatch)'); 163 | 164 | }); 165 | 166 | it('should not throw if does not return a valid type', function () { 167 | var U1 = t.union([t.String, t.Number], 'U1'); 168 | var U2 = t.union([t.Boolean, t.Object], 'U2'); 169 | var U = t.union([U1, U2, t.Array], 'U'); 170 | assert.doesNotThrow(function () { 171 | U(1); 172 | }); 173 | }); 174 | 175 | it('should handle constructors', function () { 176 | function A() {} 177 | var U = t.union([t.Boolean, A], 'U'); 178 | assert.doesNotThrow(function () { 179 | U(new A()); 180 | }); 181 | }); 182 | 183 | }); 184 | 185 | describe('update(instance, patch)', function () { 186 | 187 | it('should update the right instance', function () { 188 | var circle = Shape.update({ center: { x: 0, y: 0 }, radius: 10 }, { radius: { $set: 15 } }); 189 | assert.equal(Circle.is(circle), true); 190 | }); 191 | 192 | }); 193 | 194 | }); 195 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | function throwsWithMessage(f, message) { 4 | assert.throws(f, function (err) { 5 | assert.strictEqual(err instanceof Error, true); 6 | assert.strictEqual(err.message, message); 7 | return true; 8 | }); 9 | } 10 | 11 | function production(f) { 12 | return function () { 13 | process.env.NODE_ENV = 'production'; 14 | try { 15 | f(); 16 | } catch (e) { 17 | assert.fail(e.message); 18 | } 19 | finally { 20 | process.env.NODE_ENV = 'development'; 21 | } 22 | }; 23 | } 24 | 25 | module.exports = { 26 | throwsWithMessage: throwsWithMessage, 27 | production: production 28 | }; 29 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var library = 'Tcomb'; 4 | 5 | module.exports = [ 6 | { 7 | devtool: 'source-map', 8 | entry: './index.js', 9 | output: { 10 | path: path.resolve(__dirname, './dist'), 11 | filename: 'tcomb.js', 12 | library: library, 13 | libraryTarget: 'umd' 14 | }, 15 | plugins: [ 16 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development') }) 17 | ] 18 | }, 19 | { 20 | devtool: 'source-map', 21 | entry: './index.js', 22 | output: { 23 | path: path.resolve(__dirname, './dist'), 24 | filename: 'tcomb.min.js', 25 | library: library, 26 | libraryTarget: 'umd' 27 | }, 28 | plugins: [ 29 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), 30 | new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) 31 | ] 32 | } 33 | ]; 34 | --------------------------------------------------------------------------------