Skip to content

Latest commit

 

History

History
1355 lines (976 loc) · 51.7 KB

api.md

File metadata and controls

1355 lines (976 loc) · 51.7 KB
title parent nav_order
Built-in decoders
API Reference
20

Built-in decoders

All "batteries included" decoders available in the standard library.


Strings


# string: Decoder<string> (source) {: #string .signature}

Accepts and returns strings.

// 👍
string.verify('hello world') === 'hello world';
string.verify('🚀') === '🚀';
string.verify('') === '';

// 👎
string.verify(123);   // throws
string.verify(true);  // throws
string.verify(null);  // throws

# nonEmptyString: Decoder<string> (source) {: #nonEmptyString .signature}

Like string, but will reject the empty string or strings containing only whitespace.

// 👍
nonEmptyString.verify('hello world') === 'hello world';
nonEmptyString.verify('🚀') === '🚀';

// 👎
nonEmptyString.verify(123);   // throws
nonEmptyString.verify('  ');  // throws
nonEmptyString.verify('');    // throws

# regex(pattern: RegExp, message: string): Decoder<string> (source) {: #regex .signature}

Accepts and returns strings that match the given regular expression.

const decoder = regex(/^[0-9][0-9]+$/, 'Must be numeric');

// 👍
decoder.verify('42') === '42';
decoder.verify('83401648364738') === '83401648364738';

// 👎
decoder.verify('');     // throws
decoder.verify('1');    // throws
decoder.verify('foo');  // throws

# email: Decoder<string> (source) {: #email .signature}

Accepts and returns strings that are syntactically valid email addresses. (This will not mean that the email address actually exist.)

// 👍
email.verify('[email protected]') === '[email protected]';

// 👎
email.verify('foo');               // throws
email.verify('@acme.org');         // throws
email.verify('alice @ acme.org');  // throws

# url: Decoder<URL> (source) {: #url .signature}

Accepts strings that are valid URLs, returns the value as a URL instance.

// 👍
url.verify('http://nvie.com') === new URL('http://nvie.com/');
url.verify('https://nvie.com') === new URL('https://nvie.com/');
url.verify('git+ssh://[email protected]/foo/bar.git') === new URL('git+ssh://[email protected]/foo/bar.git');

// 👎
url.verify('foo');               // throws
url.verify('@acme.org');         // throws
url.verify('alice @ acme.org');  // throws
url.verify('/search?q=foo');     // throws

# httpsUrl: Decoder<URL> (source) {: #httpsUrl .signature}

Accepts strings that are valid URLs, but only HTTPS ones. Returns the value as a URL instance.

// 👍
httpsUrl.verify('https://nvie.com:443') === new URL('https://nvie.com/');

// 👎
httpsUrl.verify('http://nvie.com');                        // throws, not HTTPS
httpsUrl.verify('git+ssh://[email protected]/foo/bar.git');  // throws, not HTTPS

Tip! If you need to limit URLs to different protocols than HTTP, you can do as the HTTPS decoder is implemented: by adding further conditions using an .refine() call.

import { url } from 'decoders';

const gitUrl: Decoder<URL> = url.refine(
    (value) => value.protocol === 'git:',
    'Must be a git:// URL',
);

# uuid: Decoder<string> (source) {: #uuid .signature}

Accepts strings that are valid UUIDs (universally unique identifier).

// 👍
uuid.verify('123e4567-e89b-12d3-a456-426614174000') === '123e4567-e89b-12d3-a456-426614174000'
uuid.verify('123E4567-E89B-12D3-A456-426614174000') === '123E4567-E89B-12D3-A456-426614174000'

// 👎
uuid.verify('123E4567E89B12D3A456426614174000');      // throws
uuid.verify('abcdefgh-ijkl-mnop-qrst-uvwxyz012345');  // throws

# uuidv1: Decoder<URL> (source) {: #uuidv1 .signature}

Like uuid, but only accepts UUIDv1 strings.

// 👍
uuidv1.verify('123e4567-e89b-12d3-a456-426614174000') === '123e4567-e89b-42d3-a456-426614174000'

// 👎
uuidv1.verify('123e4567-e89b-42d3-a456-426614174000')  // throws

# uuidv4: Decoder<URL> (source) {: #uuidv4 .signature}

Like uuid, but only accepts UUIDv4 strings.

// 👍
uuidv4.verify('123e4567-e89b-42d3-a456-426614174000') === '123e4567-e89b-42d3-a456-426614174000'

// 👎
uuidv4.verify('123e4567-e89b-12d3-a456-426614174000')  // throws

Numbers


# number: Decoder<number> (source) {: #number .signature}

Accepts finite numbers (can be integer or float values). Values NaN, or positive and negative Infinity will get rejected.

// 👍
number.verify(123) === 123;
number.verify(-3.14) === -3.14;

// 👎
number.verify(Infinity);        // throws
number.verify(NaN);             // throws
number.verify('not a number');  // throws

# integer: Decoder<number> (source) {: #integer .signature}

Accepts only finite whole numbers.

// 👍
integer.verify(123) === 123;

// 👎
integer.verify(-3.14);           // throws
integer.verify(Infinity);        // throws
integer.verify(NaN);             // throws
integer.verify('not a integer'); // throws

# positiveNumber: Decoder<number> (source) {: #positiveNumber .signature}

Accepts only non-negative (zero or positive) finite numbers.

// 👍
positiveNumber.verify(123) === 123;
positiveNumber.verify(0) === 0;
positiveNumber.verify(-0) === -0;

// 👎
positiveNumber.verify(-42);             // throws
positiveNumber.verify(3.14);            // throws
positiveNumber.verify(Infinity);        // throws
positiveNumber.verify(NaN);             // throws
positiveNumber.verify('not a number');  // throws

# positiveInteger: Decoder<number> (source) {: #positiveInteger .signature}

Accepts only non-negative (zero or positive) finite whole numbers.

// 👍
positiveInteger.verify(123) === 123;
positiveInteger.verify(0) === 0;
positiveInteger.verify(-0) === -0;

// 👎
positiveInteger.verify(-3);              // throws
positiveInteger.verify(3.14);            // throws
positiveInteger.verify(Infinity);        // throws
positiveInteger.verify(NaN);             // throws
positiveInteger.verify('not a number');  // throws

# anyNumber: Decoder<number> (source) {: #anyNumber .signature}

Accepts any valid number value.

This also accepts special values like NaN and Infinity. Unless you want to deliberately accept those, you'll likely want to use the number decoder instead.

// 👍
anyNumber.verify(123) === 123;
anyNumber.verify(-3.14) === -3.14;
anyNumber.verify(Infinity) === Infinity;
anyNumber.verify(NaN) === NaN;

// 👎
anyNumber.verify('not a number');  // throws

Booleans


# boolean: Decoder<boolean> (source) {: #boolean .signature}

Accepts and returns booleans.

// 👍
boolean.verify(false) === false;
boolean.verify(true) === true;

// 👎
boolean.verify(undefined);      // throws
boolean.verify('hello world');  // throws
boolean.verify(123);            // throws

# truthy: Decoder<boolean> (source) {: #truthy .signature}

Accepts anything and will return its "truth" value. Will never reject.

// 👍
truthy.verify(false) === false;
truthy.verify(true) === true;
truthy.verify(undefined) === false;
truthy.verify('hello world') === true;
truthy.verify('false') === true;
truthy.verify(0) === false;
truthy.verify(1) === true;
truthy.verify(null) === false;

// 👎
// This decoder will never reject an input

# numericBoolean: Decoder<boolean> (source) {: #numericBoolean .signature}

Accepts numbers, but return their boolean representation.

// 👍
numericBoolean.verify(-1) === true;
numericBoolean.verify(0) === false;
numericBoolean.verify(123) === true;

// 👎
numericBoolean.verify(false);      // throws
numericBoolean.verify(true);       // throws
numericBoolean.verify(undefined);  // throws
numericBoolean.verify('hello');    // throws

Dates


# date: Decoder<Date> (source) {: #date .signature}

Accepts and returns Date instances.

const now = new Date();

// 👍
date.verify(now) === now;

// 👎
date.verify(123);      // throws
date.verify('hello');  // throws

# iso8601: Decoder<Date> (source) {: #iso8601 .signature}

Accepts ISO8601-formatted strings, returns them as Date instances.

This is very useful for working with dates in APIs: serialize them as .toISOString() when sending, decode them with iso8601 when receiving.

// 👍
iso8601.verify('2020-06-01T12:00:00Z'); // ≈ new Date('2020-06-01T12:00:00Z')

// 👎
iso8601.verify('2020-06-01');  // throws
iso8601.verify('hello');       // throws
iso8601.verify(123);           // throws
iso8601.verify(new Date());    // throws (does not accept dates)

Constants


# constant<T>(value: T): Decoder<T> (source) {: #constant .signature}

Accepts only the given constant value.

Note to Flow users! Flow will incorrectly infer the type for constants by default! The inferred type for constant(42) is Decoder<number>. To work around this, always use this syntax in Flow: constant((42: 42)). TypeScript will correctly infer the type of constant(42) as Decoder<42>.

const decoder = constant('hello');

// 👍
decoder.verify('hello') === 'hello';

// 👎
decoder.verify('this breaks');  // throws
decoder.verify(false);          // throws
decoder.verify(undefined);      // throws

# always<T>(value: T): Decoder<T> (source) {: #always .signature}

# hardcoded<T>(value: T): Decoder<T> (source) {: #hardcoded .signature}

Accepts anything, completely ignores it, and always returns the provided value instead.

This is useful to manually add extra fields to object decoders.

const decoder = always(42);

// 👍
decoder.verify('hello') === 42;
decoder.verify(false) === 42;
decoder.verify(undefined) === 42;

// 👎
// This decoder will never reject an input

Or use it with a function instead of a constant:

const now = always(() => new Date());

now.verify('dummy');  // e.g. new Date('2022-02-07T09:36:58.848Z')

Optionality


# null_: Decoder<null> (source) {: #null_ .signature}

Accepts and returns only the literal null value.

// 👍
null_.verify(null) === null;

// 👎
null_.verify(false);         // throws
null_.verify(undefined);     // throws
null_.verify('hello world'); // throws

# undefined_: Decoder<undefined> (source) {: #undefined_ .signature}

Accepts and returns only the literal undefined value.

// 👍
undefined_.verify(undefined) === undefined;

// 👎
undefined_.verify(null);          // throws
undefined_.verify(false);         // throws
undefined_.verify('hello world'); // throws

# optional<T>(decoder: Decoder<T>): Decoder<T | undefined> (source) {: #optional .signature}

# optional<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source) {: #optional .signature}

Accepts whatever the given decoder accepts, or undefined.

If a default value is explicitly provided, return that instead in the undefined case.

const decoder = optional(string);

// 👍
decoder.verify('hello') === 'hello';
decoder.verify(undefined) === undefined;

// 👎
decoder.verify(null);  // throws
decoder.verify(0);     // throws
decoder.verify(42);    // throws

A typical case where optional() is useful is in decoding objects with optional fields:

object({
    id: number,
    name: string,
    address: optional(string),
});

Which will decode to type:

{
  id: number;
  name: string;
  address?: string;
}

# nullable<T>(decoder: Decoder<T>): Decoder<T | null> (source) {: #nullable .signature}

# nullable<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source) {: #nullable .signature}

Accepts whatever the given decoder accepts, or null.

If a default value is explicitly provided, return that instead in the null case.

const decoder = nullable(string);

// 👍
decoder.verify('hello') === 'hello';
decoder.verify(null) === null;

// 👎
decoder.verify(undefined);  // throws
decoder.verify(0);          // throws
decoder.verify(42);         // throws

Or use it with a default value:

const decoder = nullable(iso8601, () => new Date());

decoder.verify('2022-01-01T12:00:00Z') === '2022-01-01T12:00:00Z';
decoder.verify(null);  // the current date

# maybe<T>(decoder: Decoder<T>): Decoder<T | null | undefined> (source) {: #maybe .signature}

# maybe<T, V>(decoder: Decoder<T>, defaultValue: V | (() => V)): Decoder<T | V> (source) {: #maybe .signature}

Accepts whatever the given decoder accepts, or null, or undefined.

If a default value is explicitly provided, return that instead in the null/undefined case.

const decoder = maybe(string);

// 👍
decoder.verify('hello') === 'hello';
decoder.verify(null) === null;
decoder.verify(undefined) === undefined;

// 👎
decoder.verify(0);   // throws
decoder.verify(42);  // throws

Or use it with a default value:

const decoder = maybe(string, null);

decoder.verify('hello') === 'hello';
decoder.verify(null) === null;
decoder.verify(undefined) === null;

# unknown: Decoder<unknown> (source) {: #unknown .signature}

# mixed: Decoder<unknown> (source) {: #mixed .signature}

Accepts anything and returns it unchanged.

Useful for situation in which you don't know or expect a specific type. Of course, the downside is that you won't know the type of the value statically and you'll have to further refine it yourself.

// 👍
unknown.verify('hello') === 'hello';
unknown.verify(false) === false;
unknown.verify(undefined) === undefined;
unknown.verify([1, 2]) === [1, 2];

// 👎
// This decoder will never reject an input

Arrays


# array(decoder: Decoder<T>): Decoder<T[]> (source) {: #array .signature}

Accepts arrays of whatever the given decoder accepts.

const decoder = array(string);

// 👍
decoder.verify(['hello', 'world']) === ['hello', 'world'];
decoder.verify([]) === [];

// 👎
decoder.verify(['hello', 1.2]);  // throws

# nonEmptyArray(decoder: Decoder<T>): Decoder<[T, ...T[]]> (source) {: #nonEmptyArray .signature}

Like array(), but will reject arrays with 0 elements.

const decoder = nonEmptyArray(string);

// 👍
decoder.verify(['hello', 'world']) === ['hello', 'world'];

// 👎
decoder.verify(['hello', 1.2]);  // throws
decoder.verify([]);              // throws

# poja: Decoder<unknown[]> (source) {: #poja .signature}

Accepts any array, but doesn't validate its items further.

"poja" means "plain old JavaScript array", a play on pojo.

// 👍
poja.verify([1, 'hi', true]) === [1, 'hi', true];
poja.verify(['hello', 'world']) === ['hello', 'world'];
poja.verify([]) === [];

// 👎
poja.verify({});    // throws
poja.verify('hi');  // throws

# tuple<A>(Decoder<A>): Decoder<[A]> (source) {: #tuple .signature}

# tuple<A, B>(Decoder<A>, Decoder<B>): Decoder<[A, B]> (source) {: #tuple .signature}

# tuple<A, B, C>(Decoder<A>, Decoder<B>, Decoder<C>): Decoder<[A, B, C]> (source) {: #tuple .signature}

# tuple<A, B, C, ...>(Decoder<A>, Decoder<B>, Decoder<C>, ...): Decoder<[A, B, C, ...]> (source) {: #tuple .signature}

Accepts a tuple (an array with exactly n items) of values accepted by the n given decoders.

const decoder = tuple(string, number);

// 👍
decoder.verify(['hello', 1.2]) === ['hello', 1.2];

// 👎
decoder.verify([]);                  // throws, too few items
decoder.verify(['hello', 'world']);  // throws, not the right types
decoder.verify(['a', 1, 'c']);       // throws, too many items

# set<T>(decoder: Decoder<T>): Decoder<Set<T>> (source) {: #set .signature}

Similar to array(), but returns the result as an ES6 Set.

const decoder = set(string);

// 👍
decoder.verify(['abc', 'pqr'])  // ≈ new Set(['abc', 'pqr'])
decoder.verify([])              // ≈ new Set([])

// 👎
decoder.verify([1, 2]);         // throws, not the right types

Objects


# object<A, B, ...>({ field1: Decoder<A>, field2: Decoder<B>, ... }): Decoder<{ field1: A, field2: B, ... }> (source) {: #object .signature}

Accepts objects with fields matching the given decoders. Extra fields that exist on the input object are ignored and will not be returned.

const decoder = object({
    x: number,
    y: number,
});

// 👍
decoder.verify({ x: 1, y: 2 }) === { x: 1, y: 2 };
decoder.verify({ x: 1, y: 2, z: 3 }) === { x: 1, y: 2 }; // ⚠️ extra field `z` not returned!

// 👎
decoder.verify({ x: 1 });  // throws, missing field `y`

For more information, see also The difference between object, exact, and inexact.


# exact<A, B, ...>({ field1: Decoder<A>, field2: Decoder<B>, ... }): Decoder<{ field1: A, field2: B, ... }> (source) {: #exact .signature}

Like object(), but will reject inputs that contain extra fields that are not specified explicitly.

const decoder = exact({
    x: number,
    y: number,
});

// 👍
decoder.verify({ x: 1, y: 2 }) === { x: 1, y: 2 };

// 👎
decoder.verify({ x: 1, y: 2, z: 3 });  // throws, extra field `z` not allowed
decoder.verify({ x: 1 });              // throws, missing field `y`

For more information, see also The difference between object, exact, and inexact.


# inexact<A, B, ...>({ field1: Decoder<A>, field2: Decoder<B>, ... }): Decoder<{ field1: A, field2: B, ... }> (source) {: #inexact .signature}

Like object(), but will pass through any extra fields on the input object unvalidated that will thus be of unknown type statically.

const decoder = inexact({
    x: number,
    y: number,
});

// 👍
decoder.verify({ x: 1, y: 2 }) === { x: 1, y: 2 };
decoder.verify({ x: 1, y: 2, z: 3 }) === { x: 1, y: 2, z: 3 };

// 👎
decoder.verify({ x: 1 });  // throws, missing field `y`

For more information, see also The difference between object, exact, and inexact.


# pojo: Decoder<Record<string, unknown>> (source) {: #pojo .signature}

Accepts any "plain old JavaScript object", but doesn't validate its keys or values further.

// 👍
pojo.verify({}) === {};
pojo.verify({ name: 'hi' }) === { name: 'hi' };

// 👎
pojo.verify('hi');        // throws
pojo.verify([]);          // throws
pojo.verify(new Date());  // throws
pojo.verify(null);        // throws

# dict<T>(decoder: Decoder<T>): Decoder<{ [key: string]: T }> (source) {: #dict .signature}

Accepts objects where all values match the given decoder, and returns the result as a Record<string, T>.

The main difference between object() and dict() is that you'd typically use object() if this is a record-like object, where all field names are known and the values are heterogeneous. Whereas with dict() the keys are typically dynamic and the values homogeneous, like in a dictionary, a lookup table, or a cache.

const decoder = dict(number);

// 👍
decoder.verify({ red: 1, blue: 2, green: 3 }); // ≈ { red: 1, blue: 2, green: 3 }

# mapping<T>(decoder: Decoder<T>): Decoder<Map<string, T>> (source) {: #mapping .signature}

Similar to dict(), but returns the result as a Map<string, T> (an ES6 Map) instead.

const decoder = mapping(number);

// 👍
decoder.verify({ red: 1, blue: 2, green: 3 });
// ≈ Map([
//     ['red', '1'],
//     ['blue', '2'],
//     ['green', '3'],
//   ]);

JSON values


# json: Decoder<JSONValue> (source) {: #json .signature}

Accepts any value that's a valid JSON value.

In other words: any value returned by JSON.parse() should decode without failure.

type JSONValue =
    | null
    | string
    | number
    | boolean
    | { [string]: JSONValue }
    | JSONValue[]
// 👍
json.verify({
    name: 'Amir',
    age: 27,
    admin: true,
    image: null,
    tags: ['vip', 'staff'],
});

# jsonObject: Decoder<{ [string]: JSONValue }> (source) {: #jsonObject .signature}

Accepts objects that contain only valid JSON values.

// 👍
jsonObject.verify({});                // ≈ {}
jsonObject.verify({ name: 'Amir' });  // ≈ { name: 'Amir' }

// 👎
jsonObject.verify([]);                   // throws
jsonObject.verify([{ name: 'Alice' }]);  // throws
jsonObject.verify('hello');              // throws
jsonObject.verify(null);                 // throws

# jsonArray: Decoder<JSONValue[]> (source) {: #jsonArray .signature}

Accepts arrays that contain only valid JSON values.

// 👍
jsonArray.verify([]);                  // ≈ []
jsonArray.verify([{ name: 'Amir' }]);  // ≈ [{ name: 'Amir' }]

// 👎
jsonArray.verify({});                 // throws
jsonArray.verify({ name: 'Alice' });  // throws
jsonArray.verify('hello');            // throws
jsonArray.verify(null);               // throws

Unions


# either<A, B>(Decoder<A>, Decoder<B>): Decoder<A | B> (source) {: #either .signature}

# either<A, B, C>(Decoder<A>, Decoder<B>, Decoder<C>): Decoder<A | B | C> (source) {: #either .signature}

# either<A, B, C, ...>(Decoder<A>, Decoder<B>, Decoder<C>, ...): Decoder<A | B | C | ...> (source) {: #either .signature}

Accepts values accepted by any of the given decoders.

The decoders are tried on the input one by one, in the given order. The first one that accepts the input "wins". If all decoders reject the input, the input gets rejected.

const decoder = either(number, string);

// 👍
decoder.verify('hello world') === 'hello world';
decoder.verify(123) === 123;

// 👎
decoder.verify(false);  // throws

Note to Flow users! There is a max of 9 arguments with this construct. If you hit the 9 argument limit, you can work around that by stacking, e.g. do either(<8 arguments here>, either(...)).

In TypeScript, there is no such limit.


# oneOf<T>(values: T[]): Decoder<T> (source) {: #oneOf .signature}

Accepts any value that is strictly-equal (using ===) to one of the specified values.

const decoder = oneOf(['foo', 'bar', 3]);

// 👍
decoder.verify('foo') === 'foo';
decoder.verify(3) === 3;

// 👎
decoder.verify('hello');  // throws
decoder.verify(4);        // throws
decoder.verify(false);    // throws

For example, given an array of strings, like so:

oneOf(['foo', 'bar']);

Note to Flow users! Flow will (unfortunately) infer the type of this definition as Decoder<string>. To work around this, be sure to explicitly annotate the type. Either by doing oneOf([('foo': 'foo'), ('bar': 'bar')]), or as oneOf<'foo' | 'bar'>(['foo', 'bar']). TypeScript will correctly infer the return type as Decoder<'foo' | 'bar'>.


# taggedUnion<A, B, ...>(field: string, mapping: { value1: Decoder<A>, value2: Decoder<B>, ... }): Decoder<A | B | ...> (source) {: #taggedUnion .signature}

If you are decoding tagged unions you may want to use the taggedUnion() decoder instead of the general purpose either() decoder to get better error messages and better performance.

This decoder is optimized for tagged unions, i.e. a union of objects where one field is used as the discriminator.

const A = object({ tag: constant('A'), foo: string });
const B = object({ tag: constant('B'), bar: number });

const AorB = taggedUnion('tag', { A, B });
//                        ^^^

Decoding now works in two steps:

  1. Look at the 'tag' field in the incoming object (this is the field that decides which decoder will be used)
  2. If the value is 'A', then decoder A will be used. If it's 'B', then decoder B will be used. Otherwise, this will fail.

This is effectively equivalent to either(A, B), but will provide better error messages and is more performant at runtime because it doesn't have to try all decoders one by one.


Utilities


# define<T>(fn: (blob: unknown, ok, err) => DecodeResult<T>): Decoder<T> (source) {: #define .signature}

Defines a new Decoder<T>, by implementing a custom acceptance function. The function receives three arguments:

  1. blob - the raw/unknown input (aka your external data)
  2. ok - Call ok(value) to accept the input and return value
  3. err - Call err(message) to reject the input with error message

The expected return value should be a DecodeResult<T>, which can be obtained by returning the result of calling the provided ok or err helper functions. Please note that ok() and err() don't perform side effects! You'll need to return those values.

NOTE: This is the lowest-level API to define a new decoder, and therefore not recommended unless you have a very good reason for it. Most cases can be covered more elegantly by starting from an existing decoder and using .transform() or .refine() on them instead.

// NOTE: Please do NOT implement an uppercase decoder like this! 😇
const uppercase: Decoder<string> = define(
  (blob, ok, err) => {
    if (typeof blob === 'string') {
      // Accept the input
      return ok(blob.toUpperCase());
    } else {
      // Reject the input
      return err('I only accept strings as input');
    }
  }
);

// 👍
uppercase.verify('hi there') === 'HI THERE';

// 👎
uppercase.verify(123);   // throws: 123
                         //         ^^^ I only accept strings as input

The above example is just an example to illustrate how define() works. It would be more idiomatic to implement an uppercase decoder as follows:

const uppercase: Decoder<string> = string.transform(s => s.toUpperCase());

# prep<T>(mapperFn: (raw: mixed) => mixed, decoder: Decoder<T>): Decoder<T> (source) {: #prep .signature}

Pre-process the data input before passing it into the decoder. This gives you the ability to arbitrarily customize the input on the fly before passing it to the decoder. Of course, the input value at that point is still of unknown type, so you will have to deal with that accordingly.

const decoder = prep(
  // Will convert any input to an int first, before feeding it to
  // positiveInteger. This will effectively also allow numeric strings
  // to be accepted (and returned) as integers. If this ever throws,
  // then the error message will be what gets annotated on the input.
  x => parseInt(x),
  positiveInteger,
);

// 👍
decoder.verify(42) === 42;
decoder.verify('3') === 3;

// 👎
decoder.verify('-3');  // throws: not a positive number
decoder.verify('hi');  // throws: not a number

# never: Decoder<never> (source) {: #never .signature}

# fail: Decoder<never> (source) {: #fail .signature}

Rejects all inputs, and always fails with the given error message. May be useful for explicitly disallowing keys, or for testing purposes.

const decoder = object({
  a: string,
  b: optional(never('Key b has been removed')),
});

// 👍
decoder.verify({ a: 'foo' });            // ≈ { a: 'foo' };
decoder.verify({ a: 'foo', c: 'bar' });  // ≈ { a: 'foo' };

// 👎
decoder.verify({ a: 'foo', b: 'bar' });  // throws

# instanceOf<T>(klass: Klass<T>): Decoder<T> (source) {: #instanceOf .signature}

Accepts any value that is an instanceof the given class.

const decoder = instanceOf(Error);

// 👍
const value = new Error('foo');
decoder.verify(value) === value;

// 👎
decoder.verify('foo');  // throws
decoder.verify(3);      // throws

# lazy<T>(decoderFn: () => Decoder<T>): Decoder<T> (source) {: #lazy .signature}

Lazily evaluate the given decoder. This is useful to build self-referential types for recursive data structures.

type Tree = {
    value: string;
    children: Array<Tree>;
    //              ^^^^
    //              Self-reference defining a recursive type
};

const treeDecoder: Decoder<Tree> = object({
    value: string,
    children: array(lazy(() => treeDecoder)),
    //              ^^^^^^^^^^^^^^^^^^^^^^^
    //              Use lazy() like this to refer to the treeDecoder which is
    //              getting defined here
});