Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/acamica/ts-dynamic-type-checker

TypeScript library that performs dynamic type checking. It's useful for cases where you can't use TypeScript's static type checking (like reading a JSON object from a file).
https://github.com/acamica/ts-dynamic-type-checker

Last synced: about 2 months ago
JSON representation

TypeScript library that performs dynamic type checking. It's useful for cases where you can't use TypeScript's static type checking (like reading a JSON object from a file).

Awesome Lists containing this project

README

        

# TS Dynamic Type Checker

[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors)

> TypeScript library that performs dynamic type checking

TypeScript is great. It warns you of static type errors and hence earns you lots of time and headaches. But your program probably have entrypoints (network requests, file readings, etc.) that can not be trusted completely.

For instance, supose you read some configuration from a `JSON` file:

```typescript
import { readFile } from 'fs';

interface IAwsConfig {
// definitions...
};

readFile('./my-aws-config.json', { encoding: 'utf8' }, (err, awsConfigStr) => {
if (err) {
console.error(err);
return;
}
const awsConfig: IAwsConfig = JSON.parse(awsConfigStr);
});
```

In this example, TypeScript can not prevent errors if the read JSON doesn't have an expected property. These are some cases this library was created for.

## Features

- Infers typings.
- Very lightweight (under 4kb without minifying).
- Expressive errors.
- Works both client and server-side.

## Installation

```bash
npm install --save ts-dynamic-type-checker
```

## Usage

The main concept behind the library is "**_contracts_**". A **_contract_** is an _identity function_ that throws a `TypeError` if the parameter doesn't have the expected type.

For example if you pass a `string` to the `str` **contract**, it will return the same value, for other _types_ it will throw a `TypeError`:

```typescript
import { str } from 'ts-dynamic-type-checker';

str('Hello world'); // <- Returns 'Hello world'
str(8 as any); // <- Throws a TypeError
```

### Check for specific values

```typescript
import { oneOf } from 'ts-dynamic-type-checker';
import { createInterface } from 'readline';

const isFruit = oneOf('apple', 'banana', 'strawberry', 'orange');
type IFruit = 'apple' | 'banana' | 'strawberry' | 'orange';

const readLine = createInterface({
input: process.stdin,
output: process.stdout
});

const aFruit: IFruit = 'cheese'; // <- static error. It will be warned by TypeScript itself.

readLine.question('Which is your favourite fruit?', (someFruit) => {
const favouriteFruit: IFruit = isFruit(someFruit); // <- Will throw a TypeError if `someFruit` has any other value than 'apple', 'banana', 'strawberry' or 'orange'. It's a potential dynamic error and TypeScript could not detect it.
});
```

It's important to notice that while `str` is a **contract** itself, `oneOf` is not. `oneOf` is a function that _returns_ a **contract**. You can think of it like a _contract builder_.

There are some other functions that help you build `contracts`. For instance, there is `arrOf`:

```typescript
import { arrOf, num, oneOf } from 'ts-dynamic-type-checker';

const onlyNumbers = arrOf(num);

onlyNumbers([1, 2, 3]); // <- Returns [1, 2, 3]
onlyNumbers(['Hello', 'world', 99] as any); // <- Throws a TypeError

const onlyHobbits = arrOf(oneOf('Frodo', 'Bilbo', 'Merry', 'Pippin', 'Sam', 'Gollum'));

onlyHobbits(['Bilbo', 'Sam']); // <- Returns the array
onlyHobbits(['Frodo', 'Pippin', 'Gandalf']); // <- Throws a TypeError
```

As you can see, `arrOf` takes a **contract** as parameter and returns another **contract**.

Last, but not least, the `objOf` function is perhaps the most usefull one:

```typescript
import { objOf, bool, str, num, arrOf } from 'ts-dynamic-type-checker';

const personValidator = objOf({
name: str,
age: num,
profession: oneOf('student', 'employed', 'unemployed', 'retired'),
address: objOf({
street: str,
city: str,
state: str,
country: str
}),
driving_license: bool
});

const peopleValidator = arrOf(personValidator);

// xhr function from any library you like
xhr('/URI/to/people')
.then(peopleValidator)
.then(people => /* The `people` variable is guaranteed to have the shape you have defined... */);
```

Notice that the `objOf` function takes an object that describes the _shape_ of the expected objects as a parameter. That object's properties are **contracts**.

### Type inference

It's important to mention that all the contracts are _typed_ and TypeScript with prevent errors if the parameters are incorrect and will inferere the output:

```typescript
import { str, num, objOf } from 'ts-dynamic-type-checker';

str(9); // TypeScript will error ("string expected").
const aNumber = num(9); // TypeScript will infere it's a number.

const fooBarContract = objOf({
foo: str,
bar: num
});

fooBarContract({
baz: 'Hello'
}); // <- Typescript will error

const fooBar = fooBarContract({
foo: 'Hello',
bar: 100
}); // <- TypeScript will infer type {foo: string; bar: number;}
```

## API

### Built-in `contracts`

| Function | Type Β | Example |
| --------- | -------------------------------------- | -------------------------------- |
| `bool` | `IContract` | `bool(true); ` |
| `num` | `IContract` | `num(89); ` |
| `str` | `IContract` | `str('Hello world'); ` |
| `undef` | `IContract` | `undef(undefined); ` |
| `nil` | `IContract` | `nil(null); ` |
| `arr` | ` IContract` | `arr([1, 2, 3]); ` |
| `obj` | ` IContract` | `bool({foo: 'foo'}); ` |
| `regExp` | `IContract` | `regExp(/^hello/i); ` |
| `date` | `IContract` | `date(new Date()); ` |
| `anything`| ` IContract` | `anything(4);` |
| `never` | `IContract` | `never(4 as never);` |

#### A note on `anything`

`anything` is just an _identity function_ that will never throw a `TypeError`. Its static type will be inferred from the value if possible or will default to `any`. It's useful with another functions like `objOf` (view below). For instance you can define a contract like:

```typescript
const objHasFooContract = objOf({
foo: anything
});
```

#### A note on `never`

You may think the `never` contract is useless. But it can be used to do an exhaustive check:

```typescript
const reactToSemaphore = (semaphoreLight: 'red' | 'yellow' | 'green') => {
switch (semaphoreLight) {
case 'red':
return 'stop';
case 'yellow':
return 'hurry';
case 'green':
return 'go';
default:
never(semaphoreLight);
}
};
```

The function `reactToSemaphore` will fail in runtime if passed another value than `'red' | 'yellow' | 'green'`, but also with statically check that you aren't missing a `case` in the `switch` statement.

You can read more about the use of `never` [here](https://basarat.gitbooks.io/typescript/docs/types/never.html).

### `contract` _builders_

#### `optional`

` (IContract) -> IContract`

Takes a `contract` and returns a new one that matches like the first one but also matches `undefined` values.

```typescript
const optionalNumber = optional(num);
// All the following are valid:
optionalNumber(9);
optionalNumber(undefined);
optionalNumber();
```

#### `nullable`

` (IContract) -> IContract`

Takes a `contract` and returns a new one that matches like the first one but also matches `null` values.

```typescript
const nullableNumber = nullable(num);
// The following are valid
nullableNumber(9);
nullableNumber(null);
```

#### `oneOf`

`(...(string | number | boolean)[]) -> IContract`

It is used to validate _`unum`-like values_. You specify the valid values and it returns a `contract` that will check against them. Example:

```typescript
const osContract = oneOf('Linux', 'Mac OS', 'Windows', 'Other');
const os = osContract('Linux'); // os's type is 'Linux' | 'Mac OS' | 'Windows' | 'Other'
```

TypeScript will infere the `contract`'s return value as the union of the literal types passed (up to 10 parameters, then behaves like ` IContract`).

#### `union`

`...(IContract) _> IContract`

It takes _contracts_ as arguments and returns a new _contract_ that matches if any of the them matches.

```typescript
const numOrStr = union(num, str);
numOrStr(9);
numOrStr('nine');
```

TypeScript will infere the `contract`'s return value as the union of the return values of the _contracts_ passed (up to 10 parameters, then behaves like `IContract`).

#### `arrOf`

` (IContract) -> IContract`

It takes a `contract` "_`C`_" as a parameter and returns another `contract` that expects an `array` of _elements_ that match _`C`_.

```typescript
const arrOfNumbersContract = arrOf(num);
const numbers = arrOfNumbersContract([1, 2, 3]);
```

#### `objOf`

` (IMapOfContracts) -> IContract`

Takes an _object_ that describes the _shape_ of the `objects` you want to validate and returns a `contract` with that validation. That _object_'s values must be `contracts`.

```typescript
const petContract = objOf(
name: str,
species: oneOf('dog', 'cat', 'golden fish', 'parrot', 'other'),
age: number,
gender: oneOf('male', 'female')
);
// <3
const oddy = petContract({
name: 'Oddy',
species: 'dog',
age: 8,
gender: 'female'
});
```

#### `strictObjOf`

` (IMapOfContracts) -> IContract`

It is the same than `objOf` function, but also checks that the _target_ doesn't have extra _properties_.

```typescript
// It only matches empty objects
const emptyObjectContract = strictObjOf({});
const emptyObject = emptyObjectContract({});
```

#### `instanceOf`

` (constructor: C) -> IContract`
(`I` is instance of `C`)

It takes a _class_ or a or a _constructor function_ and returns a `contract` of instances of that _class_ or _constructor_.

```typescript
class Foo {}

const instanceOfFooContract = instanceOf(Foo);
const foo = instanceOfFooContract(new Foo());
```

## Credits

Made from the [`typescript-library-starter`](https://github.com/alexjoverm/typescript-library-starter).

Thanks goes to these wonderful people:

| [
Gonzalo Gluzman](https://github.com/dggluz)
[πŸ’»](https://github.com/acamica/ts-dynamic-type-checker/commits?author=dggluz "Code") [πŸ“–](https://github.com/acamica/ts-dynamic-type-checker/commits?author=dggluz "Documentation") [⚠️](https://github.com/acamica/ts-dynamic-type-checker/commits?author=dggluz "Tests") [πŸ’‘](#example-dggluz "Examples") | [
Hernan Rajchert](https://github.com/hrajchert)
[πŸ“–](https://github.com/acamica/ts-dynamic-type-checker/commits?author=hrajchert "Documentation") [πŸ’»](https://github.com/acamica/ts-dynamic-type-checker/commits?author=hrajchert "Code") | [
Cristhian Duran](https://durancristhian.github.io/)
[πŸ’»](https://github.com/acamica/ts-dynamic-type-checker/commits?author=durancristhian "Code") [πŸ“–](https://github.com/acamica/ts-dynamic-type-checker/commits?author=durancristhian "Documentation") [πŸ’‘](#example-durancristhian "Examples") [⚠️](https://github.com/acamica/ts-dynamic-type-checker/commits?author=durancristhian "Tests") | [
NicolΓ‘s Quiroz](https://nicolasquiroz.com)
[πŸ“–](https://github.com/acamica/ts-dynamic-type-checker/commits?author=nhsz "Documentation") |
| :---: | :---: | :---: | :---: |

This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!