funtypes-schemas
TypeScript icon, indicating that this package has built-in type declarations

1.2.0 • Public • Published

funtypes-schemas

Validators and parsers for common types not covered by the base funtypes package.

Build Status Coveralls github branch Rolling Versions NPM version

This package includes all these schemas:

  • 🚀 Base64String
  • 🚀 ChainCodecs
  • 🚀 ConstrainLength
  • 🚀 DateString
  • 🚀 DateTime
  • 🚀 DateTimeString
  • 🚀 Float
  • 🚀 FloatString
  • 🚀 Integer
  • 🚀 IntegerString
  • 🚀 Migrate
  • 🚀 ParsedBase64Array
  • 🚀 ParsedBase64String
  • 🚀 ParsedDateString
  • 🚀 ParsedDateTimeString
  • 🚀 ParsedFloatString
  • 🚀 ParsedIntegerString
  • 🚀 ParsedJsonString
  • 🚀 ParsedUrlString
  • 🚀 Url
  • 🚀 UrlString

Installation

yarn add funtypes-schemas

Usage

Base64String

Validate that a string could represent base64 encoded binary data.

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({data: s.Base64String()});

Valid:

{"data": "abc=="}

🚨 Invalid:

{"data": "!~~"}

ParsedBase64String

Extends Base64String by transparently converting the string between a utf8 string and a base64 representation.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  data: s.ParsedBase64String(),
});

deepEqual(
  MySchema.parse({
    data: 'aGVsbG8gd29ybGQ=',
  }),
  {
    data: 'hello world',
  },
);

deepEqual(
  MySchema.serialize({
    data: 'hello world',
  }),
  {
    data: 'aGVsbG8gd29ybGQ=',
  },
);

ParsedBase64Array

Extends Base64String by transparently converting the string between a Uint8Array and a base64 representation.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  data: s.ParsedBase64Array(),
});

deepEqual(
  MySchema.parse({
    data: 'aGVsbG8gd29ybGQ=',
  }),
  {
    data: new Uint8Array([
      104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
    ]),
  },
);

deepEqual(
  MySchema.serialize({
    data: new Uint8Array([
      104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
    ]),
  }),
  {
    data: 'aGVsbG8gd29ybGQ=',
  },
);

ChainCodecs

Chain multiple codecs together to combine parsers, for example you can parse a Base64 encoded JSON object:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = s.ChainCodecs(
  s.ParsedBase64String(),
  s.ParsedJsonString(t.Object({value: s.Integer()})),
);

// ✅ Valid:
deepEqual(assertMySchema.parse('eyJ2YWx1ZSI6NDJ9'), {value: 42});

// ✅ Valid:
deepEqual(assertMySchema.serialize({value: 42}), 'eyJ2YWx1ZSI6NDJ9');

You can pass as many codecs as you like as parameters to ChainCodecs. They will be applied in order when parsing and in reverse order when serializing.

ConstrainLength

Constrain the length of a base type that has a length property, such as a String or an Array.

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  nonEmptyString: s.ConstrainLength(t.String, {min: 1})
  limitedList: s.ConstrainLength(t.Array(t.String), {max: 3}),
});

Valid:

{
  "nonEmptyString": "hello world",
  "limitedList": ["a", "b", "c"]
}

🚨 Invalid (string must have length >= 1):

{
  "nonEmptyString": "",
  "limitedList": ["a", "b", "c"]
}

🚨 Invalid (array must have length <= 3):

{
  "nonEmptyString": "hello world",
  "limitedList": ["a", "b", "c", "d"]
}

DateString

Represent a date (not including a time) in yyyy-mm-dd format.

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  dateOfBirth: s.DateString({max: `2022-03-15`}),
});

Valid:

{
  "dateOfBirth": "1930-01-15"
}

🚨 Invalid (date is greater than max date):

{
  "dateOfBirth": "2034-01-15"
}

🚨 Invalid (date includes a time):

{
  "dateOfBirth": "2034-01-15T11:30:00Z"
}

🚨 Invalid (date is not valid):

{
  "dateOfBirth": "hello world"
}

ParsedDateString

Equivalent to the DateString schema, except that once you have parsed the value, it is represented as a JavaScript Date object.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  timestamp: s.ParsedDateString(),
});

deepEqual(
  MySchema.parse({
    timestamp: '2022-03-16',
  }),
  {
    timestamp: new Date('2022-03-16T00:00:00.000Z'),
  },
);

deepEqual(
  MySchema.serialize({
    timestamp: new Date('2022-03-16T00:00:00.000Z'),
  }),
  {
    timestamp: '2022-03-16',
  },
);

🚨 Invalid:

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  timestamp: s.ParsedDateString(),
});

// The next line throws an error because there is a time
// component:
MySchema.serialize({
  timestamp: new Date('2022-03-16T13:45:00.000Z'),
});

DateTime

Represent a date time as a JavaScript Date object.

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  timestamp: s.DateTime({min: new Date(`2022-03-15`)}),
});

Valid:

const obj = {
  timestamp: new Date(`2022-03-16T12:03:00Z`),
};

🚨 Invalid (date is less than min date):

const obj = {
  timestamp: new Date(`2022-03-01T12:03:00Z`),
};

🚨 Invalid (date must be valid):

const obj = {
  timestamp: new Date(`2022-03-36`),
};

🚨 Invalid (date cannot be represented as a string):

const obj = {
  timestamp: `2022-03-16T12:03:00Z`,
};

DateTimeString

Represent a date time as a string.

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  timestamp: s.DateTimeString({min: new Date(`2022-03-15`), strict: true}),
});

Valid:

{
  "timestamp": "2022-03-16T12:03:00Z"
}

Valid with strict: false, but 🚨 invalid with strict: true (time zone, i.e. Z, is required):

{
  "timestamp": "2022-03-16T12:03:00"
}
```

✅ Valid with `strict: false` (equivalent to setting time to `00:00:00.000Z`), but 🚨 invalid with `strict: true` (time is required):

```json
{
  "timestamp": "2022-03-16"
}

Valid with strict: false (JavaScript treats this as 2022-03-02T12:03:00.000Z), but 🚨 invalid with strict: true (30th February is not a real date):

{
  "timestamp": "2022-02-30T12:03:00Z"
}

🚨 Invalid (date is less than min date):

{
  "timestamp": "2022-03-01T12:03:00Z"
}

🚨 Invalid (date must be valid):

{
  "timestamp": "2022-03-36"
}

ParsedDateTimeString

Equivalent to the DateTimeString schema, except that once you have parsed the value, it is represented as a JavaScript Date object.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  timestamp: s.ParsedDateTimeString({
    min: new Date(`2022-03-15`),
    strict: true,
  }),
});

deepEqual(
  MySchema.parse({
    timestamp: '2022-03-16T12:03:00Z',
  }),
  {
    timestamp: new Date('2022-03-16T12:03:00Z'),
  },
);

deepEqual(
  MySchema.serialize({
    timestamp: new Date('2022-03-16T12:03:00Z'),
  }),
  {
    timestamp: '2022-03-16T12:03:00.000Z',
  },
);

🚨 Invalid:

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  timestamp: s.ParsedDateTimeString(),
});

// The next line throws an error because the
// timestamp does not represent a valid Date:
MySchema.serialize({
  timestamp: new Date('hello world'),
});

Float

Validate that a number is not NaN and optionally is within the desired range:

  • min defaults to undefined (i.e. no minimum value).
  • max defaults to undefined (i.e. no maximum value).
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  weight: s.Float({min: 0, max: 1}),
});

Valid:

{"weight": 1}

Valid:

{"weight": 0.3456}

🚨 Invalid (value is greater than "max"):

{"weight": 15}

🚨 Invalid (value is not a number):

{"weight": "1"}

FloatString

Validate that a string represents a floating point number (optionally in the desired range). Using a string can be helpful as it allows you to get precision that may be lost in JavaScript number representations.

  • min defaults to undefined (i.e. no minimum value).
  • max defaults to undefined (i.e. no maximum value).
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  weight: s.FloatString({min: `0`, max: `1`}),
});

Valid:

{"weight": "1"}

Valid:

{"weight": "0.3456"}

🚨 Invalid (value does not represent a floating point number):

{"weight": "hello world"}

🚨 Invalid (value is greater than "max"):

{"weight": "15"}

🚨 Invalid (value is not a string):

{"weight": 1}

ParsedFloatString

Equivalent to the FloatString schema, except that once you have parsed the value, it is represented as a JavaScript number. This may result in a loss of precision.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  weight: s.ParsedFloatString({min: 0, max: 1}),
});

deepEqual(
  MySchema.parse({
    level: '0.5',
  }),
  {
    level: 0.5,
  },
);

deepEqual(
  MySchema.serialize({
    level: 0.5,
  }),
  {
    level: '0.5',
  },
);

🚨 Invalid:

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  level: s.ParsedIntegerString({min: 1, max: 6}),
});

// The next line throws an error because the value is outside the requested range:
MySchema.serialize({
  level: 42,
});

Integer

Validate that a number is an integer (optionally within the desired range)

  • min defaults to Number.MIN_SAFE_INTEGER (i.e. -9007199254740991). It can be set to a higher value, but cannot be set to a lower value.
  • max defaults to Number.MAX_SAFE_INTEGER (i.e. 9007199254740991). It can be set to a lower value, but cannot be set to a higher value.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  level: s.Integer({min: 1, max: 6}),
});

Valid:

{"level": 1}

🚨 Invalid (value is not an integer):

{"level": 1.5}

🚨 Invalid (value is greater than "max"):

{"level": 15}

🚨 Invalid (value is not a number):

{"level": "1"}

IntegerString

Validate that a string contains an integer, (optionally within the desired range)

  • min defaults to Number.MIN_SAFE_INTEGER (i.e. '-9007199254740991'). It can be set to any integer, even if that integer cannot be represented as a number in JavaScript.
  • max defaults to Number.MAX_SAFE_INTEGER (i.e. '9007199254740991'). It can be set to any integer, even if that integer cannot be represented as a number in JavaScript.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  id: s.Integer({
    min: `-9999999999999999999999999`,
    max: `9999999999999999999999999`,
  }),
});

Valid:

{"id": "99"}

Valid (integer strings can be used to represent numbers that are larger than Number.MAX_SAFE_INTEGER):

{"id": "9999999999999999999999978"}

🚨 Invalid (value is not an integer):

{"level": "1.5"}

🚨 Invalid (value is not a string):

{"level": 15}

🚨 Invalid (value is greater than "max"):

{"level": "9999999999999999999999999999999999999"}

ParsedIntegerString

Equivalent to the IntegerString schema, except that once you have parsed the value, it is represented as a JavaScript number. This restricts your integers to between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  level: s.ParsedIntegerString({min: 1, max: 6}),
});

deepEqual(
  MySchema.parse({
    level: '3',
  }),
  {
    level: 3,
  },
);

deepEqual(
  MySchema.serialize({
    level: 3,
  }),
  {
    level: '3',
  },
);

🚨 Invalid:

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  level: s.ParsedIntegerString({min: 1, max: 6}),
});

// The next line throws an error because the value is not an integer:
MySchema.serialize({
  level: 3.14,
});

ParsedJsonString

Transparently parse/serialize to/from JSON. A codec can optionally be provided to handle the parsed value.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = s.ParsedJsonString(
  t.Object({
    level: s.ParsedIntegerString(),
  }),
);

deepEqual(MySchema.parse(`{"level": "3"}`), {
  level: 3,
});

deepEqual(
  MySchema.serialize({
    level: 3,
  }),
  `{"level":"3"}`,
);

🚨 Invalid:

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = s.ParsedJsonString(
  t.Object({
    level: s.ParsedIntegerString(),
  }),
);

// The next line throws an error because the string is not valid JSON:
MySchema.parse(`{level: '3'}`);

// The next line throws an error because the value is not an integer:
MySchema.parse(`{"level": "3.14"}`);

// The next line throws an error because the value is not an integer:
MySchema.serialize({
  level: 3.14,
});

Migrate

A simplified alternative to ParsedValue/.withParser for migrating legacy data. The Migrate cannot be serialized, so it's best used in a Union where one of the other types in the union handles serialization.

🚀 Migrating an object to a new schema:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Union(
  t.Object({
    version: t.Literal(2),
    width: t.Number,
    height: t.Number,
  }),
  s.Migrate(
    t.Object({
      version: t.Literal(1),
      size: t.Number,
    }),
    ({size}) => ({
      version: 2,
      width: size,
      height: size,
    }),
  ),
);

// ✅ Valid:
deepEqual(
  MySchema.parse({
    version: 2,
    width: 10,
    height: 15,
  }),
  {
    version: 2,
    width: 10,
    height: 15,
  },
);

// ✅ Valid:
deepEqual(
  MySchema.parse({
    version: 1,
    size: 42,
  }),
  {
    version: 2,
    width: 42,
    height: 42,
  },
);

// ✅ Valid:
deepEqual(
  MySchema.serialize({
    version: 2,
    width: 10,
    height: 15,
  }),
  {
    version: 2,
    width: 10,
    height: 15,
  },
);

🚀 Setting a default for an optional property:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  values: t.Union(
    t.Array(t.Number),
    s.Migrate(t.Undefined, () => []),
  ),
});

// ✅ Valid:
deepEqual(MySchema.parse({values: [1, 2, 3]}), {values: [1, 2, 3]});

// ✅ Valid:
deepEqual(MySchema.parse({values: []}), {values: []});

// ✅ Valid:
deepEqual(MySchema.parse({values: undefined}), {values: []});

// ✅ Valid:
deepEqual(MySchema.parse({}), {values: []});

// ✅ Valid:
deepEqual(MySchema.serialize({values: [1, 2, 3]}), {values: [1, 2, 3]});

// ✅ Valid:
deepEqual(MySchema.serialize({values: []}), {values: []});

// 🚨 Invalid:
deepEqual(MySchema.serialize({values: undefined}), {values: []});

// 🚨 Invalid:
deepEqual(MySchema.serialize({}), {values: []});

Url

Represent an absolute URL as a JavaScript URL object. You can pass options to constrain the valid URLs:

  • allowedProtocols - Set of valid protocols, e.g. new Set(['http:', 'https:']). Defaults to allowing any protocol.
  • allowedHosts - Set of valid hosts, e.g. new Set(['example.com']). Defaults to allowing any host.
  • allowedOrigins - Set of allowed origins, e.g. new Set('https://example.com'). Defaults to allowing any origin.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  href: s.Url({allowedProtocols: new Set([`http:`, `https:`])}),
});

Valid:

const obj = {
  href: new URL(`http://example.com`),
};

🚨 Invalid (protocol does not match teh allowed protocols):

const obj = {
  href: new URL(`tel:0000000000`),
};

🚨 Invalid (value is not a URL):

const obj = {
  href: `http://example.com`,
};

UrlString

Represent an absolute URL as a string. You can pass options to constrain the valid URLs:

  • allowedProtocols - Set of valid protocols, e.g. new Set(['http:', 'https:']). Defaults to allowing any protocol.
  • allowedHosts - Set of valid hosts, e.g. new Set(['example.com']). Defaults to allowing any host.
  • allowedOrigins - Set of allowed origins, e.g. new Set('https://example.com'). Defaults to allowing any origin.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  href: s.UrlString({allowedProtocols: new Set([`http:`, `https:`])}),
});

Valid (will be normalized to add a trailing / when parsing):

{"href": "http://example.com"}

Valid:

{"href": "http://example.com/foo/bar"}

🚨 Invalid (protocol does not match teh allowed protocols):

{"href": "tel:0000000000"}

🚨 Invalid (value is not a string):

const obj = {
  href: new URL(`http://example.com`),
};

ParsedUrlString

Equivalent to the UrlString schema, except that once you have parsed the value, it is represented as a JavaScript URL object.

Valid:

import {deepEqual} from 'assert';

import * as t from 'funtypes';
import * as s from 'funtypes-schemas';

const MySchema = t.Object({
  href: s.ParsedUrlString(),
});

deepEqual(
  MySchema.parse({
    href: 'https://example.com/foo/bar',
  }),
  {
    href: new URL('https://example.com/foo/bar'),
  },
);

deepEqual(
  MySchema.serialize({
    href: new URL('https://example.com/foo/bar'),
  }),
  {
    href: 'https://example.com/foo/bar',
  },
);

Dependencies (0)

    Dev Dependencies (19)

    Package Sidebar

    Install

    npm i funtypes-schemas

    Weekly Downloads

    36

    Version

    1.2.0

    License

    MIT

    Unpacked Size

    89.1 kB

    Total Files

    29

    Last publish

    Collaborators

    • forbeslindesay
    • funtypes