npm package: @flix-tech/fp-ts-type-check
Description: Type validation in runtime
Author: FlixMobility Tech
Latest version: 0.2.2

fp-ts-type-check is a library for runtime type validation of variables where you can't say their type for sure i.e. data you get via some API. It's somewhat similar to Typescript's [Type Guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) except it uses type system to make sure this data is properly validated.

Features:

* **Type safe.** Typescript compiler makes sure your types and your type checkers are always in sync.
* **Detailed error reports.** If parsing of a big deep-nested structure failed - you'll know where exactly in that structure you have invalid data and why it was considered invalid
* **Composable.** Type checkers for big structures are created by composing checkers for simple structures. 
* **Functional.** Each type checker is a pure function returning `Either` type from fp-ts. It's up for you to decide how to handle errors.
* **[Tree-shakeable](https://webpack.js.org/guides/tree-shaking/).** Bundle only functions you really use.

## Installation

To install the stable version:

```
npm install '@flix-tech/fp-ts-type-check@~0.2.0'
```

While the major version number is 0 changes in minor version number bay break backward compatibility so you should stick to a fixed minor version.

## Usage

Meet `Parser[T]`, the main type of this library. `Parser[T]` is a function that takes any variable and checks if it's a variable of type `T`. It either returns the same value as `T` or `ParseError` object with parse error details. Parser's type definition looks somewhat like this:

```typescript
type Parser<A> = (x: unknown): Either<ParseError, A>;

interface ParseError {
  path: string; // Path to the property causing error in deep nested structures
  message: string; // Parsing error message
}
```

We're using the type [Either](https://gcanti.github.io/fp-ts/modules/Either.ts.html) from [fp-ts](https://github.com/gcanti/fp-ts) library.

Parsers for some complex structures you'd want to validate are composed from small parsers like here:

```typescript
import * as P from 'fp-ts-type-check';

interface ShoppingListItem {
  name: string;
  amount: { count: number; unit?: string };
}
const shoppingListItemParser: P.Parser<ShoppingListItem> = P.type({
  name: P.string,
  amount: P.type({
    count: P.number,
    unit: P.optional(P.string),
  }),
});

shoppingListItem({name: "Apple", amount: {count: 5}}); // Right<ShoppingListItem>({name: "Apple", amount: {count: 5}})

shoppingListItem({name: "Apple", amount: {count: "some"}}); // Left<ParseError>({path: ".amount.count", message: "expected number, got string"})
```

As you can see, `ParseError` data is for your eyes only, user should get a generalized error message appropriate in this case.

You can reuse your parsers to construct parsers for even bigger structures:

```typescript
type ShoppingList = array<ShoppingListItem>;

const shoppingListParser: P.Parser<ShoppingList> = P.arrayOf(shoppingListItemParser);

const validShoppingList = [
  {name: "Apple", amount: {count: 5}},
  {name: "Milk", amount: {count: 500, unit: 'ml'}},
];
shoppingListParser(validShoppingList); // Right<ShoppingList>(...)

const invalidShoppingList = [
  {name: "Apple", amount: {count: 5}},
  {name: "Milk", amount: {unit: 'ml'}}, // No count here
];
shoppingListParser(invalidShoppingList); // Left<ParseError>({path: "[1].amount.count", message: "expected number, got undefined"})
```

# API documentation

### Simple type parsers

* `string(): Parser<string>` - checks that value is string.
* `boolean(): Parser<boolean>` - checks that value is boolean.
* `number(): Parser<number>` - checks that value is number.
* `object(): Parser<object>` - checks that value is any object.
* `any(): Parser<any>` - does not check anything.
* `exact<A>(expected: A): Parser<A>` - checks that value is same as expected one.
* `oneOf<A>(allowed: A[]): Parser<A>` - checks that value is one of allowed ones.
* `keyOf<A extends object>(allowed: A): Parser<keyof A>` - checks that value is string and also is a key of object `allowed`.

## Copositional parsers

* `type<A>(propertyParsers: { [K in keyof A]: Parser<A[K]> }): Parser<A>` - checks that values is an object and validates each it's property with corresponding parser.

* `arrayOf<A>(parseBody: Parser<A>): Parser<A[]>` - checks that value is an array and every item matches  `parseBody` parser.

* `optional<A>(parseBody: Parser<A>): Parser<A | undefined>` - checks that value is either undefined or matches `parseBody` parser.

* `nullable<A>(parseBody: Parser<A>): Parser<A | null>` - checks that value is either null or matches `parseBody` parser.

* `discriminatedUnion<A extends {type: string;}>(parsers: {(type value): (type parser)}): Parser<A>` - checks that value is a [discriminated union](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) with discriminant in `type` property. Example usage:

  ```typescript
  type Foo = { type: 'foo'; foo: number };
  type Bar = { type: 'bar'; bar: number };
  type FooBar = Foo | Bar;
  
  const parser: P.Parser<FooBar> = P.discriminatedUnion({
    foo: P.type({ foo: P.number }),
    bar: P.type({ bar: P.number }),
  });
  ```

### Logic combination parsers

* `and(parserA: Parser<A>, parserB<B>): Parser<A & B>` - checks that value matches both types A and B.
* `or(parserA: Parser<A>, parserB<B>): Parser<A | B>` - checks that value matches any of types A or B.

## License

The MIT License (MIT)