├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── esm └── index.js ├── package.json ├── test └── index_test.js └── typings.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Built via npm run build 2 | cjs 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # cjs/ is ignored by .gitignore, but needs to be published to npm 2 | # node_modules is ignored by default by npm publish -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Axel Rauschmayer 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 | # TypeRight: minimal dynamic typing library 2 | 3 | To see its functionality demonstrated, take a look at [the tests](https://github.com/rauschma/type-right/blob/master/test/index_test.js). 4 | 5 | Installation: 6 | 7 | ```text 8 | npm install type-right 9 | ``` 10 | 11 | Importing: 12 | 13 | ```js 14 | import * as tr from 'type-right'; 15 | ``` 16 | 17 | ## Using `instanceof` for primitive values 18 | 19 | Having to choose between `typeof` and `instanceof` is annoying. TypeRight uses [`Symbol.hasInstance`](http://exploringjs.com/es6/ch_oop-besides-classes.html#_property-key-symbolhasinstance-method) to fix this: 20 | 21 | ```js 22 | console.log('abc' instanceof tr.PrimitiveString); // true 23 | console.log(null instanceof tr.PrimitiveNull); // true 24 | ``` 25 | 26 | ## Enforcing the types of values 27 | 28 | ```js 29 | tr.force('abc', tr.PrimitiveString); // ok 30 | tr.force(undefined, tr.PrimitiveString); // TypeError 31 | ``` 32 | 33 | If parameters can be missing or `undefined`: 34 | 35 | ```js 36 | tr.force('abc', tr.union(tr.PrimitiveString, tr.PrimitiveUndefined)); // ok 37 | tr.force(undefined, tr.union(tr.PrimitiveString, tr.PrimitiveUndefined)); // ok 38 | ``` 39 | 40 | ### Enforcing the types of parameters 41 | 42 | ```js 43 | function dist(x, y) { 44 | tr.force(x, tr.PrimitiveNumber, y, tr.PrimitiveNumber); 45 | return Math.hypot(x, y); 46 | } 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | const typeName = Symbol('typeName'); 2 | 3 | export class PrimitiveUndefined { 4 | static [Symbol.hasInstance](x) { 5 | return x === undefined; 6 | } 7 | } 8 | export class PrimitiveNull { 9 | static [Symbol.hasInstance](x) { 10 | return x === null; 11 | } 12 | } 13 | export class PrimitiveBoolean { 14 | static [Symbol.hasInstance](x) { 15 | return typeof x === 'boolean'; 16 | } 17 | } 18 | export class PrimitiveNumber { 19 | static [Symbol.hasInstance](x) { 20 | return typeof x === 'number'; 21 | } 22 | } 23 | 24 | export class PrimitiveString { 25 | static [Symbol.hasInstance](x) { 26 | return typeof x === 'string'; 27 | } 28 | } 29 | 30 | export class PrimitiveSymbol { 31 | static [Symbol.hasInstance](x) { 32 | return typeof x === 'symbol'; 33 | } 34 | } 35 | export function union(...types) { 36 | return class { 37 | static [Symbol.hasInstance](x) { 38 | for (const type of types) { 39 | if (type[Symbol.hasInstance](x)) { 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | static get [typeName]() { 46 | return types.map(t => toTypeName(t)).join(' | '); 47 | } 48 | }; 49 | } 50 | export function toTypeName(type) { 51 | if (typeof type !== 'function') { 52 | throw new TypeError('Not a type: '+type); 53 | } 54 | return type[typeName] || type.name; 55 | } 56 | function checkInternal(args) { 57 | if ((args.length % 2) !== 0) { 58 | throw new TypeError('Need an even number of arguments: '+args); 59 | } 60 | for (let i=0; i < args.length; i+=2) { 61 | const value = args[i]; 62 | const type = args[i+1]; 63 | if (! (value instanceof type)) { 64 | return i; 65 | } 66 | } 67 | return -1; 68 | } 69 | export function force(...args) { 70 | const failureIndex = checkInternal(args); 71 | if (failureIndex >= 0) { 72 | const value = args[failureIndex]; 73 | const type = args[failureIndex+1]; 74 | throw new TypeError(`Value at index #${failureIndex/2}, ${String(value)}, is not an instance of ${toTypeName(type)}`); 75 | } 76 | // Everything is OK 77 | } 78 | export function check(...args) { 79 | const failureIndex = checkInternal(args); 80 | return failureIndex < 0; 81 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "type-right", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "babel esm --out-dir cjs", 6 | "prepublishOnly": "npm run build", 7 | "test": "ava" 8 | }, 9 | "author": "Axel Rauschmayer", 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/rauschma/type-right.git" 14 | }, 15 | "typings": "typings.d.ts", 16 | "//": [ 17 | "babel-cli: needed for building", 18 | "babel-register: needed for testing via AVA" 19 | ], 20 | "devDependencies": { 21 | "babel-cli": "^6.24.1", 22 | "ava": "^0.21.0", 23 | "babel-preset-env": "^1.5.1", 24 | "babel-register": "^6.24.1" 25 | }, 26 | "module": "./esm/index.js", 27 | "main": "./cjs/index.js", 28 | "babel": { 29 | "presets": [ 30 | [ 31 | "env", 32 | { 33 | "targets": { 34 | "node": "current" 35 | } 36 | } 37 | ] 38 | ] 39 | }, 40 | "ava": { 41 | "require": [ 42 | "babel-register" 43 | ], 44 | "babel": "inherit" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/index_test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import * as tr from '../esm/index.js'; 3 | 4 | test('instanceof', t => { 5 | t.true('abc' instanceof tr.PrimitiveString); 6 | t.true(null instanceof tr.PrimitiveNull); 7 | }); 8 | test('tr.check()', t => { 9 | t.true(tr.check('abc', tr.PrimitiveString)); 10 | t.false(tr.check(undefined, tr.PrimitiveString)); 11 | t.true(tr.check('abc', tr.union(tr.PrimitiveString, tr.PrimitiveUndefined))); 12 | t.true(tr.check(undefined, tr.union(tr.PrimitiveString, tr.PrimitiveUndefined))); 13 | }); 14 | test('tr.force()', t => { 15 | t.notThrows(() => tr.force('abc', tr.PrimitiveString)); 16 | t.throws(() => tr.force(undefined, tr.PrimitiveString), TypeError); 17 | t.notThrows(() => tr.force('abc', tr.union(tr.PrimitiveString, tr.PrimitiveUndefined))); 18 | t.notThrows(() => tr.force(undefined, tr.union(tr.PrimitiveString, tr.PrimitiveUndefined))); 19 | }); 20 | test('tr.toTypeName()', t => { 21 | t.is(tr.toTypeName(tr.union(tr.PrimitiveNumber, tr.PrimitiveNull)), 'PrimitiveNumber | PrimitiveNull'); 22 | }); 23 | 24 | test('Checking parameter types', t => { 25 | function dist(x, y) { 26 | tr.force(x, tr.PrimitiveNumber, y, tr.PrimitiveNumber); 27 | return Math.hypot(x, y); 28 | } 29 | t.is(dist(3, 4), 5); 30 | t.throws(() => dist(3, undefined), TypeError); 31 | }); 32 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | export declare class PrimitiveUndefined { 2 | static [Symbol.hasInstance](x: any): boolean; 3 | } 4 | 5 | export declare class PrimitiveNull { 6 | static [Symbol.hasInstance](x: any): boolean; 7 | } 8 | 9 | export declare class PrimitiveBoolean { 10 | static [Symbol.hasInstance](x: any): boolean; 11 | } 12 | 13 | export declare class PrimitiveNumber { 14 | static [Symbol.hasInstance](x: any): boolean; 15 | } 16 | 17 | export declare class PrimitiveString { 18 | static [Symbol.hasInstance](x: any): boolean; 19 | } 20 | 21 | export declare class PrimitiveSymbol { 22 | static [Symbol.hasInstance](x: any): boolean; 23 | } 24 | 25 | export declare function union(...types: any[]): { 26 | new(): {}; 27 | [Symbol.hasInstance](x: any): boolean; 28 | readonly [typeName]: string; 29 | }; 30 | 31 | export declare function toTypeName(type: any): any; 32 | 33 | export declare function force(...args: any[]): void; 34 | 35 | export declare function check(...args: any[]): boolean; 36 | --------------------------------------------------------------------------------