runruntypes v0.1.0
RunRunTypes
RunRunTypes
is a simple runtime-only type checker. It's a fairly specific and simple library
and it might be for you if:
- You're trying to write a runtime-less app and need type checking in development
- Your types are not simple and you prefer Flow-like definitions for them
- You don't care about Flow's comment-only syntax much
Well, if you belong to this small exclusive club, then Welcome!
Disclaimer: This is frankly a horrible idea that's not meant for real world stuff, but it's been quite fun to write.
Getting Started
You can install RunRunTypes
like any other npm package:
yarn add --dev runruntypes
# or
npm install --save-dev runruntypes
NOTE: It's recommended to not ship this package in production as it pulls in
@babel/parser
as a dependency, which is really not that small. It's fairly simple to stub out this library's API though.
Usage
RunRunTypes
consists of two easy to use functions, gen
and type
.
import { gen, type } from 'runruntypes';
The gen
function is used to attach a type signature to your function. Using this signature
you can constrain the arguments' types and the function's return type. The gen
function will
wrap your function and return one that performs the checks when called. A signature is basically
a Flow type annotation:
const fn = gen`
(string, number, number) => [string, number]
`((str, a, b) => [str, a + b])
fn('test', 1, 2) // returns: ['test', 3]
fn('test', 1) // error!
fn(1, 1, 1) // error!
The above example shows a simple definition and how arguments are checked. Similarly the return types are checked when your function completes:
const fn = gen`
(string, number, number) => [string, number]
`((str, a, b) => null)
fn('test', 1, 2) // error!
The second function, type
is used to alias type signatures to a variable. It can be used to predefine
some types which can then be interpolated into your gen
definition.
import { gen, type } from 'runruntypes';
const Errorish = type`
{ message: ?string }
`;
const isError = gen`(${Errorish} | void) => bool`(err => {
return err !== undefined && !!err.message;
});
This extracts an object definition type outside of the gen
definition. For comparison, without a type alias
you'd simply write this definition inline, which can become tedious if it's being used and repeated quite often:
import { gen } from 'runruntypes';
const isError = gen`
({ message: ?string } | void) => bool
`(err => {
return err !== undefined && !!err.message;
});
RunRunTypes
uses the @babel/parser
to parse any input to it. A large subset of Flow type annotations
will be parsed without a problem and will be correctly check. You can always expect this library
to throw a TypeError
when it either:
- doesn't understand your type definition
- or; has caught a type error
Feel free to read more about Flow type annotations here
API
type
A tagged template literal that accepts a Flow type definition consisting of any of the supported types and syntax.
This will return a function that checks the first argument against the type definition and returns true
if it
passes the type checks and false
if it doesn't.
type`string`('test') // true
type`{ x: 1 }`({ x: 2 }) // false
gen
A tagged template literal that accepts a Flow arrow function type definition consisting of any of the supported types and syntax.
It expects any number of arguments and a return type. Normal type definitions that are not function definitions will cause it to throw an error.
It returns a factory function that wraps any function passed as the first argument in a new type checked and guarded function, i.e. it'll expect the argument to comply to the type definition that has been defined and will enforce it.
gen`string => string`(x => 'Hello, ' + x)('Luke')
gen`(number, 4) => string`((a, b) => '' + a + b)(2, 4)
Supported Types
Currently the following types are supported:
- primitive types (
bool
,number
,string
,null
,void
,any
) - literal types (e.g.
true
,false
,2
,"hello"
) - nullables (
?number
, i.e.null
,undefined
,number
) - arrays (
number[]
) - tuples (
[number, number]
) - objects (
{ x: number }
without support for optionals nor methods) - unions (
number | string
meaning that both are allowed) - intersections (
{ x: 1 } & { y: 2 }
meaning that both must match) - generic constructors (e.g.
Date
orElement
which will simply check the constructor's name) - "any" functions or objects (write
Object
to match any object,Function
to match any function)
You can mix and combine the types as you'd expect in a real typed language.
Remember that gen
expects a function definition and type
any of the above.
5 years ago