Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

babel-plugin-spectypes

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

babel-plugin-spectypes - npm Package Compare versions

Comparing version 0.0.48 to 1.0.0

6

dist/cjs/index.js

@@ -8,9 +8,10 @@ "use strict";

const transform_1 = require("./transform");
const packageName = 'spectypes';
const defaultPackageName = 'spectypes';
function plugin({ types: t }) {
return {
name: packageName,
name: 'spectypes',
visitor: {
VariableDeclarator(path, state) {
var _a;
const { packageName = defaultPackageName } = state.opts;
if (t.isCallExpression(path.node.init) &&

@@ -42,2 +43,3 @@ t.isIdentifier(path.node.init.callee) &&

var _a;
const { packageName = defaultPackageName } = state.opts;
if (path.node.source.value === packageName) {

@@ -44,0 +46,0 @@ (_a = state.specNames) !== null && _a !== void 0 ? _a : (state.specNames = {});

@@ -221,13 +221,13 @@ "use strict";

case 'optional':
return parseUnary('optional', expression, [...rejects, 'optional', 'filter'], context);
return parseUnary('optional', expression, [...rejects, 'optional', 'filter', 'lazy'], context);
case 'array':
return parseArray(expression, [...rejects, 'optional'], context);
return parseArray(expression, [...rejects, 'optional', 'lazy'], context);
case 'tuple':
return parseUnaryArray('tuple', expression, [...rejects, 'optional', 'filter'], context);
return parseUnaryArray('tuple', expression, [...rejects, 'optional', 'filter', 'lazy'], context);
case 'union':
return parseUnaryArray('union', expression, [...rejects, 'optional', 'filter', 'unknown', 'union'], context);
return parseUnaryArray('union', expression, [...rejects, 'optional', 'filter', 'unknown', 'union', 'lazy'], context);
case 'object':
return parseUnaryObject('object', expression, [...rejects, 'filter'], context);
return parseUnaryObject('object', expression, [...rejects, 'filter', 'lazy'], context);
case 'struct':
return parseUnaryObject('struct', expression, [...rejects, 'filter'], context);
return parseUnaryObject('struct', expression, [...rejects, 'filter', 'lazy'], context);
case 'record':

@@ -250,5 +250,5 @@ case 'UNSAFE_record':

'boolean'
], [...rejects, 'optional'], context);
], [...rejects, 'optional', 'lazy'], context);
case 'lazy':
return parseLazy(expression, [...rejects, 'filter'], context);
return parseLazy(expression, [...rejects, 'filter', 'lazy'], context);
case 'template':

@@ -277,5 +277,5 @@ return (0, exports.parseTemplate)(expression, [

case 'map':
return parseFunction('map', expression, [...rejects, 'optional', 'filter'], context);
return parseFunction('map', expression, [...rejects, 'optional', 'filter', 'lazy'], context);
case 'limit':
return parseFunction('limit', expression, [...rejects, 'optional', 'filter'], context);
return parseFunction('limit', expression, [...rejects, 'optional', 'filter', 'lazy'], context);
case 'writable':

@@ -282,0 +282,0 @@ return parseUnary('writable', expression, [...rejects, 'filter'], context);

@@ -6,4 +6,7 @@ import type babelCore from '@babel/core';

spectypesImport?: babelCore.types.Identifier;
opts: {
readonly packageName?: string;
};
};
export default function plugin({ types: t }: typeof babelCore): babelCore.PluginObj<State>;
export {};
{
"name": "babel-plugin-spectypes",
"version": "0.0.48",
"version": "1.0.0",
"private": false,

@@ -30,3 +30,3 @@ "description": "Babel plugin that compiles spectypes validators",

"@babel/core": "*",
"spectypes": "0.0.48"
"spectypes": "1.0.0"
},

@@ -46,3 +46,3 @@ "devDependencies": {

"hash-sum": "^2.0.0",
"spectypes": "0.0.48",
"spectypes": "1.0.0",
"spectypes-configs": "0.0.0",

@@ -67,3 +67,3 @@ "string-hash": "^1.1.3",

},
"readme": "# spectypes\n\n[![npm](https://img.shields.io/npm/v/spectypes)](https://npm.im/spectypes)\n[![build](https://github.com/iyegoroff/spectypes/workflows/build/badge.svg)](https://github.com/iyegoroff/spectypes/actions/workflows/build.yml)\n[![publish](https://github.com/iyegoroff/spectypes/workflows/publish/badge.svg)](https://github.com/iyegoroff/spectypes/actions/workflows/publish.yml)\n[![codecov](https://codecov.io/gh/iyegoroff/spectypes/branch/main/graph/badge.svg?t=1520230083925)](https://codecov.io/gh/iyegoroff/spectypes)\n[![Type Coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fiyegoroff%2Fspectypes%2Fmain%2Fpackage.json)](https://github.com/plantain-00/type-coverage)\n![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/spectypes)\n[![npm](https://img.shields.io/npm/l/spectypes.svg?t=1495378566925)](https://www.npmjs.com/package/spectypes)\n\nFast, compiled, eval-free data validator/transformer\n\n---\n\n## Features\n\n- <b>really fast</b>, can be even [faster](/benchmark) than `ajv`\n- <b>detailed errors</b>, failure will result into explicit error message(s) and path to invalid data\n- <b>extensively tested</b>, each release undergoes more than 900 `fast-check` powered [tests](#how-is-it-tested)\n- <b>precise types</b>, accurately infers all types and provides readable compile-time error messages\n- <b>browser friendly</b>, uses `babel` to compile validators, so no `eval` or `new Function` involved\n- <b>easily extensible</b>, [custom validators](#custom-validators) are created by mixing existing ones\n\n## Getting started\n\n1. There are two packages to install - `spectypes`, which contains type definitions and small set of runtime helpers and `babel-plugin-spectypes`, which parses and compiles validators into functions:\n\n ```\n npm i spectypes\n npm i babel-plugin-spectypes -D\n ```\n\n2. Add `babel-plugin-spectypes` to plugins section in your `babel` config:\n\n ```diff\n \"plugins\": [\n + \"babel-plugin-spectypes\"\n ]\n ```\n\n## Example\n\n```ts\nimport { array, number } from 'spectypes'\n\nconst check = array(number)\n\nexpect(check([1, 2, 3])).toEqual({\n tag: 'success',\n success: [1, 2, 3] // readonly number[]\n})\n\nexpect(check({ 0: 1 })).toEqual({\n tag: 'failure',\n failure: {\n value: { 0: 1 }, // unknown\n errors: [{ issue: 'not an array', path: [] }]\n }\n})\n\nexpect(check([1, 2, '3', false])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, '3', false], // unknown\n errors: [\n { issue: 'not a number', path: [2] },\n { issue: 'not a number', path: [3] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes';\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n for (let index = 0; index < value.length; index++) {\n const value_index = value[index]\n\n if (typeof value_index !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: [index]\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n\n...\n```\n\n</details>\n\n---\n\n## Reference\n\nPrimitive validators\n\n- [boolean](#boolean)\n- [literal](#literal)\n- [nullish](#nullish)\n- [number](#number)\n- [string](#string)\n- [unknown](#unknown)\n\nComplex validators\n\n- [array](#array)\n- [filter](#filter)\n- [limit](#limit)\n- [map](#map)\n- [merge](#merge)\n- [object](#object)\n- [optional](#optional)\n- [record](#record)\n- [struct](#struct)\n- [template](#template)\n- [tuple](#tuple)\n- [union](#union)\n\nUtilities\n\n- [lazy](#lazy)\n- [transformer](#transformer)\n- [validator](#validator)\n- [writable](#writable)\n- [Spectype](#Spectype)\n\n### Primitive validators\n\n#### boolean\n\nValidates a boolean value\n\n```ts\nimport { boolean } from 'spectypes'\n\nconst check = boolean\n\nexpect(check(true)).toEqual({\n tag: 'success',\n success: true\n})\n\nexpect(check('false')).toEqual({\n tag: 'failure',\n failure: {\n value: 'false',\n errors: [{ issue: 'not a boolean', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### literal\n\nCreates a literal validator spec. `literal`can validate strings, numbers, booleans, undefined and null. `literal(undefined)` is treated [specially](#special-cases) when used as a property validator inside `object` or `struct`.\n\n```ts\nimport { literal } from 'spectypes'\n\nconst check = literal('test')\n\nexpect(check('test')).toEqual({\n tag: 'success',\n success: 'test'\n})\n\nexpect(check('temp')).toEqual({\n tag: 'failure',\n failure: {\n value: 'temp',\n errors: [{ issue: \"not a 'test' string literal\", path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (value !== 'test') {\n ;(err = err || []).push({\n issue: \"not a '\" + 'test' + \"' string literal\",\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### nullish\n\nTransformer spec, that accepts `undefined` and `null` values and maps them to `undefined`.\n`nullish` is treated [specially](#special-cases) when used as a property validator inside `object` or `struct`.\n\n```ts\nimport { nullish } from 'spectypes'\n\nconst check = nullish\n\nexpect(check(undefined)).toEqual({\n tag: 'success'\n success: undefined\n})\n\nexpect(check(null)).toEqual({\n tag: 'success'\n success: undefined\n})\n\nexpect(check(123)).toEqual({\n tag: 'failure',\n failure: {\n value: 'temp',\n errors: [{ issue: \"not 'null' or 'undefined'\", path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err, result\n\n if (value !== null && value !== undefined) {\n ;(err = err || []).push({\n issue: \"not 'null' or 'undefined'\",\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### number\n\nValidates a number value.\n\n```ts\nimport { number } from 'spectypes'\n\nconst check = number\n\nexpect(check(0)).toEqual({\n tag: 'success',\n success: 0\n})\n\nexpect(check({})).toEqual({\n tag: 'failure',\n failure: {\n value: {},\n errors: [{ issue: 'not a number', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### string\n\nValidates a string value.\n\n```ts\nimport { string } from 'spectypes'\n\nconst check = string\n\nexpect(check('')).toEqual({\n tag: 'success',\n success: ''\n})\n\nexpect(check(null)).toEqual({\n tag: 'failure',\n failure: {\n value: null,\n errors: [{ issue: 'not a string', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### unknown\n\nEmpty validator spec. `unknown` is treated [specially](#special-cases) when used as a property validator inside `object` or `struct`.\n\n```ts\nimport { unknown } from 'spectypes'\n\nconst check = unknown\n\nexpect(check('anything')).toEqual({\n tag: 'success',\n success: 'anything'\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n### Complex validators\n\n#### array\n\nCreates an array validator spec. Takes a spec to validate each item of an array.\n\nSee [example](#example)\n\n---\n\n#### filter\n\nCan be used only as an argument for `array` and `record` to create filtered transformer specs. Filtering happens after each item or key validation. Takes a spec to validate each item or key of a collection and filter predicate.\n\n```ts\nimport { array, number, filter } from 'spectypes'\n\nconst check = array(filter(number, (x) => x > 1))\n\nexpect(check([1, 2, 3])).toEqual({\n tag: 'success',\n success: [2, 3]\n})\n\nexpect(check([1, 2, null])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, null],\n errors: [{ issue: 'not a number', path: [2] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _filter = (x) => x > 1\n\nconst check = (value) => {\n let err, result\n result = []\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n let filterindex = 0\n\n for (let index = 0; index < value.length; index++) {\n const value_index = value[index]\n\n if (typeof value_index !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: [index]\n })\n }\n\n if (!err && _filter(value_index)) {\n result[filterindex++] = value_index\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\nType predicate will be taken into account if provided\n\n```ts\nimport { array, string, filter } from 'spectypes'\n\nconst check = array(filter(string, (x): x is 'test' => x === 'test'))\n\nexpect(check(['hello', 'test', 'world'])).toEqual({\n tag: 'success',\n success: ['test'] // readonly 'test'[]\n})\n```\n\n---\n\n#### limit\n\nCreates a spec with custom constraint. Takes a basis spec and a function to perform additinal validation.\n\n```ts\nimport { number, limit } from 'spectypes'\n\nconst check = limit(number, (x) => x > 1)\n\nexpect(check(5)).toEqual({\n tag: 'success',\n success: 5\n})\n\nexpect(check(-5)).toEqual({\n tag: 'failure',\n failure: {\n value: -5,\n errors: [{ issue: 'does not fit the limit', path: [] }]\n }\n})\n\nexpect(check('5')).toEqual({\n tag: 'failure',\n failure: {\n value: '5',\n errors: [{ issue: 'not a number', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _limit = (x) => x > 1\n\nconst check = (value) => {\n let err\n let error0\n\n if (typeof value !== 'number') {\n error0 = true\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n }\n\n if (!error0 && !_limit(value)) {\n ;(err = err || []).push({\n issue: 'does not fit the limit',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\nType predicate will be taken into account if provided\n\n```ts\nimport { array, string, limit } from 'spectypes'\n\nconst check = array(limit(string, (x): x is 'test' => x === 'test'))\n\nexpect(check(['test', 'test', 'test'])).toEqual({\n tag: 'success',\n success: ['test', 'test', 'test'] // readonly 'test'[]\n})\n```\n\n---\n\n#### map\n\nCreates a spec that transforms the result of successful validation. Takes basis spec and mapping function.\n\n```ts\nimport { number, map } from 'spectypes'\n\nconst check = map(number, (x) => x + 1)\n\nexpect(check(10)).toEqual({\n tag: 'success',\n success: 11\n})\n\nexpect(check(undefined)).toEqual({\n tag: 'failure',\n failure: {\n value: undefined,\n errors: [{ issue: 'not a number', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _map = (x) => x + 1\n\nconst check = (value) => {\n let err, result\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n }\n\n if (!err) {\n result = _map(value)\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### merge\n\nCan combine `tuple` with `array` or `object` with `record` into single spec.\n\n```ts\nimport { tuple, array, string, boolean, merge } from 'spectypes'\n\nconst check = merge(tuple(string, string), array(boolean))\n\nexpect(check(['hello', 'world', true])).toEqual({\n tag: 'success',\n success: ['hello', 'world', true]\n})\n\nexpect(check(['hello', 'world', '!'])).toEqual({\n tag: 'failure',\n failure: {\n value: ['hello', 'world', '!'],\n errors: [{ issue: 'not a string', path: [2] }]\n }\n})\n\nexpect(check(['hello'])).toEqual({\n tag: 'failure',\n failure: {\n value: ['hello'],\n errors: [{ issue: 'length is less than 2', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else if (value.length < 2) {\n ;(err = err || []).push({\n issue: 'length is less than ' + 2,\n path: []\n })\n } else {\n const value_$30_ = value[0]\n\n if (typeof value_$30_ !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: [0]\n })\n }\n\n const value_$31_ = value[1]\n\n if (typeof value_$31_ !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: [1]\n })\n }\n\n for (let index = 2; index < value.length; index++) {\n const value_index = value[index]\n\n if (typeof value_index !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [index]\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n```ts\nimport { object, record, number, string, boolean, merge } from 'spectypes'\n\nconst check = merge(object({ x: number }), record(string, boolean))\n\nexpect(check({ x: 123, y: true })).toEqual({\n tag: 'success',\n success: { x: 123, y: true }\n})\n\nexpect(check({ x: true, y: 123 })).toEqual({\n tag: 'failure',\n failure: {\n value: { x: true, y: 123 },\n errors: [\n { issue: 'not a number', path: ['x'] },\n { issue: 'not a boolean', path: ['y'] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n for (let i = 0; i < _spectypes.bannedKeys.length; i++) {\n const ban = _spectypes.bannedKeys[i]\n\n if (Object.prototype.hasOwnProperty.call(value, ban)) {\n ;(err = err || []).push({\n issue: \"includes banned '\" + ban + \"' key\",\n path: []\n })\n }\n }\n\n const value_x = value.x\n\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n\n for (const key in value) {\n if (!(key === 'x')) {\n const value_key = value[key]\n\n if (typeof value_key !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [key]\n })\n }\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### object\n\nCreates an object validator spec. Validation will fail if validated object has a property set different from the one specified. Takes an object with specs to validate object properties. `literal(undefined)`, `nullish` and `unknown` are treated [specially](#special-cases) when used as a property validator inside `object`.\n\n```ts\nimport { object, number, string, boolean } from 'spectypes'\n\nconst check = object({ x: number, y: string, z: boolean })\n\nexpect(check({ x: 1, y: '2', z: false })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: false }\n})\n\nexpect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({\n tag: 'failure',\n failure: {\n value: { x: 1, y: '2', z: false, xyz: [] },\n errors: [{ issue: 'excess key - xyz', path: [] }]\n }\n})\n\nexpect(check({})).toEqual({\n tag: 'failure',\n failure: {\n value: {},\n errors: [\n { issue: 'not a number', path: ['x'] },\n { issue: 'not a string', path: ['y'] },\n { issue: 'not a boolean', path: ['z'] }\n ]\n }\n})\n\nexpect(check([])).toEqual({\n tag: 'failure',\n failure: {\n value: [],\n errors: [{ issue: 'not an object', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_x = value.x\n\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n\n const value_y = value.y\n\n if (typeof value_y !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: ['y']\n })\n }\n\n const value_z = value.z\n\n if (typeof value_z !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: ['z']\n })\n }\n\n for (const key in value) {\n if (!(key === 'x' || key === 'y' || key === 'z')) {\n ;(err = err || []).push({\n issue: 'excess key - ' + key,\n path: []\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### optional\n\nCreates an optional object property validator spec. Can be used only inside `object` and `struct` arguments. Will not produce any validation errors if property equals `undefined` or is not present in the validated object.\n\n```ts\nimport { optional, struct, number } from 'spectypes'\n\nconst check = struct({ x: optional(number) })\n\nexpect(check({ x: 5 })).toEqual({\n tag: 'success',\n success: { x: 5 }\n})\n\nexpect(check({ x: undefined })).toEqual({\n tag: 'success',\n success: { x: undefined }\n})\n\nexpect(check({})).toEqual({\n tag: 'success',\n success: {}\n})\n\nexpect(check({ x: 'x' })).toEqual({\n tag: 'failure',\n failure: {\n value: { x: 'x' },\n errors: [{ issue: 'not a number', path: ['x'] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err, result\n result = {}\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_x = value.x\n\n if ('x' in value) {\n if (value_x !== undefined) {\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n }\n\n result.x = value_x\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### record\n\nCreates a record validator spec. This validator is protected from prototype pollution and validation will fail if validated object contains properties that override `Object.proptotype` methods. This function has two signatures - one takes a spec to validate each key of a record and a spec to validate each item, another takes only item spec and treats all keys as strings. Key spec can be a `string`, `template`, string `literal` or `union` of these specs.\n\n```ts\nimport { record, boolean } from 'spectypes'\n\nconst check = record(boolean)\n\nexpect(check({ foo: false, bar: true })).toEqual({\n tag: 'success',\n success: { foo: false, bar: true }\n})\n\nexpect(check(true)).toEqual({\n tag: 'failure',\n failure: {\n value: true,\n errors: [{ issue: 'not an object', path: [] }]\n }\n})\n\nexpect(check({ toString: true })).toEqual({\n tag: 'failure',\n failure: {\n value: { toString: true },\n errors: [{ issue: \"includes banned 'toString' key\", path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n for (let i = 0; i < _spectypes.bannedKeys.length; i++) {\n const ban = _spectypes.bannedKeys[i]\n\n if (Object.prototype.hasOwnProperty.call(value, ban)) {\n ;(err = err || []).push({\n issue: \"includes banned '\" + ban + \"' key\",\n path: []\n })\n }\n }\n\n for (const key in value) {\n const value_key = value[key]\n\n if (typeof value_key !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [key]\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### struct\n\nCreates an object transformer spec. All properties of validated object that are not present in passed param will be removed from the result of successful validation. Takes an object with specs to validate object properties. `literal(undefined)`, `nullish` and `unknown` are treated [specially](#special-cases) when used as a property validator inside `struct`.\n\n```ts\nimport { struct, number, string, boolean } from 'spectypes'\n\nconst check = struct({ x: number, y: string, z: boolean })\n\nexpect(check({ x: 1, y: '2', z: false })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: false }\n})\n\nexpect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: false }\n})\n\nexpect(check({})).toEqual({\n tag: 'failure',\n failure: {\n value: {},\n errors: [\n { issue: 'not a number', path: ['x'] },\n { issue: 'not a string', path: ['y'] },\n { issue: 'not a boolean', path: ['z'] }\n ]\n }\n})\n\nexpect(check([])).toEqual({\n tag: 'failure',\n failure: {\n value: [],\n errors: [{ issue: 'not an object', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err, result\n result = {}\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_x = value.x\n\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n\n result.x = value_x\n const value_y = value.y\n\n if (typeof value_y !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: ['y']\n })\n }\n\n result.y = value_y\n const value_z = value.z\n\n if (typeof value_z !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: ['z']\n })\n }\n\n result.z = value_z\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### template\n\nCreates a template string validator spec. Takes `number`, `string`, `boolean`, `literal` specs and their` union`s to validate parts of the validated string.\n\n```ts\nimport { template, literal, number, string, boolean } from 'spectypes'\n\nconst check = template(literal('test'), string, number, boolean)\n\nexpect(check('test___123false')).toEqual({\n tag: 'success',\n success: 'test___123false'\n})\n\nexpect(check('test___false')).toEqual({\n tag: 'failure',\n failure: {\n value: 'test___false',\n errors: [{ issue: 'template literal mismatch', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _template = new RegExp(\n '^' +\n _spectypes.escape('test') +\n _spectypes.stringTest +\n _spectypes.numberTest +\n _spectypes.booleanTest +\n '$'\n)\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: []\n })\n } else if (!_template.test(value)) {\n ;(err = err || []).push({\n issue: 'template literal mismatch',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### tuple\n\nCreates a tuple validator spec. Takes specs to validate tuple parts.\n\n```ts\nimport { tuple, number, string, boolean } from 'spectypes'\n\nconst check = tuple(number, string, boolean)\n\nexpect(check([1, '2', false])).toEqual({\n tag: 'success',\n success: [1, '2', false]\n})\n\nexpect(check([])).toEqual({\n tag: 'failure',\n failure: {\n value: [],\n errors: [{ issue: 'length is not 3', path: [] }]\n }\n})\n\nexpect(check([1, '2', false, 1000])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, '2', false, 1000],\n errors: [{ issue: 'length is not 3', path: [] }]\n }\n})\n\nexpect(check(['1', '2', 'false'])).toEqual({\n tag: 'failure',\n failure: {\n value: ['1', '2', 'false'],\n errors: [\n { issue: 'not a number', path: [0] },\n { issue: 'not a boolean', path: [2] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else if (value.length !== 3) {\n ;(err = err || []).push({\n issue: 'length is not ' + 3,\n path: []\n })\n } else {\n const value_$30_ = value[0]\n\n if (typeof value_$30_ !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: [0]\n })\n }\n\n const value_$31_ = value[1]\n\n if (typeof value_$31_ !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: [1]\n })\n }\n\n const value_$32_ = value[2]\n\n if (typeof value_$32_ !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [2]\n })\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### union\n\nCreates a union validator spec. Takes specs to validate union cases.\n\n```ts\nimport { union, number, string, boolean } from 'spectypes'\n\nconst check = union(number, string, boolean)\n\nexpect(check('temp')).toEqual({\n tag: 'success',\n success: 'temp'\n})\n\nexpect(check(true)).toEqual({\n tag: 'success',\n success: true\n})\n\nexpect(check(null)).toEqual({\n tag: 'failure',\n failure: {\n value: null,\n errors: [\n { issue: 'union case #0 mismatch: not a number', path: [] },\n { issue: 'union case #1 mismatch: not a string', path: [] },\n { issue: 'union case #2 mismatch: not a boolean', path: [] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n let unmatched\n\n if (typeof value !== 'number') {\n unmatched = true\n }\n\n if (unmatched) {\n unmatched = false\n\n if (typeof value !== 'string') {\n unmatched = true\n }\n }\n\n if (unmatched) {\n unmatched = false\n\n if (typeof value !== 'boolean') {\n unmatched = true\n }\n }\n\n if (unmatched) {\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'union case #0 mismatch: not a number',\n path: []\n })\n }\n\n if (typeof value !== 'string') {\n ;(err = err || []).push({\n issue: 'union case #1 mismatch: not a string',\n path: []\n })\n }\n\n if (typeof value !== 'boolean') {\n ;(err = err || []).push({\n issue: 'union case #2 mismatch: not a boolean',\n path: []\n })\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n### Utilities\n\n#### transformer\n\nSpec that tells `babel` plugin to generate a wrapper for an external transformer spec. Any spec containing `struct`, `nullish`, `map`, `filter` and `transformer` specs will create and return new object on successful validation. Such spec has to be wrapped with `transformer` when used inside another spec.\n\n```ts\nimport { array, transformer, map, number } from 'spectypes'\n\nconst negated = map(number, (x) => -x)\nconst check = array(transformer(negated))\n\n// Incorrect usage !!!\n// const negated = transformer(map(number, (x) => -x))\n// const check = array(negated)\n\nexpect(check([1, 2, -3])).toEqual({\n tag: 'success',\n success: [-1, -2, 3]\n})\n\nexpect(check([1, 2, 'abc'])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, 'abc'],\n errors: [{ issue: 'not a number', path: [2] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _map = (x) => -x\n\nconst negated = (value) => {\n let err, result\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n }\n\n if (!err) {\n result = _map(value)\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n\nconst check = (value) => {\n let err, result\n result = []\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n for (let index = 0; index < value.length; index++) {\n let result_index\n const value_index = value[index]\n const ext_value_index0 = negated(value_index)\n\n if (ext_value_index0.tag === 'failure') {\n ;(err = err || []).push(\n ...ext_value_index0.failure.errors.map((fail) => ({\n issue: '' + fail.issue,\n path: [index, ...fail.path]\n }))\n )\n } else {\n result_index = ext_value_index0.success\n }\n\n result[index] = result_index\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### validator\n\nSpec that tells `babel` plugin to generate a wrapper for an external validator spec. Any spec <b>not</b> containing `struct`, `nullish`, `map`, `filter` and `transformer` specs on successful validation will return validated object. Such spec has to be wrapped with `validator` when used inside another spec.\n\n```ts\nimport { array, validator, limit, number } from 'spectypes'\n\nconst positive = limit(number, (x) => x >= 0)\nconst check = array(validator(positive))\n\n// Incorrect usage !!!\n// const positive = validator(limit(number, (x) => x >= 0))\n// const check = array(positive)\n\nexpect(check([0, 1, 2])).toEqual({\n tag: 'success',\n success: [0, 1, 2]\n})\n\nexpect(check([-1, -2, -3])).toEqual({\n tag: 'failure',\n failure: {\n value: [-1, -2, -3],\n errors: [\n { issue: 'does not fit the limit', path: [0] },\n { issue: 'does not fit the limit', path: [1] },\n { issue: 'does not fit the limit', path: [2] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _limit = (x) => x >= 0\n\nconst positive = (value) => {\n let err\n let error0\n\n if (typeof value !== 'number') {\n error0 = true\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n }\n\n if (!error0 && !_limit(value)) {\n ;(err = err || []).push({\n issue: 'does not fit the limit',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n for (let index = 0; index < value.length; index++) {\n const value_index = value[index]\n const ext_value_index0 = positive(value_index)\n\n if (ext_value_index0.tag === 'failure') {\n ;(err = err || []).push(\n ...ext_value_index0.failure.errors.map((fail) => ({\n issue: '' + fail.issue,\n path: [index, ...fail.path]\n }))\n )\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### lazy\n\nCreates a spec to validate a value with recursive <b>type</b>. But <b>data</b> that recursively references itself is not supported. `LazyTransformerSpec` type should be used when spec contains `struct`, `nullish`,\n`map`, `filter` and `transformer` specs, and `LazyValidatorSpec` otherwise.\n\n```ts\nimport { lazy, string, object, array, validator, LazyValidatorSpec } from 'spectypes'\n\ntype Person = {\n readonly name: string\n readonly likes: readonly Person[]\n}\n\nconst person: LazyValidatorSpec<Person> = lazy(() =>\n object({ name: string, likes: array(validator(person)) })\n)\n\nexpect(person({ name: 'Bob', likes: [{ name: 'Alice', likes: [] }] })).toEqual({\n tag: 'success',\n { name: 'Bob', likes: [{ name: 'Alice', likes: [] }] }\n})\n\nexpect(person({ name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] })).toEqual({\n tag: 'failure',\n failure: {\n value: { name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] },\n errors: [{ issue: 'not an array', path: ['likes', 0, 'likes'] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst person = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_name = value.name\n\n if (typeof value_name !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: ['name']\n })\n }\n\n const value_likes = value.likes\n\n if (!Array.isArray(value_likes)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: ['likes']\n })\n } else {\n for (let index_likes = 0; index_likes < value_likes.length; index_likes++) {\n const value_likes_index_likes = value_likes[index_likes]\n const ext_value_likes_index_likes0 = person(value_likes_index_likes)\n\n if (ext_value_likes_index_likes0.tag === 'failure') {\n ;(err = err || []).push(\n ...ext_value_likes_index_likes0.failure.errors.map((fail) => ({\n issue: '' + fail.issue,\n path: ['likes', index_likes, ...fail.path]\n }))\n )\n }\n }\n }\n\n for (const key in value) {\n if (!(key === 'name' || key === 'likes')) {\n ;(err = err || []).push({\n issue: 'excess key - ' + key,\n path: []\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### writable\n\nCreates an empty validator that removes `readonly` modifiers from the result of validation\n\n```ts\nimport { object, number, string, boolean, writable } from 'spectypes'\n\nconst check = writable(object({ x: number, y: string, z: boolean }))\n\nexpect(check({ x: 1, y: '2', z: true })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: true } // { x: number, y: string, z: true }\n})\n```\n\n---\n\n#### Spectype\n\nType to infer `success` value\n\n```ts\nimport { object, number, string, boolean, Spectype } from 'spectypes'\n\nconst check = object({ x: number, y: string, z: boolean })\n\n// { readonly x: number; readonly y: string; readonly z: boolean }\ntype Value = Spectype<typeof check>\n```\n\n## Misc\n\n### How does the plugin understand what code to transform?\n\nPlugin searches for named imports like `import { ... } from 'spectypes'` or `const { ... } = require('spectypes')`, gets all imported identifiers (aliases also supported). All variable declarations which include these identifiers will be converted into validating functions.\n\n### Special cases\n\n- When `literal(undefined)` or `unknown` is used as a property validator inside `object` or `struct` and that property is not present in the validated object the validation will fail.\n- When `nullish` is used as a property validator inside `object` or `struct` and that property is not present in the validated object the result will still contain that property set to `undefined`.\n\n### Result handling\n\nValidators return their results as 'success or failure' wrapped values and does not throw any exceptions (other than those thrown by the functions passed to `map`, `limit` or `filter`). This library does not include any functions to process validation results, but a compatible handy package exists - [ts-railway](https://github.com/iyegoroff/ts-railway)\n\n### Custom validators\n\nThere is no specific APIs to create custom validators, usually just `unknown`, `map` and `limit` are enough to create a validator for arbitrary data. For example, lets create a validator that checks if some value is a representation of a date and converts that value to `Date` object:\n\n```ts\nimport { unknown, map, limit } from 'spectypes'\n\nconst check = map(\n limit(unknown, (x) => !isNaN(Date.parse(x))),\n (x) => new Date(x)\n)\n\nconst date = new Date('Sun Apr 24 2022 12:51:57')\n\nexpect(check('Sun Apr 24 2022 12:51:57')).toEqual({\n tag: 'success',\n success: date\n})\n\nexpect(check([1, 2, 'abc'])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, 'abc'],\n errors: [{ issue: 'does not fit the limit', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _map = (x) => new Date(x)\n\nconst _limit = (x) => !isNaN(Date.parse(x))\n\nconst check = (value) => {\n let err, result\n\n if (!_limit(value)) {\n ;(err = err || []).push({\n issue: 'does not fit the limit',\n path: []\n })\n }\n\n if (!err) {\n result = _map(value)\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n### How is it tested?\n\nHaving 100% of the code covered with tests reflects only the coverage of generative code, not the generated one. It says little about the amount of potential bugs in this package. Because of that most of the test cases are randomly generated. When testing [valid data validation](packages/babel-plugin-spectypes/test/property/create-valid-property.ts) it will generate `spectypes` validator and corresponding `fast-check` arbitrary, then validator will ensure that values provided by arbitrary are valid. When testing [invalid data validation](packages/babel-plugin-spectypes/test/property/create-invalid-property.ts) it will also generate an expected error, then validator will ensure that values provided by arbitrary are invalid and lead to expected error.\n"
"readme": "# spectypes\n\n[![npm](https://img.shields.io/npm/v/spectypes)](https://npm.im/spectypes)\n[![build](https://github.com/iyegoroff/spectypes/workflows/build/badge.svg)](https://github.com/iyegoroff/spectypes/actions/workflows/build.yml)\n[![publish](https://github.com/iyegoroff/spectypes/workflows/publish/badge.svg)](https://github.com/iyegoroff/spectypes/actions/workflows/publish.yml)\n[![codecov](https://codecov.io/gh/iyegoroff/spectypes/branch/main/graph/badge.svg?t=1520230083925)](https://codecov.io/gh/iyegoroff/spectypes)\n[![Type Coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fiyegoroff%2Fspectypes%2Fmain%2Fpackage.json)](https://github.com/plantain-00/type-coverage)\n![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/spectypes)\n[![npm](https://img.shields.io/npm/l/spectypes.svg?t=1495378566925)](https://www.npmjs.com/package/spectypes)\n\nFast, compiled, eval-free data validator/transformer\n\n---\n\n## Features\n\n- <b>really fast</b>, can be even [faster](/benchmark) than `ajv`\n- <b>detailed errors</b>, failure will result into explicit error message(s) and path to invalid data\n- <b>extensively tested</b>, each release undergoes more than 900 `fast-check` powered [tests](#how-is-it-tested)\n- <b>precise types</b>, accurately infers all types and provides readable compile-time error messages\n- <b>browser friendly</b>, uses `babel` to compile validators, so no `eval` or `new Function` involved\n- <b>easily extensible</b>, [custom validators](#custom-validators) are created by mixing existing ones\n\n## Getting started\n\n1. There are two packages to install - `spectypes`, which contains type definitions and small set of runtime helpers and `babel-plugin-spectypes`, which parses and compiles validators into functions:\n\n ```\n npm i spectypes\n npm i babel-plugin-spectypes -D\n ```\n\n2. Add `babel-plugin-spectypes` to plugins section in your `babel` config:\n\n ```diff\n \"plugins\": [\n + \"babel-plugin-spectypes\"\n ]\n ```\n\n## Example\n\n```ts\nimport { array, number } from 'spectypes'\n\nconst check = array(number)\n\nexpect(check([1, 2, 3])).toEqual({\n tag: 'success',\n success: [1, 2, 3] // readonly number[]\n})\n\nexpect(check({ 0: 1 })).toEqual({\n tag: 'failure',\n failure: {\n value: { 0: 1 }, // unknown\n errors: [{ issue: 'not an array', path: [] }]\n }\n})\n\nexpect(check([1, 2, '3', false])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, '3', false], // unknown\n errors: [\n { issue: 'not a number', path: [2] },\n { issue: 'not a number', path: [3] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes';\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n for (let index = 0; index < value.length; index++) {\n const value_index = value[index]\n\n if (typeof value_index !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: [index]\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n\n...\n```\n\n</details>\n\n---\n\n## Reference\n\nPrimitive validators\n\n- [boolean](#boolean)\n- [literal](#literal)\n- [nullish](#nullish)\n- [number](#number)\n- [string](#string)\n- [unknown](#unknown)\n\nComplex validators\n\n- [array](#array)\n- [filter](#filter)\n- [limit](#limit)\n- [map](#map)\n- [merge](#merge)\n- [object](#object)\n- [optional](#optional)\n- [record](#record)\n- [struct](#struct)\n- [template](#template)\n- [tuple](#tuple)\n- [union](#union)\n\nUtilities\n\n- [lazy](#lazy)\n- [transformer](#transformer)\n- [validator](#validator)\n- [writable](#writable)\n- [Spectype](#Spectype)\n\n### Primitive validators\n\n#### boolean\n\nValidates a boolean value\n\n```ts\nimport { boolean } from 'spectypes'\n\nconst check = boolean\n\nexpect(check(true)).toEqual({\n tag: 'success',\n success: true\n})\n\nexpect(check('false')).toEqual({\n tag: 'failure',\n failure: {\n value: 'false',\n errors: [{ issue: 'not a boolean', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### literal\n\nCreates a literal validator spec. `literal`can validate strings, numbers, booleans, undefined and null. `literal(undefined)` is treated [specially](#special-cases) when used as a property validator inside `object` or `struct`.\n\n```ts\nimport { literal } from 'spectypes'\n\nconst check = literal('test')\n\nexpect(check('test')).toEqual({\n tag: 'success',\n success: 'test'\n})\n\nexpect(check('temp')).toEqual({\n tag: 'failure',\n failure: {\n value: 'temp',\n errors: [{ issue: \"not a 'test' string literal\", path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (value !== 'test') {\n ;(err = err || []).push({\n issue: \"not a '\" + 'test' + \"' string literal\",\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### nullish\n\nTransformer spec, that accepts `undefined` and `null` values and maps them to `undefined`.\n`nullish` is treated [specially](#special-cases) when used as a property validator inside `object` or `struct`.\n\n```ts\nimport { nullish } from 'spectypes'\n\nconst check = nullish\n\nexpect(check(undefined)).toEqual({\n tag: 'success'\n success: undefined\n})\n\nexpect(check(null)).toEqual({\n tag: 'success'\n success: undefined\n})\n\nexpect(check(123)).toEqual({\n tag: 'failure',\n failure: {\n value: 'temp',\n errors: [{ issue: \"not 'null' or 'undefined'\", path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err, result\n\n if (value !== null && value !== undefined) {\n ;(err = err || []).push({\n issue: \"not 'null' or 'undefined'\",\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### number\n\nValidates a number value.\n\n```ts\nimport { number } from 'spectypes'\n\nconst check = number\n\nexpect(check(0)).toEqual({\n tag: 'success',\n success: 0\n})\n\nexpect(check({})).toEqual({\n tag: 'failure',\n failure: {\n value: {},\n errors: [{ issue: 'not a number', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### string\n\nValidates a string value.\n\n```ts\nimport { string } from 'spectypes'\n\nconst check = string\n\nexpect(check('')).toEqual({\n tag: 'success',\n success: ''\n})\n\nexpect(check(null)).toEqual({\n tag: 'failure',\n failure: {\n value: null,\n errors: [{ issue: 'not a string', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### unknown\n\nEmpty validator spec. `unknown` is treated [specially](#special-cases) when used as a property validator inside `object` or `struct`.\n\n```ts\nimport { unknown } from 'spectypes'\n\nconst check = unknown\n\nexpect(check('anything')).toEqual({\n tag: 'success',\n success: 'anything'\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n### Complex validators\n\n#### array\n\nCreates an array validator spec. Takes a spec to validate each item of an array.\n\nSee [example](#example)\n\n---\n\n#### filter\n\nCan be used only as an argument for `array` and `record` to create filtered transformer specs. Filtering happens after each item or key validation. Takes a spec to validate each item or key of a collection and filter predicate.\n\n```ts\nimport { array, number, filter } from 'spectypes'\n\nconst check = array(filter(number, (x) => x > 1))\n\nexpect(check([1, 2, 3])).toEqual({\n tag: 'success',\n success: [2, 3]\n})\n\nexpect(check([1, 2, null])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, null],\n errors: [{ issue: 'not a number', path: [2] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _filter = (x) => x > 1\n\nconst check = (value) => {\n let err, result\n result = []\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n let filterindex = 0\n\n for (let index = 0; index < value.length; index++) {\n const value_index = value[index]\n\n if (typeof value_index !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: [index]\n })\n }\n\n if (!err && _filter(value_index)) {\n result[filterindex++] = value_index\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\nType predicate will be taken into account if provided\n\n```ts\nimport { array, string, filter } from 'spectypes'\n\nconst check = array(filter(string, (x): x is 'test' => x === 'test'))\n\nexpect(check(['hello', 'test', 'world'])).toEqual({\n tag: 'success',\n success: ['test'] // readonly 'test'[]\n})\n```\n\n---\n\n#### limit\n\nCreates a spec with custom constraint. Takes a basis spec and a function to perform additinal validation.\n\n```ts\nimport { number, limit } from 'spectypes'\n\nconst check = limit(number, (x) => x > 1)\n\nexpect(check(5)).toEqual({\n tag: 'success',\n success: 5\n})\n\nexpect(check(-5)).toEqual({\n tag: 'failure',\n failure: {\n value: -5,\n errors: [{ issue: 'does not fit the limit', path: [] }]\n }\n})\n\nexpect(check('5')).toEqual({\n tag: 'failure',\n failure: {\n value: '5',\n errors: [{ issue: 'not a number', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _limit = (x) => x > 1\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n } else if (!_limit(value)) {\n ;(err = err || []).push({\n issue: 'does not fit the limit',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\nType predicate will be taken into account if provided\n\n```ts\nimport { array, string, limit } from 'spectypes'\n\nconst check = array(limit(string, (x): x is 'test' => x === 'test'))\n\nexpect(check(['test', 'test', 'test'])).toEqual({\n tag: 'success',\n success: ['test', 'test', 'test'] // readonly 'test'[]\n})\n```\n\n---\n\n#### map\n\nCreates a spec that transforms the result of successful validation. Takes basis spec and mapping function.\n\n```ts\nimport { number, map } from 'spectypes'\n\nconst check = map(number, (x) => x + 1)\n\nexpect(check(10)).toEqual({\n tag: 'success',\n success: 11\n})\n\nexpect(check(undefined)).toEqual({\n tag: 'failure',\n failure: {\n value: undefined,\n errors: [{ issue: 'not a number', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _map = (x) => x + 1\n\nconst check = (value) => {\n let err, result\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n } else {\n result = _map(value)\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### merge\n\nCan combine `tuple` with `array` or `object` with `record` into single spec.\n\n```ts\nimport { tuple, array, string, boolean, merge } from 'spectypes'\n\nconst check = merge(tuple(string, string), array(boolean))\n\nexpect(check(['hello', 'world', true])).toEqual({\n tag: 'success',\n success: ['hello', 'world', true]\n})\n\nexpect(check(['hello', 'world', '!'])).toEqual({\n tag: 'failure',\n failure: {\n value: ['hello', 'world', '!'],\n errors: [{ issue: 'not a string', path: [2] }]\n }\n})\n\nexpect(check(['hello'])).toEqual({\n tag: 'failure',\n failure: {\n value: ['hello'],\n errors: [{ issue: 'length is less than 2', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else if (value.length < 2) {\n ;(err = err || []).push({\n issue: 'length is less than ' + 2,\n path: []\n })\n } else {\n const value_$30_ = value[0]\n\n if (typeof value_$30_ !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: [0]\n })\n }\n\n const value_$31_ = value[1]\n\n if (typeof value_$31_ !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: [1]\n })\n }\n\n for (let index = 2; index < value.length; index++) {\n const value_index = value[index]\n\n if (typeof value_index !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [index]\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n```ts\nimport { object, record, number, string, boolean, merge } from 'spectypes'\n\nconst check = merge(object({ x: number }), record(string, boolean))\n\nexpect(check({ x: 123, y: true })).toEqual({\n tag: 'success',\n success: { x: 123, y: true }\n})\n\nexpect(check({ x: true, y: 123 })).toEqual({\n tag: 'failure',\n failure: {\n value: { x: true, y: 123 },\n errors: [\n { issue: 'not a number', path: ['x'] },\n { issue: 'not a boolean', path: ['y'] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n for (let i = 0; i < _spectypes.bannedKeys.length; i++) {\n const ban = _spectypes.bannedKeys[i]\n\n if (Object.prototype.hasOwnProperty.call(value, ban)) {\n ;(err = err || []).push({\n issue: \"includes banned '\" + ban + \"' key\",\n path: []\n })\n }\n }\n\n const value_x = value.x\n\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n\n for (const key in value) {\n if (!(key === 'x')) {\n const value_key = value[key]\n\n if (typeof value_key !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [key]\n })\n }\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### object\n\nCreates an object validator spec. Validation will fail if validated object has a property set different from the one specified. Takes an object with specs to validate object properties. `literal(undefined)`, `nullish` and `unknown` are treated [specially](#special-cases) when used as a property validator inside `object`.\n\n```ts\nimport { object, number, string, boolean } from 'spectypes'\n\nconst check = object({ x: number, y: string, z: boolean })\n\nexpect(check({ x: 1, y: '2', z: false })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: false }\n})\n\nexpect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({\n tag: 'failure',\n failure: {\n value: { x: 1, y: '2', z: false, xyz: [] },\n errors: [{ issue: 'excess key - xyz', path: [] }]\n }\n})\n\nexpect(check({})).toEqual({\n tag: 'failure',\n failure: {\n value: {},\n errors: [\n { issue: 'not a number', path: ['x'] },\n { issue: 'not a string', path: ['y'] },\n { issue: 'not a boolean', path: ['z'] }\n ]\n }\n})\n\nexpect(check([])).toEqual({\n tag: 'failure',\n failure: {\n value: [],\n errors: [{ issue: 'not an object', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_x = value.x\n\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n\n const value_y = value.y\n\n if (typeof value_y !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: ['y']\n })\n }\n\n const value_z = value.z\n\n if (typeof value_z !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: ['z']\n })\n }\n\n for (const key in value) {\n if (!(key === 'x' || key === 'y' || key === 'z')) {\n ;(err = err || []).push({\n issue: 'excess key - ' + key,\n path: []\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### optional\n\nCreates an optional object property validator spec. Can be used only inside `object` and `struct` arguments. Will not produce any validation errors if property equals `undefined` or is not present in the validated object.\n\n```ts\nimport { optional, struct, number } from 'spectypes'\n\nconst check = struct({ x: optional(number) })\n\nexpect(check({ x: 5 })).toEqual({\n tag: 'success',\n success: { x: 5 }\n})\n\nexpect(check({ x: undefined })).toEqual({\n tag: 'success',\n success: { x: undefined }\n})\n\nexpect(check({})).toEqual({\n tag: 'success',\n success: {}\n})\n\nexpect(check({ x: 'x' })).toEqual({\n tag: 'failure',\n failure: {\n value: { x: 'x' },\n errors: [{ issue: 'not a number', path: ['x'] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err, result\n result = {}\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_x = value.x\n\n if ('x' in value) {\n if (value_x !== undefined) {\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n }\n\n result.x = value_x\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### record\n\nCreates a record validator spec. This validator is protected from prototype pollution and validation will fail if validated object contains properties that override `Object.proptotype` methods. This function has two signatures - one takes a spec to validate each key of a record and a spec to validate each item, another takes only item spec and treats all keys as strings. Key spec can be a `string`, `template`, string `literal` or `union` of these specs.\n\n```ts\nimport { record, boolean } from 'spectypes'\n\nconst check = record(boolean)\n\nexpect(check({ foo: false, bar: true })).toEqual({\n tag: 'success',\n success: { foo: false, bar: true }\n})\n\nexpect(check(true)).toEqual({\n tag: 'failure',\n failure: {\n value: true,\n errors: [{ issue: 'not an object', path: [] }]\n }\n})\n\nexpect(check({ toString: true })).toEqual({\n tag: 'failure',\n failure: {\n value: { toString: true },\n errors: [{ issue: \"includes banned 'toString' key\", path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n for (let i = 0; i < _spectypes.bannedKeys.length; i++) {\n const ban = _spectypes.bannedKeys[i]\n\n if (Object.prototype.hasOwnProperty.call(value, ban)) {\n ;(err = err || []).push({\n issue: \"includes banned '\" + ban + \"' key\",\n path: []\n })\n }\n }\n\n for (const key in value) {\n const value_key = value[key]\n\n if (typeof value_key !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [key]\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### struct\n\nCreates an object transformer spec. All properties of validated object that are not present in passed param will be removed from the result of successful validation. Takes an object with specs to validate object properties. `literal(undefined)`, `nullish` and `unknown` are treated [specially](#special-cases) when used as a property validator inside `struct`.\n\n```ts\nimport { struct, number, string, boolean } from 'spectypes'\n\nconst check = struct({ x: number, y: string, z: boolean })\n\nexpect(check({ x: 1, y: '2', z: false })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: false }\n})\n\nexpect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: false }\n})\n\nexpect(check({})).toEqual({\n tag: 'failure',\n failure: {\n value: {},\n errors: [\n { issue: 'not a number', path: ['x'] },\n { issue: 'not a string', path: ['y'] },\n { issue: 'not a boolean', path: ['z'] }\n ]\n }\n})\n\nexpect(check([])).toEqual({\n tag: 'failure',\n failure: {\n value: [],\n errors: [{ issue: 'not an object', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err, result\n result = {}\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_x = value.x\n\n if (typeof value_x !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: ['x']\n })\n }\n\n result.x = value_x\n const value_y = value.y\n\n if (typeof value_y !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: ['y']\n })\n }\n\n result.y = value_y\n const value_z = value.z\n\n if (typeof value_z !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: ['z']\n })\n }\n\n result.z = value_z\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### template\n\nCreates a template string validator spec. Takes `number`, `string`, `boolean`, `literal` specs and their` union`s to validate parts of the validated string.\n\n```ts\nimport { template, literal, number, string, boolean } from 'spectypes'\n\nconst check = template(literal('test'), string, number, boolean)\n\nexpect(check('test___123false')).toEqual({\n tag: 'success',\n success: 'test___123false'\n})\n\nexpect(check('test___false')).toEqual({\n tag: 'failure',\n failure: {\n value: 'test___false',\n errors: [{ issue: 'template literal mismatch', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _template = new RegExp(\n '^' +\n _spectypes.escape('test') +\n _spectypes.stringTest +\n _spectypes.numberTest +\n _spectypes.booleanTest +\n '$'\n)\n\nconst check = (value) => {\n let err\n\n if (typeof value !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: []\n })\n } else if (!_template.test(value)) {\n ;(err = err || []).push({\n issue: 'template literal mismatch',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### tuple\n\nCreates a tuple validator spec. Takes specs to validate tuple parts.\n\n```ts\nimport { tuple, number, string, boolean } from 'spectypes'\n\nconst check = tuple(number, string, boolean)\n\nexpect(check([1, '2', false])).toEqual({\n tag: 'success',\n success: [1, '2', false]\n})\n\nexpect(check([])).toEqual({\n tag: 'failure',\n failure: {\n value: [],\n errors: [{ issue: 'length is not 3', path: [] }]\n }\n})\n\nexpect(check([1, '2', false, 1000])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, '2', false, 1000],\n errors: [{ issue: 'length is not 3', path: [] }]\n }\n})\n\nexpect(check(['1', '2', 'false'])).toEqual({\n tag: 'failure',\n failure: {\n value: ['1', '2', 'false'],\n errors: [\n { issue: 'not a number', path: [0] },\n { issue: 'not a boolean', path: [2] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else if (value.length !== 3) {\n ;(err = err || []).push({\n issue: 'length is not ' + 3,\n path: []\n })\n } else {\n const value_$30_ = value[0]\n\n if (typeof value_$30_ !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: [0]\n })\n }\n\n const value_$31_ = value[1]\n\n if (typeof value_$31_ !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: [1]\n })\n }\n\n const value_$32_ = value[2]\n\n if (typeof value_$32_ !== 'boolean') {\n ;(err = err || []).push({\n issue: 'not a boolean',\n path: [2]\n })\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### union\n\nCreates a union validator spec. Takes specs to validate union cases.\n\n```ts\nimport { union, number, string, boolean } from 'spectypes'\n\nconst check = union(number, string, boolean)\n\nexpect(check('temp')).toEqual({\n tag: 'success',\n success: 'temp'\n})\n\nexpect(check(true)).toEqual({\n tag: 'success',\n success: true\n})\n\nexpect(check(null)).toEqual({\n tag: 'failure',\n failure: {\n value: null,\n errors: [\n { issue: 'union case #0 mismatch: not a number', path: [] },\n { issue: 'union case #1 mismatch: not a string', path: [] },\n { issue: 'union case #2 mismatch: not a boolean', path: [] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst check = (value) => {\n let err\n let unmatched\n\n if (typeof value !== 'number') {\n unmatched = true\n }\n\n if (unmatched) {\n unmatched = false\n\n if (typeof value !== 'string') {\n unmatched = true\n }\n }\n\n if (unmatched) {\n unmatched = false\n\n if (typeof value !== 'boolean') {\n unmatched = true\n }\n }\n\n if (unmatched) {\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'union case #0 mismatch: not a number',\n path: []\n })\n }\n\n if (typeof value !== 'string') {\n ;(err = err || []).push({\n issue: 'union case #1 mismatch: not a string',\n path: []\n })\n }\n\n if (typeof value !== 'boolean') {\n ;(err = err || []).push({\n issue: 'union case #2 mismatch: not a boolean',\n path: []\n })\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n### Utilities\n\n#### transformer\n\nSpec that tells `babel` plugin to generate a wrapper for an external transformer spec. Any spec containing `struct`, `nullish`, `map`, `filter` and `transformer` specs will create and return new object on successful validation. Such spec has to be wrapped with `transformer` when used inside another spec.\n\n```ts\nimport { array, transformer, map, number } from 'spectypes'\n\nconst negated = map(number, (x) => -x)\nconst check = array(transformer(negated))\n\n// Incorrect usage !!!\n// const negated = transformer(map(number, (x) => -x))\n// const check = array(negated)\n\nexpect(check([1, 2, -3])).toEqual({\n tag: 'success',\n success: [-1, -2, 3]\n})\n\nexpect(check([1, 2, 'abc'])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, 'abc'],\n errors: [{ issue: 'not a number', path: [2] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _map = (x) => -x\n\nconst negated = (value) => {\n let err, result\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n } else {\n result = _map(value)\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n\nconst check = (value) => {\n let err, result\n result = []\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n for (let index = 0; index < value.length; index++) {\n let result_index\n const value_index = value[index]\n const ext_value_index0 = negated(value_index)\n\n if (ext_value_index0.tag === 'failure') {\n ;(err = err || []).push(\n ...ext_value_index0.failure.errors.map((fail) => ({\n issue: '' + fail.issue,\n path: [index, ...fail.path]\n }))\n )\n } else {\n result_index = ext_value_index0.success\n }\n\n result[index] = result_index\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n---\n\n#### validator\n\nSpec that tells `babel` plugin to generate a wrapper for an external validator spec. Any spec <b>not</b> containing `struct`, `nullish`, `map`, `filter` and `transformer` specs on successful validation will return validated object. Such spec has to be wrapped with `validator` when used inside another spec.\n\n```ts\nimport { array, validator, limit, number } from 'spectypes'\n\nconst positive = limit(number, (x) => x >= 0)\nconst check = array(validator(positive))\n\n// Incorrect usage !!!\n// const positive = validator(limit(number, (x) => x >= 0))\n// const check = array(positive)\n\nexpect(check([0, 1, 2])).toEqual({\n tag: 'success',\n success: [0, 1, 2]\n})\n\nexpect(check([-1, -2, -3])).toEqual({\n tag: 'failure',\n failure: {\n value: [-1, -2, -3],\n errors: [\n { issue: 'does not fit the limit', path: [0] },\n { issue: 'does not fit the limit', path: [1] },\n { issue: 'does not fit the limit', path: [2] }\n ]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _limit = (x) => x >= 0\n\nconst positive = (value) => {\n let err\n\n if (typeof value !== 'number') {\n ;(err = err || []).push({\n issue: 'not a number',\n path: []\n })\n } else if (!_limit(value)) {\n ;(err = err || []).push({\n issue: 'does not fit the limit',\n path: []\n })\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n\nconst check = (value) => {\n let err\n\n if (!Array.isArray(value)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: []\n })\n } else {\n for (let index = 0; index < value.length; index++) {\n const value_index = value[index]\n const ext_value_index0 = positive(value_index)\n\n if (ext_value_index0.tag === 'failure') {\n ;(err = err || []).push(\n ...ext_value_index0.failure.errors.map((fail) => ({\n issue: '' + fail.issue,\n path: [index, ...fail.path]\n }))\n )\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### lazy\n\nCreates a spec to validate a value with recursive <b>type</b>. But <b>data</b> that recursively references itself is not supported. `LazyTransformerSpec` type should be used when spec contains `struct`, `nullish`,\n`map`, `filter` and `transformer` specs, and `LazyValidatorSpec` otherwise.\n\n```ts\nimport { lazy, string, object, array, validator, LazyValidatorSpec } from 'spectypes'\n\ntype Person = {\n readonly name: string\n readonly likes: readonly Person[]\n}\n\nconst person: LazyValidatorSpec<Person> = lazy(() =>\n object({ name: string, likes: array(validator(person)) })\n)\n\nexpect(person({ name: 'Bob', likes: [{ name: 'Alice', likes: [] }] })).toEqual({\n tag: 'success',\n { name: 'Bob', likes: [{ name: 'Alice', likes: [] }] }\n})\n\nexpect(person({ name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] })).toEqual({\n tag: 'failure',\n failure: {\n value: { name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] },\n errors: [{ issue: 'not an array', path: ['likes', 0, 'likes'] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst person = (value) => {\n let err\n\n if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) {\n ;(err = err || []).push({\n issue: 'not an object',\n path: []\n })\n } else {\n const value_name = value.name\n\n if (typeof value_name !== 'string') {\n ;(err = err || []).push({\n issue: 'not a string',\n path: ['name']\n })\n }\n\n const value_likes = value.likes\n\n if (!Array.isArray(value_likes)) {\n ;(err = err || []).push({\n issue: 'not an array',\n path: ['likes']\n })\n } else {\n for (let index_likes = 0; index_likes < value_likes.length; index_likes++) {\n const value_likes_index_likes = value_likes[index_likes]\n const ext_value_likes_index_likes0 = person(value_likes_index_likes)\n\n if (ext_value_likes_index_likes0.tag === 'failure') {\n ;(err = err || []).push(\n ...ext_value_likes_index_likes0.failure.errors.map((fail) => ({\n issue: '' + fail.issue,\n path: ['likes', index_likes, ...fail.path]\n }))\n )\n }\n }\n }\n\n for (const key in value) {\n if (!(key === 'name' || key === 'likes')) {\n ;(err = err || []).push({\n issue: 'excess key - ' + key,\n path: []\n })\n }\n }\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: value }\n}\n```\n\n</details>\n\n---\n\n#### writable\n\nCreates an empty validator that removes `readonly` modifiers from the result of validation\n\n```ts\nimport { object, number, string, boolean, writable } from 'spectypes'\n\nconst check = writable(object({ x: number, y: string, z: boolean }))\n\nexpect(check({ x: 1, y: '2', z: true })).toEqual({\n tag: 'success',\n success: { x: 1, y: '2', z: true } // { x: number, y: string, z: true }\n})\n```\n\n---\n\n#### Spectype\n\nType to infer `success` value\n\n```ts\nimport { object, number, string, boolean, Spectype } from 'spectypes'\n\nconst check = object({ x: number, y: string, z: boolean })\n\n// { readonly x: number; readonly y: string; readonly z: boolean }\ntype Value = Spectype<typeof check>\n```\n\n## Misc\n\n### How does the plugin understand what code to transform?\n\nPlugin searches for named imports like `import { ... } from 'spectypes'` or `const { ... } = require('spectypes')`and gets all imported identifiers (aliases also supported). All variable declarations which include these identifiers will be converted into validating functions.\n\n### Special cases\n\n- When `literal(undefined)` or `unknown` is used as a property validator inside `object` or `struct` and that property is not present in the validated object the validation will fail.\n- When `nullish` is used as a property validator inside `object` or `struct` and that property is not present in the validated object the result will still contain that property set to `undefined`.\n\n### Result handling\n\nValidators return their results as 'success or failure' wrapped values and does not throw any exceptions (other than those thrown by the functions passed to `map`, `limit` or `filter`). This library does not include any functions to process validation results, but a compatible handy package exists - [ts-railway](https://github.com/iyegoroff/ts-railway)\n\n### Custom validators\n\nThere is no specific APIs to create custom validators, usually just `unknown`, `map` and `limit` are enough to create a validator for arbitrary data. For example, lets create a validator that checks if some value is a representation of a date and converts that value to `Date` object:\n\n```ts\nimport { unknown, map, limit } from 'spectypes'\n\nconst check = map(\n limit(unknown, (x) => !isNaN(Date.parse(x))),\n (x) => new Date(x)\n)\n\nconst date = new Date('Sun Apr 24 2022 12:51:57')\n\nexpect(check('Sun Apr 24 2022 12:51:57')).toEqual({\n tag: 'success',\n success: date\n})\n\nexpect(check([1, 2, 'abc'])).toEqual({\n tag: 'failure',\n failure: {\n value: [1, 2, 'abc'],\n errors: [{ issue: 'does not fit the limit', path: [] }]\n }\n})\n```\n\n<details>\n <summary>Transformed code</summary>\n\n```js\nimport * as _spectypes from 'spectypes'\n\nconst _map = (x) => new Date(x)\n\nconst _limit = (x) => !isNaN(Date.parse(x))\n\nconst check = (value) => {\n let err, result\n\n if (!_limit(value)) {\n ;(err = err || []).push({\n issue: 'does not fit the limit',\n path: []\n })\n } else {\n result = _map(value)\n }\n\n return err\n ? { tag: 'failure', failure: { value, errors: err } }\n : { tag: 'success', success: result }\n}\n```\n\n</details>\n\n### How is it tested?\n\nHaving 100% of the code covered with tests reflects only the coverage of generative code, not the generated one. It says little about the amount of potential bugs in this package. Because of that most of the test cases are randomly generated. When testing [valid data validation](packages/babel-plugin-spectypes/test/property/create-valid-property.ts) it will generate `spectypes` validator and corresponding `fast-check` arbitrary, then validator will ensure that values provided by arbitrary are valid. When testing [invalid data validation](packages/babel-plugin-spectypes/test/property/create-invalid-property.ts) it will also generate an expected error, then validator will ensure that values provided by arbitrary are invalid and lead to expected error.\n"
}

@@ -565,6 +565,4 @@ # spectypes

let err
let error0
if (typeof value !== 'number') {
error0 = true
;(err = err || []).push({

@@ -574,5 +572,3 @@ issue: 'not a number',

})
}
if (!error0 && !_limit(value)) {
} else if (!_limit(value)) {
;(err = err || []).push({

@@ -646,5 +642,3 @@ issue: 'does not fit the limit',

})
}
if (!err) {
} else {
result = _map(value)

@@ -1494,5 +1488,3 @@ }

})
}
if (!err) {
} else {
result = _map(value)

@@ -1588,6 +1580,4 @@ }

let err
let error0
if (typeof value !== 'number') {
error0 = true
;(err = err || []).push({

@@ -1597,5 +1587,3 @@ issue: 'not a number',

})
}
if (!error0 && !_limit(value)) {
} else if (!_limit(value)) {
;(err = err || []).push({

@@ -1778,3 +1766,3 @@ issue: 'does not fit the limit',

Plugin searches for named imports like `import { ... } from 'spectypes'` or `const { ... } = require('spectypes')`, gets all imported identifiers (aliases also supported). All variable declarations which include these identifiers will be converted into validating functions.
Plugin searches for named imports like `import { ... } from 'spectypes'` or `const { ... } = require('spectypes')`and gets all imported identifiers (aliases also supported). All variable declarations which include these identifiers will be converted into validating functions.

@@ -1836,5 +1824,3 @@ ### Special cases

})
}
if (!err) {
} else {
result = _map(value)

@@ -1841,0 +1827,0 @@ }

@@ -11,11 +11,14 @@ import type babelCore from '@babel/core'

spectypesImport?: babelCore.types.Identifier
opts: { readonly packageName?: string }
}
const packageName = 'spectypes'
const defaultPackageName = 'spectypes'
export default function plugin({ types: t }: typeof babelCore): babelCore.PluginObj<State> {
return {
name: packageName,
name: 'spectypes',
visitor: {
VariableDeclarator(path, state) {
const { packageName = defaultPackageName } = state.opts
if (

@@ -53,2 +56,4 @@ t.isCallExpression(path.node.init) &&

ImportDeclaration(path, state) {
const { packageName = defaultPackageName } = state.opts
if (path.node.source.value === packageName) {

@@ -55,0 +60,0 @@ state.specNames ??= {}

@@ -466,9 +466,19 @@ import { types as t } from '@babel/core'

case 'optional':
return parseUnary('optional', expression, [...rejects, 'optional', 'filter'], context)
return parseUnary(
'optional',
expression,
[...rejects, 'optional', 'filter', 'lazy'],
context
)
case 'array':
return parseArray(expression, [...rejects, 'optional'], context)
return parseArray(expression, [...rejects, 'optional', 'lazy'], context)
case 'tuple':
return parseUnaryArray('tuple', expression, [...rejects, 'optional', 'filter'], context)
return parseUnaryArray(
'tuple',
expression,
[...rejects, 'optional', 'filter', 'lazy'],
context
)

@@ -479,3 +489,3 @@ case 'union':

expression,
[...rejects, 'optional', 'filter', 'unknown', 'union'],
[...rejects, 'optional', 'filter', 'unknown', 'union', 'lazy'],
context

@@ -485,6 +495,6 @@ )

case 'object':
return parseUnaryObject('object', expression, [...rejects, 'filter'], context)
return parseUnaryObject('object', expression, [...rejects, 'filter', 'lazy'], context)
case 'struct':
return parseUnaryObject('struct', expression, [...rejects, 'filter'], context)
return parseUnaryObject('struct', expression, [...rejects, 'filter', 'lazy'], context)

@@ -512,3 +522,3 @@ case 'record':

],
[...rejects, 'optional'],
[...rejects, 'optional', 'lazy'],
context

@@ -518,3 +528,3 @@ )

case 'lazy':
return parseLazy(expression, [...rejects, 'filter'], context)
return parseLazy(expression, [...rejects, 'filter', 'lazy'], context)

@@ -549,6 +559,11 @@ case 'template':

case 'map':
return parseFunction('map', expression, [...rejects, 'optional', 'filter'], context)
return parseFunction('map', expression, [...rejects, 'optional', 'filter', 'lazy'], context)
case 'limit':
return parseFunction('limit', expression, [...rejects, 'optional', 'filter'], context)
return parseFunction(
'limit',
expression,
[...rejects, 'optional', 'filter', 'lazy'],
context
)

@@ -555,0 +570,0 @@ case 'writable':

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc