{"id":19592383,"url":"https://github.com/smikhalevski/doubter","last_synced_at":"2025-10-07T02:15:29.625Z","repository":{"id":52341360,"uuid":"511943295","full_name":"smikhalevski/doubter","owner":"smikhalevski","description":"🤔 Runtime validation and transformation library.","archived":false,"fork":false,"pushed_at":"2024-10-13T14:06:11.000Z","size":7490,"stargazers_count":67,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"next","last_synced_at":"2024-10-13T14:57:57.518Z","etag":null,"topics":["assert","parsing","transformation","validation"],"latest_commit_sha":null,"homepage":"https://smikhalevski.github.io/doubter/latest/modules/core.html","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smikhalevski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-08T15:32:15.000Z","updated_at":"2024-10-13T13:38:00.000Z","dependencies_parsed_at":"2024-04-02T13:49:26.055Z","dependency_job_id":"3e375950-10a8-4f8e-ab33-b6a8271b2a3b","html_url":"https://github.com/smikhalevski/doubter","commit_stats":{"total_commits":89,"total_committers":1,"mean_commits":89.0,"dds":0.0,"last_synced_commit":"5258c1945ed114a4ef69c2619343c48ecfbf9bc7"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fdoubter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fdoubter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fdoubter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smikhalevski%2Fdoubter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smikhalevski","download_url":"https://codeload.github.com/smikhalevski/doubter/tar.gz/refs/heads/next","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247608150,"owners_count":20965952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assert","parsing","transformation","validation"],"created_at":"2024-11-11T08:34:55.934Z","updated_at":"2025-10-07T02:15:24.350Z","avatar_url":"https://github.com/smikhalevski.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"#readme\"\u003e\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logo-dark.png\" /\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"./assets/logo-light.png\" /\u003e\n    \u003cimg alt=\"Doubter\" src=\"./assets/logo-light.png\" width=\"400\" /\u003e\n  \u003c/picture\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://codesandbox.io/s/y5kec4\"\u003e\u003cimg src=\"./assets/button-playground.png\" alt=\"Playground\" height=\"41\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://smikhalevski.github.io/doubter/next/modules/core.html\"\u003e\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/button-api-docs-dark.png\" /\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"./assets/button-api-docs-light.png\" /\u003e\n    \u003cimg alt=\"API Docs\" src=\"./assets/button-api-docs-light.png\" height=\"41\" /\u003e\n  \u003c/picture\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr\u003e\n\nRuntime validation and transformation library.\n\n- TypeScript first;\n- Sync and async validation and transformation flows;\n- [Circular object references support](#circular-object-references);\n- Collect all validation issues, or [exit early](#early-return);\n- [Runtime type introspection](#introspection);\n- [Human-oriented type coercion](#type-coercion);\n- [High performance and low memory consumption](#performance);\n- Zero dependencies;\n- [Pluggable architecture](#plugins);\n- Tree-shakable: [3 — 12 kB gzipped](https://bundlephobia.com/result?p=doubter) depending on what features you use;\n- Check out the [Cookbook](#cookbook) for real-life examples!\n\n```shell\nnpm install --save-prod doubter\n```\n\n\u003e [!IMPORTANT]\\\n\u003e Docs on the [`next`](https://github.com/smikhalevski/doubter/tree/next#readme) branch describe the canary release\n\u003e [`doubter@next`](https://www.npmjs.com/package/doubter/v/next). Navigate to the\n\u003e [`latest`](https://github.com/smikhalevski/doubter/tree/latest#readme) branch for docs that describe the latest stable\n\u003e release.\n\n\u003cbr\u003e\n\n🚀\u0026ensp;**Features**\n\n- [Introduction](#introduction)\n- [Validation errors](#validation-errors)\n- [Operations](#operations)\n- [Conversions](#conversions)\n- [Early return](#early-return)\n- [Annotations and metadata](#annotations-and-metadata)\n- [Parsing context](#parsing-context)\n- [Shape piping](#shape-piping)\n- [Replace, allow, and deny a value](#replace-allow-and-deny-a-value)\n- [Optional and non-optional](#optional-and-non-optional)\n- [Nullable and nullish](#nullable-and-nullish)\n- [Exclude a shape](#exclude-a-shape)\n- [Deep partial](#deep-partial)\n- [Fallback value](#fallback-value)\n- [Branded types](#branded-types)\n- [Type coercion](#type-coercion)\n- [Introspection](#introspection)\n- [Localization](#localization)\n- [Plugins](#plugins)\n- [Advanced shapes](#advanced-shapes)\n\n⏱\u0026ensp;[**Performance**](#performance)\n\n🍿\u0026ensp;[**Comparison with peers**](#comparison-with-peers)\n\n🎯\u0026ensp;**Data types**\n\n- Strings\u003cbr\u003e\n  [`string`](#string)\n\n- Numbers\u003cbr\u003e\n  [`number`](#number)\n  [`bigint`](#bigint)\n  [`nan`](#nan)\n\n- Booleans\u003cbr\u003e\n  [`boolean`](#boolean-bool)\n  [`bool`](#boolean-bool)\n\n- Symbols\u003cbr\u003e\n  [`symbol`](#symbol)\n\n- Literal values\u003cbr\u003e\n  [`enum`](#enum)\n  [`const`](#const)\n  [`null`](#null)\n  [`undefined`](#undefined)\n  [`void`](#void)\n\n- Objects\u003cbr\u003e\n  [`object`](#object)\n  [`record`](#record)\n  [`instanceOf`](#instanceof)\n\n- Collections\u003cbr\u003e\n  [`array`](#array)\n  [`tuple`](#tuple)\n  [`set`](#set)\n  [`map`](#map)\n\n- Dates\u003cbr\u003e\n  [`date`](#date)\n\n- Promises\u003cbr\u003e\n  [`promise`](#promise)\n\n- Functions\u003cbr\u003e\n  [`function`](#function-fn)\n  [`fn`](#function-fn)\n\n- Shape composition\u003cbr\u003e\n  [`union`](#union-or)\n  [`or`](#union-or)\n  [`intersection`](#intersection-and)\n  [`and`](#intersection-and)\n  [`not`](#not)\n\n- Unconstrained values\u003cbr\u003e\n  [`any`](#any)\n  [`unknown`](#unknown)\n\n- Other\u003cbr\u003e\n  [`convert`](#convert-convertasync)\n  [`lazy`](#lazy)\n  [`never`](#never)\n\n🍪\u0026ensp;**Cookbook**\n\n- [Type-safe URL query params](#type-safe-url-query-params)\n- [Type-safe environment variables](#type-safe-environment-variables)\n- [Type-safe CLI arguments](#type-safe-cli-arguments)\n- [Type-safe `localStorage`](#type-safe-localstorage)\n- [Rename object keys](#rename-object-keys)\n- [Conditionally applied shapes](#conditionally-applied-shapes)\n\n# Introduction\n\nLet's create a simple shape of a user:\n\n```ts\nimport * as d from 'doubter';\n\nconst userShape = d.object({\n  name: d.string(),\n  age: d.number()\n});\n// ⮕ Shape\u003c{ name: string, age: number }\u003e\n```\n\nThis is the shape of an object with two required properties \"name\" and \"age\". Shapes are the core concept in Doubter,\nthey are validation and transformation pipelines that have an input and an output.\n\nApply the shape to an input value with the [`parse`](#parsing-and-trying) method:\n\n```ts\nuserShape.parse({\n  name: 'John Belushi',\n  age: 30\n});\n// ⮕ { name: 'John Belushi', age: 30 }\n```\n\nIf the provided value is valid, then it is returned as is. If an incorrect value is provided, then a validation error is\nthrown:\n\n```ts\nuserShape.parse({\n  name: 'Peter Parker',\n  age: 'seventeen'\n});\n// ❌ ValidationError: type.number at /age: Must be a number\n```\n\nCurrently, the only constraint applied to the \"age\" property value is that it must be a number. Let's modify the shape\nto check that age is an integer and that user is an adult:\n\n```diff\n  const userShape = d.object({\n    name: d.string(),\n-   age: d.number()\n+   age: d.number().int().between(18, 100)\n  });\n```\n\nHere we added two operations to the number shape. Operations can check, refine, and alter input values. There are lots\nof operations [available through plugins](#built-in-plugins), and you can easily add your own [operation](#operations)\nwhen you need a custom logic.\n\nNow shape would not only check that the \"age\" is a number, but also assert that it is an integer between 18 and 100:\n\n```ts\nuserShape.parse({\n  name: 'Peter Parker',\n  age: 16\n});\n// ❌ ValidationError: number.gte at /age: Must be greater than or equal to 18\n```\n\nIf you are using TypeScript, you can infer the type of the value that the shape describes:\n\n```ts\ntype User = d.Input\u003ctypeof userShape\u003e;\n\nconst user: User = {\n  name: 'Dan Aykroyd',\n  age: 27\n};\n```\n\nRead more about [static type inference](#static-type-inference) and [runtime type introspection](#introduction).\n\n## Async shapes\n\nMost of the shapes are synchronous, but they may become asynchronous when one of the below is used:\n\n- [Async operations](#async-operations);\n- [Async conversions](#async-conversions);\n- [`d.promise`](#promise) that constrains the fulfilled value;\n- [Custom async shapes](#advanced-shapes).\n\nLet's have a look at a shape that synchronously checks that an input value is a string:\n\n```ts\nconst shape1 = d.string();\n// ⮕ Shape\u003cstring\u003e\n\nshape1.isAsync // ⮕ false\n```\n\nIf we add an async operation to the string shape, it would become asynchronous:\n\n```ts\nconst shape2 = d.string().checkAsync(\n  value =\u003e doAsyncCheck(value)\n);\n// ⮕ Shape\u003cstring\u003e\n\nshape2.isAsync // ⮕ true\n```\n\nThe shape that checks that the input value is a `Promise` instance is synchronous, because it doesn't have to wait for\nthe input promise to be fulfilled before ensuring that input has a proper type:\n\n```ts\nconst shape3 = d.promise();\n// ⮕ Shape\u003cPromise\u003cany\u003e\u003e\n\nshape3.isAsync // ⮕ false\n```\n\nBut if you want to check that a promise is fulfilled with a number, here when the shape becomes asynchronous:\n\n```ts\nconst shape4 = d.promise(d.number());\n// ⮕ Shape\u003cPromise\u003cnumber\u003e\u003e\n\nshape4.isAsync // ⮕ true\n```\n\nAsynchronous shapes don't support synchronous parsing, and would throw an error if it is used:\n\n```ts\nshape4.parse(Promise.resolve(42));\n// ❌ Error: Shape is async\n\nshape4.parseAsync(Promise.resolve(42));\n// ⮕ Promise { 42 } \n```\n\nOn the other hand, synchronous shapes support asynchronous parsing:\n\n```ts\nd.string().parseAsync('Mars');\n// ⮕ Promise { 'Mars' } \n```\n\nThe shape that depends on an asynchronous shape, also becomes asynchronous:\n\n```ts\nconst userShape = d.object({\n  avatar: d.promise(d.instanceOf(Blob))\n});\n// ⮕ Shape\u003c{ avatar: Promise\u003cBlob\u003e }\u003e\n\nuserShape.isAsync // ⮕ true\n```\n\n## Parsing and trying\n\nAll shapes can parse input values and there are several methods for that purpose. Consider a number shape:\n\n```ts\nconst shape1 = d.number();\n// ⮕ Shape\u003cnumber\u003e\n```\n\nThe [`parse`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#parse) method takes an input value and\nreturns an output value, or throws a [validation error](#validation-errors) if parsing fails:\n\n```ts\nshape.parse(42);\n// ⮕ 42\n\nshape.parse('Mars');\n// ❌ ValidationError: type.number at /: Must be a number\n```\n\nIt isn't always convenient to write a try-catch blocks to handle validation errors. Use the\n[`try`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#try) method in such cases:\n\n```ts\nshape.try(42);\n// ⮕ { ok: true, value: 42 }\n\nshape.try('Mars');\n// ⮕ { ok: false, issues: [ … ] }\n```\n\nRead more about issues in [Validation errors](#validation-errors) section.\n\nSometimes you don't care about validation errors, and want a default value to be returned if things go south. Use the\n[`parseOrDefault`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#parseOrDefault) method for that:\n\n```ts\nshape.parseOrDefault(42);\n// ⮕ 42\n\nshape.parseOrDefault('Mars');\n// ⮕ undefined\n\nshape.parseOrDefault('Pluto', 5.3361);\n// ⮕ 5.3361\n```\n\nIf you need a fallback value for a nested shape consider using the [`catch`](#fallback-value) method.\n\nFor [asynchronous shapes](#async-shapes) there's an alternative for each of those methods:\n[`parseAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#parseAsync),\n[`tryAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#tryAsync), and\n[`parseOrDefaultAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#parseOrDefaultAsync).\n\nMethods listed in this section can be safely detached from the shape instance:\n\n```ts\nconst { parseOrDefault } = d.string();\n\nparseOrDefault('Jill');\n// ⮕ 'Jill'\n\nparseOrDefault(42);\n// ⮕ undefined\n```\n\nAll parsing methods accept options argument.\n\n```ts\nd.number().parse('42', { earlyReturn: true });\n// ⮕ 42\n```\n\nFollowing options are available:\n\n\u003cdl\u003e\n\u003cdt\u003e\u003ccode\u003eearlyReturn\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nIf `true` then parsing is aborted after the first issue is encountered. Refer to [Early return](#early-return) section\nfor more details.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003econtext\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe custom context that can be accessed from custom check callbacks, refinement predicates, alteration callbacks,\nconverters, and fallback functions. Refer to [Parsing context](#parsing-context) section for more details.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003emessages\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nAn object that maps an issue code to a default message. Refer to [Override default messages](#override-default-messages)\nsection for more details.\n\n\u003c/dd\u003e\n\u003c/dl\u003e\n\n## Static type inference\n\n\u003e [!IMPORTANT]\\\n\u003e Static type inference feature requires TypeScript 4.1 + with enabled\n\u003e [`strictNullChecks`](https://www.typescriptlang.org/tsconfig#strictNullChecks).\n\nSince shapes can transform values, they can have different input and output types. For example, this string shape has\nthe same input an output:\n\n```ts\nconst shape1 = d.string();\n// ⮕ Shape\u003cstring\u003e\n\nshape1.parse('Pluto');\n// ⮕ 'Pluto'\n\nshape1.parse(undefined);\n// ❌ ValidationError: type.string at /: Must be a string\n```\n\nLet's derive a new shape that would [replace `undefined`](#optional-and-non-optional) input values with a default value\n\"Mars\":\n\n```ts\nconst shape2 = shape1.optional('Mars');\n// ⮕ Shape\u003cstring | undefined, string\u003e\n\nshape2.parse('Pluto');\n// ⮕ 'Pluto'\n\n// 🟡 Replaces undefined with the default value\nshape2.parse(undefined);\n// ⮕ 'Mars'\n```\n\nInfer the input and output types of `shape2`:\n\n```ts\ntype Shape2Input = d.Input\u003ctypeof shape2\u003e;\n// ⮕ string | undefined\n\ntype Shape2Output = d.Output\u003ctypeof shape2\u003e;\n// ⮕ string\n```\n\nBesides static type inference, you can check at runtime what input types and literal values does the shape accept using\n[shape introspection](#introspection):\n\n```ts\nshape2.inputs;\n// ⮕ [Type.STRING, undefined]\n\nshape2.accepts(d.Type.STRING);\n// ⮕ true\n\nshape2.accepts('Mars');\n// ⮕ true\n\nshape2.accepts(42);\n// ⮕ false\n```\n\n# Validation errors\n\nValidation errors which are thrown by [parsing methods](#parsing-and-trying), and\n[`Err`](https://smikhalevski.github.io/doubter/next/interfaces/core.Err.html) objects returned by\n`try` and `tryAsync` methods have the `issues` property which holds an array of validation issues:\n\n```ts\nconst shape = d.object({ age: d.number() });\n// ⮕ Shape\u003c{ age: number }\u003e\n\nconst result = shape.try({ age: 'seventeen' });\n```\n\nThe `result` contains the [`Err`](https://smikhalevski.github.io/doubter/next/interfaces/core.Err.html) object\nwith the array of issues:\n\n```json5\n{\n  ok: false,\n  issues: [\n    {\n      code: 'type.number',\n      path: ['age'],\n      input: 'seventeen',\n      message: 'Must be a number',\n      param: undefined,\n      meta: undefined\n    }\n  ]\n}\n```\n\n\u003cdl\u003e\n\u003cdt\u003e\u003ccode\u003ecode\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe code of the validation issue. In the example above, `\"type\"` code refers to a failed number type check. While shapes\ncheck input value type and raise type issues, there also [various operations](#built-in-plugins) that also may raise\nissues with unique codes, see the table below.\n\nYou can add [a custom operation](#operations) to any shape and return an issue with your custom code.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003epath\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe object path as an array of keys, or `undefined` if there's no path. Keys can be strings, numbers (for example, array\nindices), symbols, and any other values since they can be `Map` keys, see [`d.map`](#map).\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003einput\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe input value that caused a validation issue. Note that if the shape applies [type coercion](#type-coercion),\n[conversions](#conversions), or if there are operations that transform the value, then `input` may contain an already\ntransformed value.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003emessage\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe human-readable issue message. Refer to [Localization](#localization) section for more details.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003eparam\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe parameter value associated with the issue. For built-in checks, the parameter value depends on `code`, see the table\nbelow.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003emeta\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe optional metadata associated with the issue. Refer to [Annotations and metadata](#annotations-and-metadata) section\nfor more details.\n\n\u003c/dd\u003e\n\u003c/dl\u003e\n\n\u003cbr/\u003e\n\n| Code                | Caused by                                           | Param                                                 |\n|:--------------------|:----------------------------------------------------|:------------------------------------------------------|\n| `any.deny`          | [`shape.deny(x)`](#deny-a-value)                    | The denied value `x`                                  |\n| `any.exclude`       | [`shape.exclude(…)`](#exclude-a-shape)              | The excluded shape                                    |\n| `any.refine`        | [`shape.refine(…)`](#refinements)                   | The predicate callback                                |\n| `array.includes`    | [`d.array().includes(x)`](#array)                   | The included value `x`                                |\n| `array.min`         | [`d.array().min(n)`](#array)                        | The minimum array length `n`                          |\n| `array.max`         | [`d.array().max(n)`](#array)                        | The maximum array length `n`                          |\n| `bigint.min`        | [`d.bigint().min(n)`](#bigint)                      | The minimum value `n`                                 |\n| `bigint.max`        | [`d.bigint().max(n)`](#bigint)                      | The maximum value `n`                                 |\n| `date.min`          | [`d.date().min(n)`](#date)                          | The minimum value `n`                                 |\n| `date.max`          | [`d.date().max(n)`](#date)                          | The maximum value `n`                                 |\n| `number.finite`     | [`d.number().finite()`](#number)                    | —                                                     |\n| `number.int`        | [`d.number().int()`](#number)                       | —                                                     |\n| `number.gt`         | [`d.number().gte(x)`](#number)                      | The minimum value `x`                                 |\n| `number.lt`         | [`d.number().lte(x)`](#number)                      | The maximum value `x`                                 |\n| `number.gte`        | [`d.number().gt(x)`](#number)                       | The exclusive minimum value `x`                       |\n| `number.lte`        | [`d.number().lt(x)`](#number)                       | The exclusive maximum value `x`                       |\n| `number.multipleOf` | [`d.number().multipleOf(x)`](#number)               | The divisor `x`                                       |\n| `object.allKeys`    | [`d.object().allKeys(keys)`](#key-relationships)    | The `keys` array                                      |\n| `object.notAllKeys` | [`d.object().notAllKeys(keys)`](#key-relationships) | The `keys` array                                      |\n| `object.orKeys`     | [`d.object().orKeys(keys)`](#key-relationships)     | The `keys` array                                      |\n| `object.xorKeys`    | [`d.object().xorKeys(keys)`](#key-relationships)    | The `keys` array                                      |\n| `object.oxorKeys`   | [`d.object().oxorKeys(keys)`](#key-relationships)   | The `keys` array                                      |\n| `object.exact`      | [`d.object().exact()`](#unknown-keys)               | The array of unknown keys                             |\n| `object.plain`      | [`d.object().plain()`](#object)                     | —                                                     |\n| `set.min`           | [`d.set().min(n)`](#set)                            | The minimum `Set` size `n`                            |\n| `set.max`           | [`d.set().max(n)`](#set)                            | The maximum `Set` size `n`                            |\n| `string.nonBlank`   | [`d.string().nonBlank()`](#string)                  | —                                                     |\n| `string.min`        | [`d.string().min(n)`](#string)                      | The minimum string length `n`                         |\n| `string.max`        | [`d.string().max(n)`](#string)                      | The maximum string length `n`                         |\n| `string.regex`      | [`d.string().regex(re)`](#string)                   | The regular expression `re`                           |\n| `string.includes`   | [`d.string().includes(x)`](#string)                 | The included string `x`                               |\n| `string.startsWith` | [`d.string().startsWith(x)`](#string)               | The substring `x`                                     |\n| `string.endsWith`   | [`d.string().endsWith(x)`](#string)                 | The substring `x`                                     |\n| `type.array`        | [`d.array()`](#array)                               | —                                                     |\n| `type.bigint`       | [`d.bigint()`](#bigint)                             | —                                                     |\n| `type.boolean`      | [`d.boolean()`](#boolean-bool)                      | —                                                     |\n| `type.const`        | [`d.const(x)`](#const)                              | The expected constant value `x`                       |\n| `type.date`         | [`d.date()`](#date)                                 | —                                                     |\n| `type.enum`         | [`d.enum(…)`](#enum)                                | The array of unique value                             |\n| `type.function`     | [`d.function()`](#function-fn)                      | —                                                     |\n| `type.instanceOf`   | [`d.instanceOf(Class)`](#instanceof)                | The class constructor `Class`                         |\n| `type.intersection` | [`d.and(…)`](#intersection-and)                     | —                                                     |\n| `type.map`          | [`d.map()`](#map)                                   | —                                                     |\n| `type.never`        | [`d.never()`](#never)                               | —                                                     |\n| `type.number`       | [`d.number()`](#number)                             | —                                                     |\n| `type.object`       | [`d.object()`](#object)                             | —                                                     |\n| `type.promise`      | [`d.promise()`](#promise)                           | —                                                     |\n| `type.tuple`        | [`d.tuple(…)`](#tuple)                              | The expected tuple length                             |\n| `type.set`          | [`d.set()`](#set)                                   | —                                                     |\n| `type.string`       | [`d.string()`](#string)                             | —                                                     |\n| `type.symbol`       | [`d.symbol()`](#symbol)                             | —                                                     |\n| `type.union`        | [`d.or(…)`](#union-or)                              | [Issues raised by a union](#issues-raised-by-a-union) |\n\n# Operations\n\n\u003e [!IMPORTANT]\\\n\u003e While operations are a powerful tool, most of the time you don't need to add operations directly. Instead, you can use\n\u003e the higher-level API: [checks](#checks), [refinements](#refinements), and [alterations](#alterations).\n\nOperations can check and transform the shape output value. Let's create a shape with an operation that trims an input\nstring:\n\n```ts\nconst shape1 = d.string().addOperation(value =\u003e {\n  return { ok: true, value: value.trim() };\n});\n// ⮕ StringShape\n\nshape1.parse('  Space  ');\n// ⮕ 'Space'\n```\n\nOperations added via [`addOperation`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#addOperation)\nmust return a [`Result`](https://smikhalevski.github.io/doubter/next/types/core.Result.html):\n\n- `null` if the value is valid and unchanged;\n- an [`Ok`](https://smikhalevski.github.io/doubter/next/interfaces/core.Ok.html) object (as in example above) if the\n  value was transformed;\n- an array of [`Issue`](https://smikhalevski.github.io/doubter/next/interfaces/core.Issue.html) objects if the operation\n  has failed.\n\nMultiple operations can be added to shape, and they are executed in the same order they were added. To access all\noperations that were added use the\n[`operations`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#operations) property.\n\nIn contrast to [conversions](#conversions) and [pipes](#shape-piping), operations don't change the base shape. So you\ncan mix them with other operations that belong to the prototype of the base shape:\n\n```ts\nconst shape2 = d\n  .string()\n  .addOperation(value =\u003e {\n    return { ok: true, value: value.trim() };\n  })\n  // 🟡 d.StringShape.prototype.min\n  .min(6);\n\nshape2.parse('  Neptune  ');\n// ⮕ 'Neptune'\n\nshape2.parse('  Moon  ');\n// ❌ ValidationError: string.min at /: Must have the minimum length of 6\n```\n\nOperations can be parameterized. This is particularly useful if you want to reuse the operation multiple times.\n\n```ts\nconst checkRegex: d.OperationCallback = (value, param) =\u003e {\n  if (param.test(value)) {\n    return null;\n  }\n  return [{ message: 'Must match ' + param }];\n};\n\n// 🟡 Pass a param when operation is added\nconst shape3 = d.string().addOperation(checkRegex, { param: /a/ });\n// ⮕ StringShape\n\nshape3.parse('Mars');\n// ⮕ 'Mars'\n\nshape3.parse('Venus');\n// ❌ ValidationError: unknown at /: Must match /a/\n```\n\nOperations have access to parsing options, so you can provide [a custom context](#parsing-context) to change the\noperation behaviour:\n\n```ts\nconst shape4 = d.string().addOperation((value, param, options) =\u003e {\n  return {\n    ok: true,\n    value: value.substring(options.context.substringStart)\n  };\n});\n// ⮕ StringShape\n\nshape4.parse(\n  'Hello, Bill',\n  {\n    // 🟡 Provide the context during parsing\n    context: { substringStart: 7 }\n  }\n);\n// ⮕ 'Bill'\n```\n\nOperations can throw a [`ValidationError`](https://smikhalevski.github.io/doubter/next/classes/core.ValidationError.html)\nto notify Doubter that parsing issues occurred. While this has the same effect as returning an array of issues, it is\nrecommended to throw a `ValidationError` as the last resort since catching errors has a high performance penalty.\n\n```ts\nconst shape5 = d.number().addOperation(value =\u003e {\n  if (value \u003c 32) {\n    throw new ValidationError([{ code: 'too_small' }]);\n  }\n  return null;\n});\n\nshape5.try(16);\n// ⮕ { ok: false, issues: [{ code: 'too_small' }] }\n```\n\n## Tolerance for issues\n\nOperations are executed only if the base shape type requirements are satisfied:\n\n```ts\nconst shape = d.string().addOperation(value =\u003e {\n  return { ok: true, value: value.trim() };\n});\n\n// 🟡 Operation isn't executed because 42 isn't a string\nshape.parse(42);\n// ❌ ValidationError: type.string at /: Must be a string\n```\n\nFor composite shapes, operations may become non-type-safe. Let's consider an object shape with an operation:\n\n```ts\nconst checkUser: d.OpeationCallback = user =\u003e {\n  if (user.age \u003c user.yearsOfExperience) {\n    return [{ code: 'invalid_age' }];\n  }\n  return null;\n};\n\nconst userShape = d\n  .object({\n    age: d.number(),\n    yearsOfExperience: d.number()\n  })\n  .addOperation(checkUser);\n// ⮕ Shape\u003c{ age: number, yearsOfExperience: number }\u003e\n```\n\nThe `checkUser` operation is guaranteed to receive an object, but its properties aren't guaranteed to have correct\ntypes.\n\nUse [`tolerance`](https://smikhalevski.github.io/doubter/next/types/core.OperationOptions.html#tolerance) operation\noption to change how the operation behaves in case there are issues caused by the shape it is added to:\n\n\u003cdl\u003e\n\u003cdt\u003e\u003ccode\u003e\"skip\"\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nIf the shape or preceding operations have raised issues, then the operation is skipped but consequent operations are\nstill applied.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003e\"abort\"\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nIf the shape or preceding operations have raised issues, then the operation is skipped and consequent operations aren't\napplied. Also, if this operation itself raises issues then consequent operations aren't applied.\n\n\u003c/dd\u003e\n\u003cdt\u003e\u003ccode\u003e\"auto\"\u003c/code\u003e\u003c/dt\u003e\n\u003cdd\u003e\n\nThe operation is applied regardless of previously raised issues. This is the default behavior.\n\n\u003c/dd\u003e\n\u003c/dl\u003e\n\nSo to make `checkUser` operation type-safe, we can use \"skip\" or \"abort\".\n\n```diff\n  const userShape = d\n    .object({\n      age: d.number(),\n      yearsOfExperience: d.number()\n    })\n-   .addOperation(checkUser);\n+   .addOperation(checkUser, { tolerance: 'abort' });\n```\n\nSome shapes cannot guarantee that the input value is of the required type. For example, if any of the underlying shapes\nin an intersection shape have raised issues, an intersection shape itself cannot guarantee that its operations would\nreceive the value of the expected type, so it doesn't apply any operations if there are issues.\n\nThese shapes never apply operations if an underlying shape has raised an issue:\n\n- [`DenyShape`](#deny-a-value)\n- [`IntersectionShape`](#intersection-and)\n- [`LazyShape`](#lazy)\n- [`PipeShape`](#shape-piping)\n- [`ReplaceShape`](#replace-a-value)\n- [`ConvertShape`](#conversions)\n- [`UnionShape`](#union-or)\n\n## Async operations\n\nOperations callbacks can be asynchronous. They have the same set of arguments as synchronous alternative, by must return\na promise. Consequent operations after the asynchronous operation would wait for its result:\n\n```ts\nconst shape = d\n  .string()\n  .addAsyncOperation(async value =\u003e {\n    if (await doAsyncCheck(value)) {\n      return null;\n    }\n    return [{ code: 'kaputs' }];\n  });\n\nshape.isAsync;\n// ⮕ true\n\nshape.parseAsync('Hello');\n```\n\nAdding an async operation to the shape, makes shape itself async, so use\n[`parseAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#parseAsync),\n[`tryAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#tryAsync), or\n[`parseOrDefaultAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#parseOrDefaultAsync).\n\n## Checks\n\nChecks are the most common [operations](#operations) that allow constraining the input value beyond type assertions. For\nexample, if you want to constrain a numeric input to be greater than or equal to 5:\n\n```ts\nconst shape = d.number().check(value =\u003e {\n  if (value \u003c 5) {\n    // 🟡 Return an issue, or an array of issues\n    return { code: 'kaputs' };\n  }\n});\n// ⮕ NumberShape\n\nshape.parse(10);\n// ⮕ 10\n\nshape.parse(3);\n// ❌ ValidationError: kaputs at /\n```\n\nA check callback receives the shape output value and must return an issue or an array of issues if the value is invalid.\nIf the value is valid, a check callback must return `null`, `undefined`, or an empty array.\n\nAdd asynchronous checks using\n[`checkAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#checkAsync). This method has the same\nsemantics as [`check`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#check) but returns a promise\nand [makes the shape asynchronous](#async-shapes).\n\n\u003e [!NOTE]\\\n\u003e You can [parameterize](#operations) checks and [set tolerance for issues](#tolerance-for-issues) the same way as any\n\u003e other operation.\n\nMost shapes have [a set of built-in checks](#built-in-plugins). The check we've just implemented above is called\n[`gte`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#gte) (greater than equals):\n\n```ts\nd.number().gte(5);\n```\n\nAdd as many checks as you need to the shape. You can mix built-in checks with any other custom operations, they are\nexecuted in the same order they were added.\n\n```ts\nd.string().max(4).regex(/a/).try('Pluto');\n```\n\nIn the example above, an [`Err`](https://smikhalevski.github.io/doubter/next/interfaces/core.Err.html) object is\nreturned:\n\n```json5\n{\n  ok: false,\n  issues: [\n    {\n      code: 'string.max',\n      path: [],\n      input: 'Pluto',\n      message: 'Must have the maximum length of 4',\n      param: 4,\n      meta: undefined\n    },\n    {\n      code: 'string.regex',\n      path: [],\n      input: 'Pluto',\n      message: 'Must match the pattern /a/',\n      param: /a/,\n      meta: undefined\n    }\n  ]\n}\n```\n\n\u003e [!NOTE]\\\n\u003e You can find the list of issue codes and corresponding param values in [Validation errors](#validation-errors)\n\u003e section.\n\n## Refinements\n\nRefinements are [operations](#operations) that use a predicate callback to validate an input. For example, the shape\nbelow would raise an issue if the input string is less than six characters long.\n\n```ts\nconst shape1 = d.string().refine(value =\u003e value.length \u003e 5);\n// ⮕ Shape\u003cstring\u003e\n\nshape1.parse('Uranus');\n// ⮕ 'Uranus'\n\nshape1.parse('Mars');\n// ❌ ValidationError: any.refine at /: Must conform the predicate\n```\n\nAdd asynchronous refinements using\n[`refineAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#refineAsync). This method has the\nsame semantics as [`refine`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#refine) but returns a\npromise and [makes the shape asynchronous](#async-shapes).\n\nUse refinements to [narrow](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the output type of the shape:\n\n```ts\nfunction isMarsOrPluto(value: string): value is 'Mars' | 'Pluto' {\n  return value === 'Mars' || value === 'Pluto';\n}\n\nd.string().refine(isMarsOrPluto)\n// ⮕ Shape\u003cstring, 'Mars' | 'Pluto'\u003e\n```\n\nBy default, `refine` raises issues which have the [`\"any.refine\"`](#validation-errors) code. You can provide a custom\ncode:\n\n```ts\nconst shape2 = d.string().refine(\n  isMarsOrPluto,\n  {\n    code: 'illegal_planet',\n    message: 'Must be Mars or Pluto'\n  }\n);\n\nshape2.parse('Venus');\n// ❌ ValidationError: illegal_planet at /: Must be Mars or Pluto\n```\n\n\u003e [!NOTE]\\\n\u003e You can [parameterize](#operations) refinements and [set tolerance for issues](#tolerance-for-issues) the same way as\n\u003e any other operation.\n\n## Alterations\n\nAlterations are [operations](#operations) that synchronously transform the shape output value without changing its type.\nFor example, let's consider a string shape that trims the value and then checks that it has at least 3 characters:\n\n```ts\nd.string()\n  .alter(value =\u003e value.trim())\n  .min(3);\n// ⮕ StringShape\n```\n\nAdd asynchronous alterations using\n[`alterAsync`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#alterAsync). This method has the\nsame semantics as [`alter`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#alter) but returns a\npromise and [makes the shape asynchronous](#async-shapes).\n\nUse any transformation library in conjunction with alternations:\n\n```ts\nd.number().alter(Math.abs).alter(Math.pow, { param: 3 });\n```\n\nAlteration callbacks must return the value of the same type, so consequent operations are type-safe. If you want to\nconvert the shape output value to another type, consider using [conversions](#conversions).\n\n\u003e [!NOTE]\\\n\u003e You can [parameterize](#operations) alterations and [set tolerance for issues](#tolerance-for-issues) the same way as\n\u003e any other operation.\n\n# Conversions\n\nConversions are close relatives of [alterations](#alterations) that also transform shape output value. The main\ndifference from alterations is that conversions can change the shape output type. Let's consider a shape that takes a\nstring as an input and converts it to a number:\n\n```ts\nconst shape = d.string().convert(parseFloat);\n// ⮕ Shape\u003cstring, number\u003e\n```\n\nThis shape ensures that the input value is a string and passes it to a converter callback:\n\n```ts\nshape.parse('42');\n// ⮕ 42\n\nshape.parse('seventeen');\n// ⮕ NaN\n```\n\nThrow a [`ValidationError`](https://smikhalevski.github.io/doubter/next/classes/core.ValidationError.html)\ninside the callback to notify parser that the conversion cannot be successfully completed:\n\n```ts\nfunction toNumber(input: string): number {\n  const output = parseFloat(input);\n\n  if (isNaN(output)) {\n    throw new d.ValidationError([{ code: 'nan' }]);\n  }\n  return output;\n}\n\nconst shape = d.string().convert(toNumber);\n\nshape.parse('42');\n// ⮕ 42\n\nshape.parse('seventeen');\n// ❌ ValidationError: nan at /\n```\n\n## Async conversions\n\nLet's consider a _synchronous_ conversion:\n\n```ts\nconst syncShape1 = d.string().convert(\n  value =\u003e 'Hello, ' + value\n);\n// ⮕ Shape\u003cstring\u003e\n\nsyncShape1.isAsync // ⮕ false\n\nsyncShape1.parse('Jill');\n// ⮕ 'Hello, Jill'\n```\n\nThe converter callback receives and returns a string and so does `syncShape1`.\n\nNow lets return a promise from the converter callback:\n\n```ts\nconst syncShape2 = d.string().convert(\n  value =\u003e Promise.resolve('Hello, ' + value)\n);\n// ⮕ Shape\u003cstring, Promise\u003cstring\u003e\u003e\n\nsyncShape2.isAsync // ⮕ false\n\nsyncShape2.parse('Jill');\n// ⮕ Promise\u003cstring\u003e\n```\n\nNotice that `syncShape2` is asymmetric: it expects a string input and converts it to a `Promise\u003cstring\u003e`. `syncShape2`\nis still synchronous, since the converter callback _synchronously wraps_ a value in a promise.\n\nNow let's create an _asynchronous_ shape using the async conversion:\n\n```ts\nconst asyncShape1 = d.string().convertAsync(\n  value =\u003e Promise.resolve('Hello, ' + value)\n);\n// ⮕ Shape\u003cstring\u003e\n\n// 🟡 Notice that the shape is async\nasyncShape1.isAsync // ⮕ true\n\nawait asyncShape1.parseAsync('Jill');\n// ⮕ Promise { 'Hello, Jill' }\n```\n\nNotice that `asyncShape1` converts the input string value to output string but the conversion itself is asynchronous.\n\nA shape is asynchronous if it uses asynchronous conversions. Here's an asynchronous object shape:\n\n```ts\nconst asyncShape2 = d.object({\n  foo: d.string().convertAsync(\n    value =\u003e Promise.resolve(value)\n  )\n});\n// ⮕ Shape\u003c{ foo: string }\u003e\n\nasyncShape2.isAsync // ⮕ true\n```\n\nRefer to [Async shapes](#async-shapes) section for more details on when shapes can become asynchronous.\n\n# Early return\n\nBy default, Doubter collects all issues during parsing. In some cases, you may want to halt parsing and raise a\nvalidation error as soon as the first issue was encountered. To do this, pass the\n[`earlyReturn`](https://smikhalevski.github.io/doubter/next/interfaces/core.ParseOptions.html#earlyReturn)\noption to the [parsing methods](#parsing-and-trying).\n\n```ts\nd.string()\n  .max(4)\n  .regex(/a/)\n  .try('Pluto', { earlyReturn: true });\n```\n\nThis would return the [`Err`](https://smikhalevski.github.io/doubter/next/interfaces/core.Err.html) object with\nonly one issue:\n\n```json5\n{\n  ok: false,\n  issues: [\n    {\n      code: 'string.max',\n      path: undefined,\n      input: 'Pluto',\n      message: 'Must have the maximum length of 4',\n      param: 4,\n      meta: undefined\n    }\n  ]\n}\n```\n\n# Annotations and metadata\n\nShapes and issues can be enriched with additional metadata.\n\nAdd an annotation to a shape:\n\n```ts\nconst shape = d.string().annotate({ description: 'Username' });\n\nshape.annotations;\n// ⮕ { description: 'Username' }\n```\n\n[`annotate`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#annotate) returns the clone of the\nshape with updated annotations. Annotations are merged when you add them:\n\n```ts\nshape.annotate({ foo: 'bar' }).annotations;\n// ⮕ { description: 'Username', foo: 'bar' }\n```\n\n[Validation issues](#validation-errors) have a\n[`meta`](https://smikhalevski.github.io/doubter/next/interfaces/core.Issue.html#meta) property that you can use\nto store an arbitrary data.\n\nYou can pass the [`meta`](https://smikhalevski.github.io/doubter/next/interfaces/core.IssueOptions.html#meta)\noption to any [built-in check](#checks) and its value is assigned to the `meta` property of the raised validation issue.\n\n```ts\nconst shape = d.number().gt(5, { meta: 'Useful data' });\n// ⮕ Shape\u003cnumber\u003e\n\nconst result = shape.try(2);\n// ⮕ { ok: false, issues: … }\n\nif (!result.ok) {\n  result.issues[0].meta // ⮕ 'Useful data'\n}\n```\n\nThis comes handy if you want to enhance an issue with an additional data that can be used later during issues\nprocessing. For example, during [localization](#localization).\n\n# Parsing context\n\nInside [operation](#operations) callbacks, [check](#checks) callbacks, [refinement predicates](#refinements),\n[alteration](#alterations) callbacks, [converters](#conversions), [fallback](#fallback-value) functions, and\n[message](#localization) callbacks you can access options passed to the parser. The\n[`context`](https://smikhalevski.github.io/doubter/next/interfaces/core.ParseOptions.html#context) option may\nstore an arbitrary data, which is `undefined` by default.\n\nFor example, here's how you can use context to convert numbers to formatted strings:\n\n```ts\nconst shape = d.number().convert(\n  (value, options) =\u003e new Intl.NumberFormat(options.context.locale).format(value)\n);\n// ⮕ Shape\u003cnumber, string\u003e\n\nshape.parse(\n  1000,\n  {\n    // 🟡 Pass a context\n    context: { locale: 'en-US' }\n  }\n);\n// ⮕ '1,000'\n```\n\n# Shape piping\n\nWith shape piping you to can pass the shape output to another shape.\n\n```ts\nd.string()\n  .convert(parseFloat)\n  .to(d.number().lt(5).gt(10));\n// ⮕ Shape\u003cstring, number\u003e\n```\n\nFor example, you can validate that an input value is an [instance of a class](#instanceof) and then validate its\nproperties using [`object`](#object):\n\n```ts\nclass Planet {\n  constructor(readonly name: string) {}\n}\n\nconst shape = d.instanceOf(Planet).to(\n  d.object({\n    name: d.string().min(4)\n  })\n);\n\nshape.parse({ name: 'Pluto' });\n// ❌ ValidationError: type.instanceOf at /: Must be a class instance\n\nshape.parse(new Planet('X'));\n// ❌ ValidationError: string.min at /name: Must have the minimum length of 4\n\nshape.parse(new Planet('Mars'));\n// ⮕ Planet { name: 'Mars' }\n```\n\n# Replace, allow, and deny a value\n\nAll shapes support [`replace`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#replace),\n[`allow`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#allow), and\n[`deny`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#deny) methods that change how\nseparate literal values are processed.\n\n## Replace a value\n\nYou can replace an input value with an output value:\n\n```ts\nconst shape1 = d.enum(['Mars', 'Pluto']).replace('Pluto', 'Jupiter');\n// ⮕ Shape\u003c'Mars' | 'Pluto', 'Mars' | 'Jupiter'\u003e\n\nshape1.parse('Mars');\n// ⮕ 'Mars'\n\nshape1.parse('Pluto');\n// ⮕ 'Jupiter'\n```\n\nWith `replace` you can extend possible input values:\n\n```ts\nd.const('Venus').replace('Mars', 'Uranus');\n// ⮕ Shape\u003c'Venus' | 'Mars', 'Venus' | 'Uranus'\u003e\n```\n\nThis would also work with non-literal input types:\n\n```ts\nd.number().replace(0, 'zero');\n// ⮕ Shape\u003cnumber, number | 'zero'\u003e\n```\n\n`replace` narrows its arguments to literal type but in TypeScript type system not all values have a separate literal\ntype. For example, there's no literal type for `NaN` and `Infinity` values. In such cases `replace` doesn't exclude the\nreplaced value type from the output type:\n\n```ts\nd.enum([33, 42]).replace(NaN, 0);\n// ⮕ Shape\u003cnumber, 33 | 42 | 0\u003e\n```\n\nReplaced values aren't processed by the underlying shape:\n\n```ts\nconst shape2 = d.number().gte(3).replace(0, 'zero');\n// ⮕ Shape\u003cnumber | 'zero'\u003e\n\nshape2.parse(2);\n// ❌ ValidationError: number.gte at /: Must be greater than 3\n\n// 🟡 Notice that 0 doesn't satisfy the gte constraint\nshape2.parse(0);\n// ⮕ 'zero'\n```\n\n## Allow a value\n\nYou can allow a value as both input and output:\n\n```ts\nd.const('Mars').allow('Pluto');\n// ⮕ Shape\u003c'Mars' | 'Pluto'\u003e\n```\n\n`allow` follows exactly the same semantics as [`replace`](#replace-a-value).\n\nYou can allow a value for a non-literal input types:\n\n```ts\nconst shape = d.number().finite().allow(NaN);\n// ⮕ Shape\u003cnumber\u003e\n\nshape.parse(NaN);\n// ⮕ NaN\n\nshape.parse(Infinity);\n// ❌ ValidationError: number.finite at /: Must be a finite number\n```\n\n## Deny a value\n\nConsider the enum shape:\n\n```ts\nconst shape1 = d.enum(['Mars', 'Pluto', 'Jupiter']);\n// ⮕ Shape\u003c'Mars' | 'Pluto' | 'Jupiter'\u003e\n```\n\nTo remove a value from this enum you can use the\n[`deny`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#deny) method:\n\n```ts\nshape1.deny('Pluto');\n// ⮕ Shape\u003c'Mars' | 'Jupiter'\u003e\n```\n\nValue denial works with any shape. For example, you can deny a specific number:\n\n```ts\nconst shape2 = d.number().deny(42);\n// ⮕ Shape\u003cnumber\u003e\n\nshape2.parse(33);\n// ⮕ 33\n\nshape2.parse(42);\n// ❌ ValidationError: any.deny at /: Must not be equal to 42\n```\n\n`deny` prohibits value for _both input and output_:\n\n```ts\nconst shape3 = d.number().convert(value =\u003e value * 2).deny(42);\n// ⮕ Shape\u003cnumber\u003e\n\nshape3.parse(21);\n// ❌ ValidationError: any.deny at /: Must not be equal to 42\n```\n\n# Optional and non-optional\n\nMarking a shape as optional [allows `undefined`](#allow-a-value) in both its input and output:\n\n```ts\nd.string().optional();\n// ⮕ Shape\u003cstring | undefined\u003e\n```\n\nYou can provide a default value of any type, so it would be used as an output if input value is `undefined`:\n\n```ts\nd.string().optional(42);\n// ⮕ Shape\u003cstring | undefined, string | 42\u003e\n```\n\nYou can achieve the same behaviour using a union:\n\n```ts\nd.or([\n  d.string(),\n  d.undefined()\n]);\n// ⮕ Shape\u003cstring | undefined\u003e\n```\n\nOr using [`allow`](#allow-a-value):\n\n```ts\nd.string().allow(undefined);\n// ⮕ Shape\u003cstring | undefined\u003e\n```\n\nYou can mark any shape as non-optional which effectively [denies `undefined`](#deny-a-value) values from both\ninput and output. For example, lets consider a union of an optional string and a number:\n\n```ts\nconst shape1 = d.or([\n  d.string().optional(),\n  d.number()\n]);\n// ⮕ Shape\u003cstring | undefined | number\u003e\n\nshape1.parse(undefined);\n// ⮕ undefined\n\nconst shape2 = shape1.nonOptional();\n// ⮕ Shape\u003cstring | number\u003e\n\nshape2.parse(undefined);\n// ❌ ValidationError: any.deny at /: Must not be equal to undefined\n```\n\n# Nullable and nullish\n\nMarking a shape as nullable [allows `null`](#allow-a-value) for both input and output:\n\n```ts\nd.string().nullable();\n// ⮕ Shape\u003cstring | null\u003e\n```\n\nYou can provide a default value, so it would be used as an output if input value is `null`:\n\n```ts\nd.string().nullable(42);\n// ⮕ Shape\u003cstring | null, string | 42\u003e\n```\n\nTo allow both `null` and `undefined` values use `nullish`:\n\n```ts\nd.string().nullish();\n// ⮕ Shape\u003cstring | null | undefined\u003e\n```\n\n`nullish` also supports the default value:\n\n```ts\nd.string().nullish(8080);\n// ⮕ Shape\u003cstring | null | undefined, string | 8080\u003e\n```\n\n# Exclude a shape\n\nShape exclusions work the same way as `Exclude` helper type in TypeScript. When an exclusion is applied, the output\nvalue returned by the underlying shape _must not conform_ the excluded shape.\n\n```ts\nconst shape = d.enum(['Mars', 'Venus', 'Pluto']).exclude(d.const('Pluto'));\n// ⮕ Shape\u003c'Mars' | 'Venus' | 'Pluto', 'Mars' | 'Venus'\u003e\n\nshape.parse('Mars');\n// ⮕ 'Mars'\n\nshape.parse('Pluto');\n// ❌ ValidationError: any.exclude at /: Must not conform the excluded shape\n```\n\nExclusions work with any shape combinations:\n\n```ts\nd.or([d.number(), d.string()]).exclude(d.string());\n// ⮕ Shape\u003cnumber | string, number\u003e\n```\n\nSometimes you need an exclusion at runtime, but don't need it on the type level. For example, let's define a shape that\nallows any number except the \\[3, 5] range:\n\n```ts\n// 🟡 Note that the shape output is inferred as never\nd.number().exclude(d.number().min(3).max(5));\n// ⮕ Shape\u003cnumber, never\u003e\n```\n\nSince the excluded shape constrains the `number` type, the output type is inferred as `never`. While the excluded shape\nonly restricts a limited range of numbers, there's no way to express this in TypeScript. So here's the workaround:\n\n```ts\nd.number().not(d.number().min(3).max(5));\n// ⮕ Shape\u003cnumber\u003e\n```\n\n`not` works exactly like `exclude` at runtime, but it doesn't perform the exclusion on the type level.\n\n```ts\nd.enum(['Bill', 'Jill']).not(d.const('Jill'));\n// ⮕ Shape\u003c'Bill', 'Jill'\u003e\n```\n\nYou can also use [`d.not`](#not) to negate an arbitrary shape.\n\n# Deep partial\n\nAll object-like shapes (objects, arrays, maps, sets, promises, etc.) can be converted to a deep partial alternative\nusing `deepPartial` method:\n\n```ts\nconst shape1 = d.array(\n  d.object({\n    name: d.string(),\n    age: d.number()\n  })\n);\n// ⮕ Shape\u003c{ name: string, age: number }[]\u003e\n\nshape1.deepPartial();\n// ⮕ Shape\u003cArray\u003c{ name?: string, age?: number } | undefined\u003e\u003e\n```\n\nUnions, intersections and lazy shapes can also be converted to deep partial:\n\n```ts\nconst shape2 = d\n  .or([\n    d.number(),\n    d.object({ name: d.string() })\n  ])\n  .deepPartial()\n// ⮕ Shape\u003cnumber | { name?: string }\u003e\n\nshape2.parse(42);\n// ⮕ 42\n\nshape2.parse({ name: undefined });\n// ⮕ { name: undefined }\n\nshape2.parse({ name: 'Frodo' });\n// ⮕ { name: 'Frodo' }\n\nshape2.parse({ name: 8080 });\n// ❌ ValidationError: type.string at /name: Must be a string\n```\n\nDeep partial isn't applied to [converted shapes](#conversions):\n\n```ts\nconst shape2 = d\n  .object({\n    years: d.array(d.string())\n      .convert(years =\u003e years.map(parseFloat))\n  })\n  .deepPartial();\n// ⮕ Shape\u003c{ years?: string[] }, { years?: number[] }\u003e\n```\n\nIn the example above, array elements don't allow `undefined` even after `deepPartial` was applied, this happened because\narray is converted during parsing.\n\n\u003e [!NOTE]\\\n\u003e You can also implement [deep partial protocol](#implementing-deep-partial-support) in your custom shapes.\n\n# Fallback value\n\nIf issues were detected during parsing a shape can return a fallback value.\n\n```ts\nconst shape1 = d.string().catch('Mars');\n\nshape1.parse('Pluto');\n// ⮕ 'Pluto'\n\nshape1.parse(42);\n// ⮕ 'Mars'\n```\n\nPass a callback as a fallback value, it would be executed every time the catch clause is reached:\n\n```ts\nconst shape2 = d.number().catch(Date.now);\n\nshape2.parse(42);\n// ⮕ 42\n\nshape2.parse('Pluto');\n// ⮕ 1671565311528\n\nshape2.parse('Mars');\n// ⮕ 1671565326707\n```\n\nFallback functions receive an input value, an array of issues and\n[parsing options](https://smikhalevski.github.io/doubter/next/interfaces/core.ParseOptions.html) (so you can\naccess your [custom context](#parsing-context) if needed).\n\n```ts\nd.string().catch((input, issues, options) =\u003e {\n  // Return a fallback value\n});\n```\n\nA fallback function can throw a [`ValidationError`](#validation-errors) to indicate that a fallback value cannot be\nproduced. Issues from this error would be incorporated in the parsing result.\n\n```ts\nconst shape3 = d.object({\n  name: d.string().catch(() =\u003e {\n    throw new d.ValidationError([{ code: 'kaputs' }]);\n  })\n});\n\nshape3.parse({ name: 47 });\n// ❌ ValidationError: kaputs at /name\n```\n\n# Branded types\n\nIn TypeScript, values are considered to be of equivalent type if they are structurally the same. For example, plain\nstrings are assignable to one another:\n\n```ts\nfunction bookTicket(flightCode: string): void {\n  // Booking logic\n}\n\n// 🟡 No type errors, but \"Bill\" isn't a flight code\nbookTicket('Bill');\n```\n\nIn some cases, it can be desirable to simulate nominal typing inside TypeScript. For instance, you may wish to write a\nfunction that only accepts an input that has been validated by Doubter. This can be achieved with branded types:\n\n```ts\nconst flightCodeShape = d.string().refine(isFlightCode).brand\u003c'flightCode'\u003e();\n// ⮕ Shape\u003cstring, Branded\u003cstring, 'flightCode'\u003e\u003e\n\ntype FlightCode = d.Output\u003ctypeof flightCodeShape\u003e;\n\n// 🟡 Note that the argument type isn't a plain string\nfunction bookTicket(flightCode: FlightCode): void {\n  // Booking logic\n}\n\nbookTicket(flightCodeShape.parse('BA2490'));\n// Ok, valid flight code\n\nbookTicket('Bill');\n// ❌ Error: Expected BRAND to be flightCode\n```\n\n\u003e [!NOTE]\\\n\u003e Branded types don't affect the runtime result of `parse`. It is a static-type-only construct.\n\n# Type coercion\n\nType coercion is the process of converting value from one type to another (such as a string to a number, an array to\na `Set`, and so on).\n\nWhen coercion is enabled, input values are implicitly converted to the required input type whenever possible. For\nexample, you can coerce input values to a number type:\n\n```ts\nconst shape = d.number().coerce();\n// ⮕ NumberShape\n\nshape.isCoercing // ⮕ true\n\nshape.parse([new String('8080')]);\n// ⮕ 8080\n\nshape.parse(null);\n// ⮕ 0\n```\n\nCoercion rules differ from JavaScript so the behavior is more predictable and human-like. With Doubter, you can coerce\ninput to the following types:\n\n- [string](#coerce-to-a-string)\n- [number](#coerce-to-a-number)\n- [boolean](#coerce-to-a-boolean)\n- [bigint](#coerce-to-a-bigint)\n- [const](#coerce-to-a-const)\n- [enum](#coerce-to-an-enum)\n- [array](#coerce-to-an-array)\n- [`Date`](#coerce-to-a-date)\n- [`Promise`](#coerce-to-a-promise)\n- [`Map`](#coerce-to-a-map)\n- [`Set`](#coerce-to-a-set)\n\n## Custom type coercion\n\nIf you want to implement a custom coercion, you can use [`catch`](#fallback-value) to handle invalid input values:\n\n```ts\nconst yesNoShape = d.boolean().catch((value, issues) =\u003e {\n  if (value === 'yes') {\n    return true;\n  }\n  if (value === 'no') {\n    return false;\n  }\n  throw new ValidationError(issues);\n});\n\nyesNoShape.parse('yes');\n// ⮕ true\n\nd.array(yesNoShape).parse([true, 'no']);\n// ⮕ [true, false]\n\nyesNoShape.parse('true');\n// ❌ ValidationError: type.boolean at /: Must be a boolean\n```\n\nOr you can use [`d.convert`](#convert-convertasync) to preprocess all input values:\n\n```ts\nconst yesNoShape = d\n  .convert(value =\u003e {\n    if (value === 'yes') {\n      return true;\n    }\n    if (value === 'no') {\n      return false;\n    }\n    // Let the consequent shape handle this value\n    return value;\n  })\n  .to(d.boolean());\n\nyesNoShape.parse('yes');\n// ⮕ true\n\nyesNoShape.parse('true');\n// ❌ ValidationError: type.boolean at /: Must be a boolean\n```\n\n# Introspection\n\nDoubter provides various ways to introspect your shapes at runtime. Let's start by accessing shape input types using\nthe [`inputs`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#inputs) property:\n\n```ts\nconst shape1 = d.or([d.string(), d.boolean()]);\n// ⮕ Shape\u003cstring | boolean\u003e\n\nshape1.inputs;\n// ⮕ [Type.STRING, Type.BOOLEAN]\n```\n\n`inputs` array may contain literal values:\n\n```ts\nd.enum(['Mars', 42]).inputs;\n// ⮕ ['Mars', 42]\n```\n\nLiteral values are absorbed by matching type when combined in unions.\n\n```ts\nconst shape2 = d.or([\n  d.enum(['Uranus', 1984]),\n  d.number()\n]);\n// ⮕ Shape\u003c'Uranus' | number\u003e\n\nshape2.inputs;\n// ⮕ ['Uranus', Type.NUMBER]\n```\n\nIf `inputs` is an empty array, it means that the shape doesn't accept any input values, and would _always_ raise\nvalidation issues.\n\n```ts\nconst shape3 = d.and([d.number(), d.const('Mars')]);\n// ⮕ Shape\u003cnever\u003e\n        \nshape3.inputs;\n// ⮕ []\n```\n\nTo detect the type of the value use\n[`Type.of`](https://smikhalevski.github.io/doubter/next/classes/core.Type.html#of):\n\n```ts\nType.of('Mars');\n// ⮕ Type.STRING\n\nType.of(Type.NUMBER);\n// ⮕ Type.NUMBER\n```\n\nTypes returned from `Type.of` are a superset of types returned from the `typeof` operator.\n\n\u003ctable\u003e\n\u003ctr\u003e\u003cth\u003e\u003ccode\u003eType.of\u003c/code\u003e\u003c/th\u003e\u003cth\u003e\u003ccode\u003etypeof\u003c/code\u003e\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.OBJECT\u003c/code\u003e\u003c/td\u003e\u003ctd rowspan=\"7\"\u003e\u003ccode\u003e'object'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.ARRAY\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.DATE\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.PROMISE\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.SET\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.MAP\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.NULL\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.FUNCTION\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'function'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.STRING\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'string'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.SYMBOL\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'symbol'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.NUMBER\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'number'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.BIGINT\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'bigint'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.BOOLEAN\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'boolean'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.UNDEFINED\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e\u003ccode\u003e'undefined'\u003c/code\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ccode\u003eType.UNKNOWN\u003c/code\u003e\u003c/td\u003e\u003ctd\u003e—\u003c/tr\u003e\n\u003c/table\u003e\n\n## Unknown value type\n\n`Type.UNKNOWN` type emerges when accepted inputs cannot be statically inferred. For example, if [`d.any`](#any),\n[`d.unknown`](#unknown), or [`d.convert`](#convert-convertasync) are used:\n\n```ts\nconst shape1 = d.convert(parseFloat);\n// ⮕ Shape\u003cany, number\u003e\n\nshape1.inputs;\n// ⮕ [Type.UNKNOWN]\n```\n\n`Type.UNKNOWN` behaves like TypeScript's `unknown`.\n\nIt absorbs other types in unions:\n\n```ts\nconst shape2 = d.or([d.string(), d.unknown()]);\n// ⮕ Shape\u003cunknown\u003e\n\nshape2.inputs;\n// ⮕ [Type.UNKNOWN]\n```\n\nAnd it is erased in intersections:\n\n```ts\nconst shape3 = d.and([d.string(), d.unknown()]);\n// ⮕ Shape\u003cstring\u003e\n\nshape3.inputs;\n// ⮕ [Type.STRING]\n\nconst shape4 = d.and([d.never(), d.unknown()]);\n// ⮕ Shape\u003cnever\u003e\n\nshape4.inputs;\n// ⮕ []\n```\n\n## Check that an input is accepted\n\nTo check that the shape accepts a particular input type or value use the\n[`accepts`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#accepts) method:\n\n```ts\nconst shape1 = d.string();\n// ⮕ Shape\u003cstring\u003e\n\nshape1.accepts(Type.STRING);\n// ⮕ true\n\nshape1.accepts('Venus');\n// ⮕ true\n```\n\nCheck that a value is accepted:\n\n```ts\nconst shape2 = d.enum(['Mars', 'Venus']);\n// ⮕ Shape\u003c'Mars' | 'Venus'\u003e\n\nshape2.accepts('Mars');\n// ⮕ true\n\nshape2.accepts('Pluto');\n// ⮕ false\n\n// 🟡 Enum doesn't accept arbitrary strings\nshape2.accepts(Type.STRING);\n// ⮕ false\n```\n\nFor example, you can check that the shape is [optional](#optional-and-non-optional) by checking that it accepts\n`undefined` input value:\n\n```ts\nconst shape3 = d.number().optional();\n// ⮕ Shape\u003cnumber | undefined\u003e\n\nshape3.accepts(1984);\n// ⮕ true\n\nshape3.accepts(undefined);\n// ⮕ true\n\n// 🟡 Note that null isn't accepted\nshape3.accepts(null);\n// ⮕ false\n```\n\nThe fact that a shape accepts a particular input type or value, does not guarantee that it wouldn't raise a validation\nissue. For example, consider the [pipe](#shape-piping) from [`d.any`](#any) to [`d.string`](#string):\n\n```ts\nconst fuzzyShape = d.any().to(d.string());\n// ⮕ Shape\u003cany, string\u003e\n```\n\n`fuzzyShape` accepts [`Type.UNKNOWN`](#unknown-value-type) because it is based on `d.any`:\n\n```ts\nfuzzyShape.inputs;\n// ⮕ [Type.UNKNOWN]\n```\n\nSince `fuzzyShape` accepts any values, an `undefined` is also accepted:\n\n```ts\nfuzzyShape.accepts(undefined);\n// ⮕ true\n```\n\nBut parsing `undefined` with `fuzzyShape` would produce an error, since `undefined` doesn't satisfy `d.string` on the\nright-hand side of the pipe:\n\n```ts\nfuzzyShape.parse(undefined);\n// ❌ ValidationError: type.string at /: Must be a string\n```\n\n## Nested shapes\n\nObject, array, union ond other composite shapes provide access to their nested shapes:\n\n```ts\nconst userShape = d.object({\n  name: d.string(),\n  age: d.number()\n});\n// ⮕ Shape\u003c{ name: string, age: number }\u003e\n\nuserShape.propShapes.name;\n// ⮕ Shape\u003cstring\u003e\n\nconst userOrNameShape = d.or([userShape, d.string()]);\n// ⮕ Shape\u003c{ name: string, age: number } | string\u003e\n\nuserOrNameShape.shapes[0];\n// ⮕ userShape\n```\n\n[`Shape.at`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#at) method derives a sub-shape at the\ngiven key, and if there's no such key then `null` is returned:\n\n```ts\nuserShape.at('age');\n// ⮕ Shape\u003cnumber\u003e\n\nuserShape.at('emotionalDamage');\n// ⮕ null\n```\n\nThis is especially useful with unions and intersections:\n\n```ts\nconst shape = d.or([\n  d.object({\n    foo: d.string()\n  }),\n  d.object({\n    foo: d.number()\n  })\n]);\n\nshape.at('foo')\n// ⮕ Shape\u003cstring | number\u003e\n\nshape.at('bar')\n// ⮕ null\n```\n\n# Localization\n\nAll shape factories and built-in checks support a custom issue messages:\n\n```ts\nd.string('Hey, string here').min(3, 'Too short');\n```\n\n[Pass a function as a message](https://smikhalevski.github.io/doubter/next/types/core.MessageCallback.html), and\nit would receive an [issue](#validation-errors) that would be raised, and parsing options. You can assign\n`issue.message` or return a message. For example, when using with React you may return a JSX element:\n\n```tsx\nconst reactMessage: d.Message = (issue, options) =\u003e (\n  \u003cspan style={{ color: 'red' }}\u003e\n    The minimum length is {issue.param}\n  \u003c/span\u003e\n);\n\nd.number().min(5, reactMessage);\n```\n\nSemantics described above are applied to the\n[`message`](https://smikhalevski.github.io/doubter/next/interfaces/core.IssueOptions.html#message) option as well:\n\n```ts\nd.string().length(3, { message: 'Invalid length' })\n```\n\n## Override default messages\n\nDefault issue messages can be overridden by\n[`messages`](https://smikhalevski.github.io/doubter/next/interfaces/core.ParseOptions.html#messages) option:\n\n```ts\nimport * as d from 'doubter';\n\nd.string().parse(42, {\n  messages: {\n    'type.string': 'Yo, not a string!'\n  }\n});\n// ❌ ValidationError: type.string at /: Yo, not a string!\n```\n\nThe full list of issue codes can be found in [Validation errors](#validation-errors) section.\n\n# Plugins\n\nBy default, when you import Doubter, you also get all [built-in plugins](#built-in-plugins) as well:\n\n```ts\nimport * as d from 'doubter';\n\nd.string().min(2); // ✅ min is defined\n\nd.number().gte(3); // ✅ gte is defined\n```\n\nIf you import `doubter/core`, you would get only core set of shapes without any plugins:\n\n```ts\nimport * as d from 'doubter/core';\n\nd.string().min(2); // ❌ min is undefined\n\nd.number().gte(3); // ❌ gte is undefined\n```\n\nYou can cherry-pick plugins that you need:\n\n```ts\nimport * as d from 'doubter/core';\nimport 'doubter/plugin/string-essentials';\n\nd.string().min(2); // ✅ min is defined\n\nd.number().gte(3); // ❌ gte is undefined\n```\n\n## Built-in plugins\n\n- [**Array essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_array_essentials.html)\u003cbr/\u003e\n  [`length`](https://smikhalevski.github.io/doubter/next/classes/core.ArrayShape.html#length)\n  [`min`](https://smikhalevski.github.io/doubter/next/classes/core.ArrayShape.html#min)\n  [`max`](https://smikhalevski.github.io/doubter/next/classes/core.ArrayShape.html#max)\n  [`nonEmpty`](https://smikhalevski.github.io/doubter/next/classes/core.ArrayShape.html#nonEmpty)\n  [`includes`](https://smikhalevski.github.io/doubter/next/classes/core.ArrayShape.html#includes)\n\n- [**Bigint essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_bigint_essentials.html)\u003cbr/\u003e\n  [`positive`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html#positive)\n  [`negative`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html#negative)\n  [`nonPositive`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html#nonPositive)\n  [`nonNegative`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html#nonNegative)\n  [`min`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html#min)\n  [`max`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html#max)\n\n- [**Date essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_date_essentials.html)\u003cbr/\u003e\n  [`min`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html#min)\n  [`max`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html#max)\n  [`after`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html#after)\n  [`before`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html#before)\n  [`toISOString`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html#toISOString)\n  [`toTimestamp`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html#toTimestamp)\n\n- [**Number essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_number_essentials.html)\u003cbr/\u003e\n  [`finite`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#finite)\n  [`int`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#int)\n  [`positive`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#positive)\n  [`negative`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#negative)\n  [`nonPositive`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#nonPositive)\n  [`nonNegative`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#nonNegative)\n  [`between`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#between)\n  [`gt`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#gt)\n  [`lt`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#lt)\n  [`gte`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#gte)\n  [`lte`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#lte)\n  [`min`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#min)\n  [`max`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#max)\n  [`multipleOf`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#multipleOf)\n  [`safe`](https://smikhalevski.github.io/doubter/next/classes/core.NumberShape.html#safe)\n\n- [**Object essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_object_essentials.html)\u003cbr/\u003e\n  [`plain`](https://smikhalevski.github.io/doubter/next/classes/core.ObjectShape.html#plain)\n  [`allKeys`](https://smikhalevski.github.io/doubter/next/classes/core.ObjectShape.html#allKeys)\n  [`notAllKeys`](https://smikhalevski.github.io/doubter/next/classes/core.ObjectShape.html#notAllKeys)\n  [`orKeys`](https://smikhalevski.github.io/doubter/next/classes/core.ObjectShape.html#orKeys)\n  [`xorKeys`](https://smikhalevski.github.io/doubter/next/classes/core.ObjectShape.html#xorKeys)\n  [`oxorKeys`](https://smikhalevski.github.io/doubter/next/classes/core.ObjectShape.html#oxorKeys)\n\n- [**Set essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_set_essentials.html)\u003cbr/\u003e\n  [`size`](https://smikhalevski.github.io/doubter/next/classes/core.SetShape.html#size)\n  [`min`](https://smikhalevski.github.io/doubter/next/classes/core.SetShape.html#min)\n  [`max`](https://smikhalevski.github.io/doubter/next/classes/core.SetShape.html#max)\n  [`nonEmpty`](https://smikhalevski.github.io/doubter/next/classes/core.SetShape.html#nonEmpty)\n\n- [**String essentials**](https://smikhalevski.github.io/doubter/next/modules/plugin_string_essentials.html)\u003cbr/\u003e\n  [`length`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#length)\n  [`min`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#min)\n  [`max`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#max)\n  [`regex`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#regex)\n  [`includes`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#includes)\n  [`startsWith`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#startsWith)\n  [`endsWith`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#endsWith)\n  [`nonBlank`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#nonBlank)\n  [`nonEmpty`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#nonEmpty)\n  [`trim`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#trim)\n  [`toLowerCase`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#toLowerCase)\n  [`toUpperCase`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html#toUpperCase)\n\n- [**Object eval**](https://smikhalevski.github.io/doubter/next/modules/plugin_object_eval.html)\u003cbr/\u003e\n  If `new Function` calls are allowed by the environment, this plugin compiles internal methods of\n  the `ObjectShape` to boost performance.\n\n## Recommended plugins\n\n- [@doubter/plugin-string-format](https://github.com/smikhalevski/doubter-plugin-string-format#readme)\u003cbr\u003e\n  Extends `StringShape` with email, FQDN, MIME, BIC, ISIN, Luhn, and many other format checks.\n\n## Integrations\n\nYou can combine Doubter with your favourite predicate library using [refinements](#refinements).\n\nFor example, create a shape that validates that input is an email using\n[Validator.js](https://github.com/validatorjs/validator.js):\n\n```ts\nimport * as d from 'doubter';\nimport isEmail from 'validator/lib/isEmail';\n\nconst emailShape = d.string().refine(isEmail, 'Must be an email');\n// ⮕ Shape\u003cstring\u003e\n\nemailShape.parse('Not an email');\n// ❌ ValidationError: any.refine at /: Must be an email\n\nemailShape.parse('foo@bar.com');\n// ⮕ 'foo@bar.com'\n```\n\nYou can use Doubter [alterations](#alterations) with various utility libraries, such as [Lodash](https://lodash.com/):\n\n```ts\nimport * as d from 'doubter';\nimport * as _ from 'lodash';\n\nconst shape = d.array(d.number()).alter(_.uniq);\n\nshape.parse([1, 2, 3, 3, 2]);\n// ⮕ [1, 2, 3])\n```\n\nOr use native JavaScript methods as alteration callbacks:\n\n```ts\nconst shape = d.number().alter(Math.abs).alter(Math.round).min(3);\n\nshape.parse(-3.1415);\n// ⮕ 3\n\nshape.parse(2);\n// ❌ ValidationError: number.gte at /: Must be greater than or equal to 3\n```\n\n## Authoring a plugin\n\nPlugins use\n[TypeScript's module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)\nto extend functionality of shapes exported from the\n[doubter/core](https://smikhalevski.github.io/doubter/next/classes/core.html) module.\n\nBelow is an example, how you can implement a naive email check and extend the\n[`StringShape`](https://smikhalevski.github.io/doubter/next/classes/core.StringShape.html).\n\n```ts\nimport { StringShape } from 'doubter/core';\n\ndeclare module 'doubter/core' {\n  interface StringShape {\n    email(): this;\n  }\n}\n\nStringShape.prototype.email = function () {\n  return this.addOperation(value =\u003e {\n    if (value.includes('@')) {\n      return null;\n    }\n    return [{ code: 'email', message: 'Must be an email' }]\n  });\n};\n```\n\nNow you can use this check when building a string shape:\n\n```ts\nconst shape = d.string().email();\n\nshape.parse('foo@bar.com');\n// ⮕ 'foo@bar.com'\n\nshape.parse('foo');\n// ❌ ValidationError: email at /: Must be an email\n```\n\nYou can use generic [operations](#operations), [checks](#checks), [refinements](#refinements),\n[alterations](#alterations), [conversions](#conversions), and any other functionality of the shape that is being\nextended.\n\n# Advanced shapes\n\nYou can create custom shapes by extending the\n[`Shape`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html) class.\n\n`Shape` has several protected methods that you can override to change different aspects of the shape logic.\n\n\u003cdl\u003e\n\u003cdt\u003e\n  \u003ca href=\"https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#_apply\"\u003e\n    \u003ccode\u003e_apply(input, options, nonce)\u003c/code\u003e\n  \u003c/a\u003e\n\u003c/dt\u003e\n\u003cdd\u003e\n\nSynchronous input parsing is delegated to this method. It receives an `input` that must be parsed and should return\nthe [`Result`](https://smikhalevski.github.io/doubter/next/types/core.Result.html):\n\n- `null` if the output value is the same as the input value;\n- an [`Ok`](https://smikhalevski.github.io/doubter/next/interfaces/core.Ok.html) object (as in example above) if the\n  output contains a new value;\n- an array of [`Issue`](https://smikhalevski.github.io/doubter/next/interfaces/core.Issue.html) objects if parsing has\n  failed.\n\n\u003c/dd\u003e\n\u003cdt\u003e\n  \u003ca href=\"https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#_applyAsync\"\u003e\n    \u003ccode\u003e_applyAsync(input, options, nonce)\u003c/code\u003e\n  \u003c/a\u003e\n\u003c/dt\u003e\n\u003cdd\u003e\n\nAsynchronous input parsing is delegated to this method. It has the same semantics as `_apply` but returns a `Promise`.\nYou need to override this method only if you have a separate logic for async parsing.\n\n\u003c/dd\u003e\n\u003cdt\u003e\n  \u003ca href=\"https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#_isAsync\"\u003e\n    \u003ccode\u003e_isAsync()\u003c/code\u003e\n  \u003c/a\u003e\n\u003c/dt\u003e\n\u003cdd\u003e\n\nThe value returned from this method is toggles which method is used for parsing:\n\n- if `true` then `_applyAsync` would be used for parsing, and `_apply` would always throw an error;\n- if `false` then `_apply` can be used for parsing along with `_applyAsync`.\n\n\u003c/dd\u003e\n\u003cdt\u003e\n  \u003ca href=\"https://smikhalevski.github.io/doubter/next/classes/core.Shape.html#_getInputs\"\u003e\n    \u003ccode\u003e_getInputs()\u003c/code\u003e\n  \u003c/a\u003e\n\u003c/dt\u003e\n\u003cdd\u003e\n\nMust return an array of types and values that can be processed by the shape. Elements of the returned array don't have\nto be unique. Refer to [Introspection](#introspection) section for more details about types.\n\n\u003c/dd\u003e\n\u003c/dl\u003e\n\nLet's create a custom shape that parses an input string as a number:\n\n```ts\nclass NumberLikeShape extends d.Shape\u003cstring, number\u003e {\n\n  protected _apply(input: unknown, options: d.ParseOptions, nonce: number): d.Result\u003cnumber\u003e {\n\n    // 1️⃣ Validate the input and return issues if it is invalid\n    if (typeof input !== 'string' || isNaN(parseFloat(input))) {\n      return [{\n        code: 'kaputs',\n        message: 'Must be a number-like',\n        input,\n      }];\n    }\n\n    // 2️⃣ Apply operations to the output value\n    return this._applyOperations(input, parseFloat(input), options, null) as d.Result;\n  }\n}\n```\n\nNow let's use this shape alongside with other built-in shapes:\n\n```ts\nconst shape = d.array(new NumberLikeShape());\n// ⮕ Shape\u003cstring[], number[]\u003e\n\nshape.parse(['42', '33']);\n// ⮕ [42, 33]\n\nshape.parse(['seventeen']);\n// ❌ ValidationError: kaputs at /0: Must be a number-like\n```\n\n## Implementing deep partial support\n\nTo enable `deepPartial` support, your shape must implement\n[`DeepPartialProtocol`](https://smikhalevski.github.io/doubter/next/interfaces/core.DeepPartialProtocol.html).\n\n```ts\nclass MyShape\n  extends Shape\n  implements DeepPartialProtocol\u003cMyDeepPartialShape\u003e {\n\n  deepPartial(): MyDeepPartialShape {\n    // Create and return a deep partial version of MyShape\n  }\n}\n```\n\nThis is sufficient to enable type inference and runtime support for `deepPartial` method.\n\n# Performance\n\nThe chart below showcases the performance comparison of Doubter and its peers, in terms of millions of operations per\nsecond (greater is better).\n\n\u003cp align=\"center\"\u003e\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/perf-dark.svg\" /\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"./assets/perf-light.svg\" /\u003e\n  \u003cimg alt=\"Performance comparison chart\" src=\"./assets/perf-light.svg\" /\u003e\n\u003c/picture\u003e\u003c/p\u003e\n\nTests were conducted using [TooFast](https://github.com/smikhalevski/toofast#readme) on Apple M1 with Node.js v20.4.0.\n\nTo reproduce [the performance test suite](./src/test/perf/overall.perf.js) results, clone this repo and run:\n\n```shell\nnpm ci\nnpm run build\nnpm run perf -- -t overall\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eDetailed results\u003c/summary\u003e\n\n```\nSuccess path\n\n  Loose validation\n    ● doubter    7.9 MHz ± 0.5%    128.4 B  ± 0.11%\n    ● Ajv       15.8 MHz ± 1.33%   156.2 B  ± 0.01%\n    ● zod        1.1 MHz ± 0.5%      4.2 kB ± 0.01%\n    ● myzod      2.4 MHz ± 0.5%    506.4 B  ± 0.04%\n    ● valita     4.4 MHz ± 0.5%    117.9 B  ± 0.07%\n    ● valibot    3.0 MHz ± 0.5%      1.3 kB ± 0.01%\n\n  Strict validation\n    ● doubter    4.3 MHz ± 0.5%    149.9 B  ± 0.06%\n    ● Ajv       13.1 MHz ± 1.15%   152.3 B  ± 0.01%\n    ● zod        1.2 MHz ± 0.5%      4.2 kB ± 0.01%\n    ● myzod      2.5 MHz ± 0.5%    316.7 B  ± 0.13%\n    ● valita     4.3 MHz ± 0.5%    120.7 B  ± 0.46%\n    ● valibot    3.0 MHz ± 0.5%      1.3 kB ± 0%\n\nFailure path\n\n  Loose validation\n    ● doubter    4.2 MHz ± 0.6%      1.2 kB ± 0.01%\n    ● Ajv       13.3 MHz ± 1.11%   356.4 B  ± 0.01%\n    ● zod      175.0 kHz ± 1.04%    11.0 kB ± 0.22%\n    ● myzod     76.9 kHz ± 0.5%      2.8 kB ± 0.09%\n    ● valita     3.1 MHz ± 0.5%      1.5 kB ± 0%\n    ● valibot    3.0 MHz ± 0.53%     1.3 kB ± 0.02%\n\n  Strict validation\n    ● doubter    2.9 MHz ± 0.5%      1.2 kB ± 0.01%\n    ● Ajv       12.6 MHz ± 1.25%   331.6 B  ± 0%\n    ● zod      178.0 kHz ± 1.08%    10.8 kB ± 0.22%\n    ● myzod     64.5 kHz ± 0.5%      2.8 kB ± 0.17%\n    ● valita     3.0 MHz ± 0.5%      1.4 kB ± 0%\n    ● valibot    3.0 MHz ± 0.5%      1.3 kB ± 0%\n```\n\n\u003c/details\u003e\n\n# Comparison with peers\n\nThe table below highlights features that are unique to Doubter and its peers.\n\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\u003cth\u003e\u003c/th\u003e\u003cth width=\"100\"\u003eDoubter\u003c/th\u003e\u003cth width=\"100\"\u003eZod\u003c/th\u003e\u003cth width=\"100\"\u003eValita\u003c/th\u003e\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003c!--                                                                                 Doubter    Zod        Valita    --\u003e\n\n\u003ctr\u003e\u003ctd colspan=\"4\"\u003e\u003cbr\u003e\u003cb\u003eShapes and parsing\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#static-type-inference\"\u003eStatic type inference\u003c/a\u003e          \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#early-return\"\u003eEarly return\u003c/a\u003e                            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#validation-errors\"\u003eCustom issue codes\u003c/a\u003e                 \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#replace-allow-and-deny-a-value\"\u003eReplace/allow/deny\u003c/a\u003e    \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#exclude-a-shape\"\u003eExclude/not\u003c/a\u003e                          \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#discriminated-unions\"\u003eDiscriminated unions\u003c/a\u003e            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e1\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#introspection\"\u003eIntrospection at runtime\u003c/a\u003e               \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e2\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#annotations-and-metadata\"\u003eAnnotations/metadata\u003c/a\u003e        \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#making-objects-partial-and-required\"\u003ePartial objects\u003c/a\u003e  \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#deep-partial\"\u003eDeep partial\u003c/a\u003e                            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e3\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#circular-object-references\"\u003eCircular objects\u003c/a\u003e          \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#nested-shapes\"\u003eDerive sub-shapes\u003c/a\u003e                      \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#key-relationships\"\u003eObject key relationships\u003c/a\u003e           \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#parsing-context\"\u003eParsing context\u003c/a\u003e                      \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\u003ctd colspan=\"4\"\u003e\u003cbr\u003e\u003cb\u003eAsync flow\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#async-shapes\"\u003eAsync shapes\u003c/a\u003e                            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#refinements\"\u003eAsync refinements\u003c/a\u003e                        \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#async-conversions\"\u003eAsync conversions\u003c/a\u003e                  \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#checks\"\u003eAsync checks\u003c/a\u003e                                  \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#alterations\"\u003eAsync alterations\u003c/a\u003e                        \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#async-shapes\"\u003eCheck that shape is async\u003c/a\u003e               \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\u003ctd colspan=\"4\"\u003e\u003cbr\u003e\u003cb\u003eType coercion\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-string\"\u003eString\u003c/a\u003e                            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e4\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-number\"\u003eNumber\u003c/a\u003e                            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e4\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-boolean\"\u003eBoolean\u003c/a\u003e                          \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e4\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-bigint\"\u003eBigInt\u003c/a\u003e                            \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e4\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-date\"\u003eDate\u003c/a\u003e                                \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e\u0026ensp;🌕 \u003csup\u003e4\u003c/sup\u003e\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-set\"\u003eSet\u003c/a\u003e                                  \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-map\"\u003eMap\u003c/a\u003e                                  \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-an-array\"\u003eArray\u003c/a\u003e                             \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-an-enum\"\u003eEnum\u003c/a\u003e                               \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#coerce-to-a-const\"\u003eConst\u003c/a\u003e                              \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\u003ctd colspan=\"4\"\u003e\u003cbr\u003e\u003cb\u003eOther\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\u003ca href=\"#plugins\"\u003ePlugin-centric\u003c/a\u003e                               \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eTree-shakeable                                                      \u003c/td\u003e\u003cth\u003e🟢\u003c/th\u003e\u003cth\u003e🔴\u003c/th\u003e\u003cth\u003e🟢\u003c/th\u003e\u003c/tr\u003e\n\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n1. Zod uses [`z.union`](https://zod.dev/?id=unions) for regular unions and\n   [`z.discriminatedUnion`](https://zod.dev/?id=discriminated-unions) for discriminated unions, and discriminator key\n   must be supplied manually as an argument. Doubter uses `d.union` to describe both regular unions and discriminated\n   unions, and discriminator key is\n   [detected automatically](#discriminated-unions).\n\n2. Zod schemas are class instances so introspection is possible, but there's no way to get\n   [a list of types accepted by a schema](#introspection).\n\n3. Zod supports [`deepPartial`](https://zod.dev/?id=deeppartial) for objects only. Doubter allows any shape to implement\n   [`DeepPartialProtocol`](#implementing-deep-partial-support) and all shapes (except for primitives) support it\n   out-of-the-box.\n\n4. Zod coerces input values using wrapper constructors. Doubter uses custom converters for type coercion. For example,\n   with Zod `null` is coerced to `\"null\"`, while with Doubter `null` is coerced to an empty string.\n\n# `any`\n\n[`d.any`](https://smikhalevski.github.io/doubter/next/functions/core.any.html) returns a\n[`Shape`](https://smikhalevski.github.io/doubter/next/classes/core.Shape.html) instance.\n\nAn unconstrained value that is inferred as `any`:\n\n```ts\nd.any();\n// ⮕ Shape\u003cany\u003e\n```\n\nUse `any` to create shapes that are unconstrained at runtime but constrained at compile time:\n\n```ts\nd.any\u003c{ foo: string }\u003e();\n// ⮕ Shape\u003c{ foo: string }\u003e\n```\n\nCreate a shape that is constrained by a\n[narrowing predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html):\n\n```ts\nd.any((value): value is string =\u003e typeof value === 'string');\n// ⮕ Shape\u003cany, string\u003e\n```\n\n# `array`\n\n[`d.array`](https://smikhalevski.github.io/doubter/next/functions/core.array.html) returns an\n[`ArrayShape`](https://smikhalevski.github.io/doubter/next/classes/core.ArrayShape.html) instance.\n\nConstrains a value to be an array:\n\n```ts\nd.array();\n// ⮕ Shape\u003cany[]\u003e\n```\n\nRestrict array element types:\n\n```ts\nd.array(d.number());\n// ⮕ Shape\u003cnumber[]\u003e\n```\n\nConstrain the length of an array:\n\n```ts\nd.array(d.string()).min(1).max(10);\n```\n\nLimit both minimum and maximum array length at the same time:\n\n```ts\nd.array(d.string()).length(5);\n```\n\nConvert array values during parsing:\n\n```ts\nd.array(d.string().convert(parseFloat));\n// ⮕ Shape\u003cstring[], number[]\u003e\n```\n\nMake an array readonly:\n\n```ts\nd.array(d.string()).readonly();\n// ⮕ Shape\u003cstring[], readonly string[]\u003e\n```\n\n## Coerce to an array\n\nIterables and array-like objects are converted to array via `Array.from(value)`:\n\n```ts\nconst shape = d.array(d.string()).coerce();\n\nshape.parse(new Set(['John', 'Jack']));\n// ⮕ ['John', 'Jack']\n\nshape.parse({ 0: 'Bill', 1: 'Jill', length: 2 });\n// ⮕ ['Bill', 'Jill']\n```\n\nScalars, non-iterable and non-array-like objects are wrapped into an array:\n\n```ts\nshape.parse('Rose');\n// ⮕ ['Rose']\n```\n\n# `bigint`\n\n[`d.bigint`](https://smikhalevski.github.io/doubter/next/functions/core.bigint.html) returns a\n[`BigIntShape`](https://smikhalevski.github.io/doubter/next/classes/core.BigIntShape.html) instance.\n\nConstrains a value to be a bigint.\n\n```ts\nd.bigint();\n// ⮕ Shape\u003cbigint\u003e\n```\n\n## Coerce to a bigint\n\n`null` and `undefined` are converted to 0:\n\n```ts\nconst shape = d.bigint().coerce();\n\nshape.parse(null);\n// ⮕ BigInt(0)\n```\n\nNumber, string and boolean values are converted via `BigInt(value)`:\n\n```ts\nshape.parse('18588');\n// ⮕ BigInt(18588)\n\nshape.parse('Unexpected')\n// ❌ ValidationError: type.bigint at /: Must be a bigint\n```\n\nArrays with a single element are unwrapped and the value is coerced:\n\n```ts\nshape.parse([0xdea]);\n// ⮕ BigInt(3562)\n\nshape.parse([BigInt(1), BigInt(2)]);\n// ❌ ValidationError: type.bigint at /: Must be a bigint\n```\n\n# `boolean`, `bool`\n\n[`d.boolean`](https://smikhalevski.github.io/doubter/next/functions/core.boolean.html) returns a\n[`BooleanShape`](https://smikhalevski.github.io/doubter/next/classes/core.BooleanShape.html) instance.\n\nConstrains a value to be boolean.\n\n```ts\nd.boolean();\n// or\nd.bool();\n// ⮕ Shape\u003cboolean\u003e\n```\n\n## Coerce to a boolean\n\n`null`, `undefined`, `'false'` and 0 are converted to `false`:\n\n```ts\nconst shape = d.boolean().coerce();\n\nshape.parse(null);\n// ⮕ false\n```\n\n`'true'` and 1 are converted to `true`:\n\n```ts\nshape.parse('true');\n// ⮕ true\n\nshape.parse('yes');\n// ❌ ValidationError: type.boolean at /: Must be a boolean\n```\n\nArrays with a single element are unwrapped and the value is coerced:\n\n```ts\nshape.parse([undefined]);\n// ⮕ false\n\nshape.parse([0, 1]);\n// ❌ ValidationError: type.boolean at /: Must be a boolean\n```\n\n# `const`\n\n[`d.const`](https://smikhalevski.github.io/doubter/next/functions/core.const.html) returns a\n[`ConstShape`](https://smikhalevski.github.io/doubter/next/classes/core.ConstShape.html) instance.\n\nConstrains a value to be an exact value:\n\n```ts\nd.const('Mars');\n// ⮕ Shape\u003c'Mars'\u003e\n```\n\nThere are shortcuts for [`null`](#null), [`undefined`](#undefined) and [`nan`](#nan) constants.\n\nConsider using [`enum`](#enum) if you want to check that an input is one of multiple values.\n\n## Coerce to a const\n\n`d.const` coerces an input depending on the type of the given constant value. `const` uses\n[bigint](#coerce-to-a-bigint), [number](#coerce-to-a-number), [string](#coerce-to-a-string),\n[boolean](#coerce-to-a-boolean), or [`Date`](#coerce-to-a-date) coercion rules if given constant matches one of these\ntypes. For example, if a given constant value is a string then [the string coercion rules](#coerce-to-a-string) are\napplied:\n\n```ts\nconst shape1 = d.const(BigInt(42)).coerce();\n\nshape1.parse([new String('42')]);\n// ⮕ BigInt(42)\n```\n\nConstant values of other types aren't coerced, but `d.const` would try to unwrap arrays with a single element to check\nthe element equals to the given constant:\n\n```ts\nconst users = new Set(['Bill']);\n\nconst shape2 = d.const(users).coerce();\n\nshape1.parse([users]);\n// ⮕ users\n\nshape1.parse(new Set(['Bill']));\n// ❌ ValidationError: type.set at /: Must be equal to [object Set]\n```\n\n# `convert`, `convertAsync`\n\nBoth [`d.convert`](https://smikhalevski.github.io/doubter/next/functions/core.convert.html) and\n[`d.convertAsync`](https://smikhalevski.github.io/doubter/next/functions/core.convertAsync.html) return a\n[`ConvertShape`](https://smikhalevski.github.io/doubter/next/classes/core.ConvertShape.html) instance.\n\nConverts the input value:\n\n```ts\nconst shape = d.convert(parseFloat);\n// ⮕ Shape\u003cany, number\u003e\n```\n\nUse `convert` in conjunction with [shape piping](#shape-piping):\n\n```ts\nshape.to(d.number().min(3).max(5));\n```\n\nApply async conversions with `convertAsync`:\n\n```ts\nd.convertAsync(value =\u003e Promise.resolve('Hello, ' + value));\n// ⮕ Shape\u003cany, string\u003e\n```\n\nFor more information, see [Conversions](#conversions) section.\n\n# `date`\n\n[`d.date`](https://smikhalevski.github.io/doubter/next/functions/core.date.html) returns a\n[`DateShape`](https://smikhalevski.github.io/doubter/next/classes/core.DateShape.html) instance.\n\nConstrains a value to be a valid date.\n\n```ts\nd.date();\n// ⮕ Shape\u003cDate\u003e\n```\n\nConstrain the minimum and maximum dates:\n\n```ts\nd.date().after('2003-03-12').before('2030-01-01');\n```\n\nConvert date to ISO string or timestamp:\n\n```ts\nd.date().toISOString().parse(new Date());\n// ⮕ '2023-07-10T19:31:52.395Z'\n\nd.date().toTimestamp().parse(new Date());\n// ⮕ 1689017512395\n```\n\n## Coerce to a `Date`\n\nStrings and numbers are converted via `new Date(value)` and if an invalid date is produced then an issue is raised:\n\n```ts\nconst shape = d.date().coerce();\n\nshape.parse('2023-01-22');\n// ⮕ Date\n\nshape.parse('Yesterday');\n// ❌ ValidationError: type.date at /: Must be a Date\n```\n\nArrays with a single element are unwrapped and the value is coerced:\n\n```ts\nshape.parse([1674352106419]);\n// ⮕ Date\n\nshape.parse(['2021-12-03', '2023-01-22']);\n// ❌ ValidationError: type.date at /: Must be a Date\n```\n\n# `enum`\n\n[`d.enum`](https://smikhalevski.github.io/doubter/next/functions/core.enum.html) returns an\n[`EnumShape`](https://smikhalevski.github.io/doubter/next/classes/core.EnumShape.html) instance.\n\nConstrains a value to be equal to one of predefined values:\n\n```ts\nd.enum(['Mars', 'Pluto', 'Jupiter']);\n// ⮕ Shape\u003c'Mars', 'Pluto', 'Jupiter'\u003e\n```\n\nOr use a native TypeScript enum to limit possible values:\n\n```ts\nenum Planet {\n  MARS,\n  PLUTO,\n  JUPITER\n}\n\nd.enum(Planet);\n// ⮕ Shape\u003cPlanet\u003e\n```\n\nOr use\n[an object with a `const` assertion](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions):\n\n```ts\nconst planets = {\n  MARS: 'Mars',\n  PLUTO: 'Pluto',\n  JUPITER: 'Jupiter'\n} as const;\n\nd.enum(plants);\n// ⮕ Shape\u003c'Mars', 'Pluto', 'Jupiter'\u003e\n```\n\n## Coerce to an enum\n\nIf an enum is defined via a native TypeScript enum or via a const object, then enum element names are coerced to\ncorresponding values:\n\n```ts\nenum Users {\n  JILL,\n  SARAH,\n  JAMES\n}\n\nconst shape1 = d.enum(Users).coerce();\n\nshape1.parse('SARAH');\n// ⮕ 1\n```\n\nArrays with a single element are unwrapped and the value is coerced:\n\n```ts\nshape1.parse(['JAMES']);\n// ⮕ 2\n\nshape1.parse([1]);\n// ⮕ 1\n\nshape1.parse([1, 2]);\n// ❌ ValidationError: type.enum at /: Must be equal to one of 0,1,2\n```\n\nOther values follow [`const` coercion rules](#coerce-to-a-const):\n\n```ts\nconst shape2 = d.enum([1970, new Date(0)]).coerce();\n\nshape2.parse(new String('1970'));\n// ⮕ 1970\n\nshape2.parse(0);\n// ⮕ Date { Jan 1, 1970 }\n```\n\n# `function`, `fn`\n\n[`d.function`](https://smikhalevski.github.io/doubter/next/functions/core.function.html) returns a\n[`FunctionShape`](https://smikhalevski.github.io/doubter/next/classes/core.FunctionShape.html) instance.\n\nConstrain a value to be a function with the given signature.\n\nA function that has no arguments and returns `any`:\n\n```ts\nd.function()\n// ⮕ Shape\u003c() =\u003e any\u003e\n\n// or use a shorter alias\nd.fn();\n```\n\nProvide an array of argument shapes:\n\n```ts\nd.fn([d.string(), d.number()]);\n// ⮕ Shape\u003c(arg1: string, arg2: number) =\u003e any\u003e\n```\n\nOr provide a shape that constrains an array of arguments:\n\n```ts\nd.fn(d.array(d.string()));\n// ⮕ Shape\u003c(...args: string[]) =\u003e any\u003e\n```\n\nAny shape that constrains an array type would do, you can even use a union:\n\n```ts\nd.fn(\n  d.or([\n    d.array(d.string()),\n    d.tuple([d.string(), d.number()])\n  ])\n);\n// ⮕ Shape\u003c(...args: string[] | [string, number]) =\u003e any\u003e\n```\n\nTo constrain the return value of a function shape, use the `return` method.\n\n```ts\nd.fn().return(d.string());\n// ⮕ Shape\u003c() =\u003e string\u003e\n```\n\nTo constrain a value of `this`:\n\n```ts\nd.fn().this(\n  d.object({ userId: d.string })\n);\n// ⮕ Shape\u003c(this: { userId: string }) =\u003e any\u003e\n```\n\n## Parsing a function\n\nFunction shapes check that an input value is a function:\n\n```ts\nconst shape1 = d.fn();\n\nshape1.parse(() =\u003e 42);\n// ⮕ () =\u003e any\n\nshape1.parse('Mars');\n// ❌ ValidationError: type.function at /: Must be a function\n```\n\nBy default, the input function is returned as-is during parsing. If you want a parsed function to be type-safe at\nruntime use `strict` method to [ensure the parsed function signature](#ensuring-function-signature).\n\n```ts\nconst callbackShape = d.fn([d.string()])\n  .return(d.number().int())\n  .strict();\n\nconst callback = callbackShape.parse(value =\u003e parseInt(value));\n// ⮕ (arg: string) =\u003e number\n```\n\n`callback` ensures that the argument is string and the returned value is a number, or throws a `ValidationError` if\ntypes are invalid at runtime.\n\n## Ensuring function signature\n\nYou can ensure a function signature type-safety at runtime.\n\nLet's declare a function shape that takes two number arguments and returns a number as well:\n\n```ts\nconst sumShape = d.fn([d.number(), d.number()]).return(d.number());\n// ⮕ Shape\u003c(arg1: number, arg2: number) =\u003e number\u003e\n```\n\nNow let's ensure a signature of a particular function:\n\n```ts\nconst sum = sumShape.ensure(\n  (arg1, arg2) =\u003e arg1 + arg2\n);\n// ⮕ (arg1: number, arg2: number) =\u003e number\n\nsum(2, 3);\n// ⮕ 5\n```\n\n`sum` would throw a [`ValidationError`](#validation-errors) if the required signature is violated at runtime:\n\n```ts\nsum(2, '3');\n// ❌ ValidationError: type.number at /arguments/1: Must be a number\n\nsum(NaN, 2);\n// ❌ ValidationError: type.number at /arguments/0: Must be an number\n\nsum(1, 2, 3);\n// ❌ ValidationError: array.max at /arguments: Must have the maximum length of 2\n```\n\nUsing function shape you can parse `this` and return values as well.\n\n```ts\nconst callbackShape = d.fn([d.number().int()])\n  .this(d.array(d.string()))\n  .return(d.string());\n// ⮕ Shape\u003c(this: string[], arg: number) =\u003e string\u003e\n\nconst callback = callbackShape.ensure(function (index) {\n  // 🟡 May be undefined if index is out of bounds\n  return this[index];\n});\n```\n\nWhen called with a valid index, a string is returned:\n\n```ts\ncallback.call(['Jill', 'Sarah'], 1);\n// ⮕ 'Sarah'\n```\n\nBut if an index is out of bounds, an error is thrown:\n\n```ts\ncallback.call(['James', 'Bob'], 33);\n// ❌ ValidationError: type.string at /return: Must be a string\n```\n\nAn error is thrown if an argument isn't an integer:\n\n```ts\ncallback.call(['Bill', 'Tess'], 3.14);\n// ❌ ValidationError: number.int at /arguments/0: Must be an integer\n```\n\n## Coercing arguments\n\nFunction shapes go well with [type coercion](#type-coercion):\n\n```ts\nconst plus2Shape = d.fn([d.number().coerce()]).return(d.number());\n// ⮕ Shape\u003c(arg: number) =\u003e number\u003e\n\nconst plus2 = plus2Shape.ensure(arg =\u003e arg + 2);\n// ⮕ (arg: number) =\u003e number\n```\n\nWhile `plus2` requires a single integer parameter, we can call it at runtime with a number-like string and get an\nexpected numeric result because an argument is coerced:\n\n```ts\nplus2('40');\n// ⮕ 42\n```\n\n## Transforming arguments and return values\n\nHere's a function shape that converts a string argument to a number:\n\n```ts\nconst shape = d.fn([d.string().convert(parseFloat)]);\n// ⮕ Shape\u003c(arg: number) =\u003e any, (arg: string) =\u003e any\u003e\n```\n\nNote that the input and output functions described by this shape have different signatures. Let's implement of this\nfunction:\n\n```ts\nfunction inputFunction(arg: number): any {\n  return arg + 2;\n}\n\nconst outputFunction = shape.ensure(inputFunction);\n// ⮕ (arg: string) =\u003e any\n```\n\nThe pseudocode below demonstrates the inner workings of the `outputFunction`:\n\n```ts\nfunction outputFunction(...inputArgs) {\n\n  const outputThis = shape.thisShape.parse(this);\n\n  const outputArgs = shape.argsShape.parse(inputArgs);\n\n  const inputResult = inputFunction.apply(outputThis, outputArgs);\n  \n  const outputResult = shape.resultShape.parse(inputResult);\n  \n  return outputResult;\n}\n```\n\n# `instanceOf`\n\n[`d.instanceOf`](https://smikhalevski.github.io/doubter/next/functions/core.instanceOf.html) returns an\n[`InstanceShape`](https://smikhalevski.github.io/doubter/next/classes/core.InstanceShape.html) instance.\n\nConstrains a value to be an object that is an instance of a class:\n\n```ts\nclass User {\n  name?: string;\n}\n\nd.instanceOf(User);\n// ⮕ Shape\u003cUser\u003e\n```\n\n# `intersection`, `and`\n\n[`d.intersection`](https://smikhalevski.github.io/doubter/next/functions/core.intersection.html) returns an\n[`IntersectionShape`](https://smikhalevski.github.io/doubter/next/classes/core.IntersectionShape.html) instance.\n\nCreates a shape that checks that the input value conforms to all shapes.\n\n```ts\nd.intersection([\n  d.object({\n    name: d.string()\n  }),\n  d.object({\n    age: d.number()\n  })\n]);\n// ⮕ Shape\u003c{ name: string } \u0026 { age: number }\u003e\n```\n\nOr use a shorter alias `and`:\n\n```ts\nd.and([\n  d.array(d.string()),\n  d.array(d.enum(['Peter', 'Paul']))\n]);\n// ⮕ Shape\u003cstring[] \u0026 Array\u003c'Peter' | 'Paul'\u003e\u003e\n```\n\n## Intersecting objects\n\nWhen working with objects, [extend objects](#extending-objects) instead of intersecting them whenever possible, since\nobject shapes are more performant than object intersection shapes.\n\nThere's a logical difference between extended and intersected objects. Let's consider two shapes that both contain the\nsame key:\n\n```ts\nconst shape1 = d.object({\n  foo: d.string(),\n  bar: d.boolean(),\n});\n\nconst shape2 = d.object({\n  // 🟡 Notice that the type of foo property in shape2 differs from shape1.\n  foo: d.number()\n});\n```\n\nWhen you [extend an object](#extending-objects) properties of the left object are overwritten with properties of the\nright object:\n\n```ts\nconst shape = shape1.extend(shape2);\n// ⮕ Shape\u003c{ foo: number, bar: boolean }\u003e\n```\n\nThe intersection requires the input value to conform both shapes at the same time, it's not possible since there are no\nvalues that can satisfy the `string | number` type. So the type of property `foo` becomes `never` and no value would be\nable to satisfy the resulting intersection shape.\n\n```ts\nconst shape = d.and([shape1, shape2]);\n// ⮕ Shape\u003c{ foo: never, bar: boolean }\u003e\n```\n\n# `lazy`\n\n[`d.lazy`](https://smikhalevski.github.io/doubter/next/functions/core.lazy.html) returns a\n[`LazyShape`](https://smikhalevski.github.io/doubter/next/classes/core.LazyShape.html) instance.\n\nWith `lazy` you can declare recursive shapes. To showcase how to use it, let's create a shape that validates JSON data:\n\n```ts\ntype JSON =\n  | number\n  | string\n  | boolean\n  | null\n  | JSON[]\n  | { [key: string]: JSON };\n\nconst jsonShape: d.Shape\u003cJSON\u003e = d.lazy(() =\u003e\n  d.or([\n    d.number(),\n    d.string(),\n    d.boolean(),\n    d.null(),\n    d.array(jsonShape),\n    d.record(jsonShape)\n  ])\n);\n\njsonShape.parse({ name: 'Jill' });\n// ⮕ { name: 'Jill' }\n\njsonShape.parse({ tag: Symbol() });\n// ❌ ValidationError: type.union at /tag: Must conform the union\n```\n\nNote that the `JSON` type is defined explicitly, because it cannot be inferred from the shape which references itself\ndirectly in its own initializer.\n\nYou can also use `d.lazy` like this:\n\n```ts\nconst jsonShape: d.Shape\u003cJSON\u003e = d.or([\n  d.number(),\n  d.string(),\n  d.boolean(),\n  d.null(),\n  d.array(d.lazy(() =\u003e jsonShape)),\n  d.record(d.lazy(() =\u003e jsonShape))\n]);\n```\n\n## Circular object references\n\nDoubter supports circular object references out-of-the-box:\n\n```ts\ninterface User {\n  friends: User[];\n}\n\nconst hank: User = {\n  friends: []\n};\n\n// 🟡 The circular reference\nhank.friends.push(hank);\n\nconst userShape1: d.Shape\u003cUser\u003e = d.lazy(() =\u003e\n  d.object({\n    friends: d.array(userShape1)\n  })\n);\n\nuserShape1.parse(hank);\n// ⮕ hank\n\nuserShape1.parse(hank).friends[0];\n// ⮕ hank\n```\n\nYou can replace circular references with a replacement value:\n\n```ts\nconst userShape2: d.Shape\u003cUser\u003e = d.lazy(() =\u003e\n  d.object({\n    friends: d.array(userShape2)\n  })\n).circular('Me and Myself');\n\nuserShape1.parse(hank);\n// ⮕ hank\n\nuserShape2.parse(hank).friends[0];\n// ⮕ 'Me and Myself'\n```\n\nYou can [provide a callback](https://smikhalevski.github.io/doubter/next/classes/core.LazyShape.html#circular)\nthat returns a value that is used as a replacement value for circular references. Or it can throw a\n[`ValidationError`](#validation-errors) from the callback to indicate that circular references aren't allowed:\n\n```ts\nconst userShape3: d.Shape\u003cUser\u003e = d.lazy(() =\u003e\n  d.object({\n    friends: d.array(userShape3)\n  })\n).circular((input, options) =\u003e {\n  throw new d.ValidationError([{ code: 'kaputs' }]);\n});\n\nuserShape1.parse(hank);\n// ❌ ValidationError: kaputs at /friends/0\n```\n\nBy default, Doubter neither parses nor validates an object if it was already seen, and returns such object as is. This\nbehaviour was chosen as the default for `d.lazy` because otherwise the result would be ambiguous when conversions are\nintroduced.\n\n```ts\ninterface Foo {\n  bar?: Foo;\n}\n\nconst foo: Foo = {};\n\nfoo.bar = foo;\n\nconst fooShape: d.Shape\u003cFoo, string\u003e = d.lazy(() =\u003e\n  d.object({\n    bar: fooShape.optional(),\n  })\n).convert(output =\u003e {\n  //        ⮕ {bar?: Foo} | {bar?: string}\n  return 'hello';\n});\n\nfooShape.parse(foo);\n// ⮕ 'hello'\n```\n\n# `map`\n\n[`d.map`](https://smikhalevski.github.io/doubter/next/functions/core.map.html) returns a\n[`MapShape`](https://smikhalevski.github.io/doubter/next/classes/core.MapShape.html) instance.\n\nConstrains an input to be a `Map` instance:\n\n```ts\nd.map(d.string(), d.number());\n// ⮕ Shape\u003cMap\u003cstring, number\u003e\u003e\n```\n\nMark a `Map` as readonly:\n\n```ts\nd.map(d.string(), d.number()).readonly();\n// ⮕ Shape\u003cMap\u003cstring, number\u003e, ReadonlyMap\u003cstring, number\u003e\u003e\n```\n\n\u003e [!NOTE]\\\n\u003e Marking a `Map` as readonly, only affects type checking. At runtime, you would still be able to set and delete items.\n\n## Coerce to a `Map`\n\nArrays, iterables and array-like objects that withhold entry-like elements (a tuple with two elements) are converted to\n`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmikhalevski%2Fdoubter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmikhalevski%2Fdoubter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmikhalevski%2Fdoubter/lists"}