{"id":13727027,"url":"https://github.com/badrap/valita","last_synced_at":"2026-05-21T00:10:32.046Z","repository":{"id":38829666,"uuid":"342700336","full_name":"badrap/valita","owner":"badrap","description":"A typesafe validation \u0026 parsing library for TypeScript.","archived":false,"fork":false,"pushed_at":"2026-05-04T16:49:54.000Z","size":1065,"stargazers_count":420,"open_issues_count":7,"forks_count":12,"subscribers_count":8,"default_branch":"main","last_synced_at":"2026-05-04T18:35:53.797Z","etag":null,"topics":["bun","deno","input-validation","javascript","nodejs","typesafe","typescript","validation-library"],"latest_commit_sha":null,"homepage":"","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/badrap.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-02-26T21:02:25.000Z","updated_at":"2026-05-04T16:48:27.000Z","dependencies_parsed_at":"2024-01-07T21:05:37.450Z","dependency_job_id":"beb3ac4a-9f2e-4a43-adcb-211a5c9323ca","html_url":"https://github.com/badrap/valita","commit_stats":{"total_commits":394,"total_committers":8,"mean_commits":49.25,"dds":0.07614213197969544,"last_synced_commit":"fdae912bc17e614360ff5a3dbae5dae142650f5b"},"previous_names":[],"tags_count":68,"template":false,"template_full_name":null,"purl":"pkg:github/badrap/valita","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badrap%2Fvalita","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badrap%2Fvalita/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badrap%2Fvalita/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badrap%2Fvalita/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/badrap","download_url":"https://codeload.github.com/badrap/valita/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badrap%2Fvalita/sbom","scorecard":{"id":222959,"data":{"date":"2025-08-11","repo":{"name":"github.com/badrap/valita","commit":"cbe4d3bf082fbca1ed97ac4dfeaf69a7990aada4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":6.9,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/20 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":10,"reason":"16 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/ci.yml:12","Info: jobLevel 'contents' permission set to 'read': .github/workflows/version-or-publish.yml:17","Info: jobLevel 'issues' permission set to 'read': .github/workflows/version-or-publish.yml:39","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/version-or-publish.yml:36","Info: found token with 'none' permissions: .github/workflows/ci.yml:1","Info: found token with 'none' permissions: .github/workflows/version-or-publish.yml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Pinned-Dependencies","score":7,"reason":"dependency not pinned by hash detected -- score normalized to 7","details":["Warn: containerImage not pinned by hash: .devcontainer/Dockerfile:1: pin your Docker image by updating node:22-alpine to node:22-alpine@sha256:1b2479dd35a99687d6638f5976fd235e26c5b37e8122f786fcd5fe231d63de5b","Warn: npmCommand not pinned by hash: .github/workflows/version-or-publish.yml:52","Info:   6 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   3 out of   4 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: all commits (10) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T03:02:02.628Z","repository_id":38829666,"created_at":"2025-08-17T03:02:02.628Z","updated_at":"2025-08-17T03:02:02.628Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33281514,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-20T15:12:43.734Z","status":"ssl_error","status_checked_at":"2026-05-20T15:12:42.300Z","response_time":356,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["bun","deno","input-validation","javascript","nodejs","typesafe","typescript","validation-library"],"created_at":"2024-08-03T01:03:36.327Z","updated_at":"2026-05-21T00:10:32.039Z","avatar_url":"https://github.com/badrap.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","typescript"],"sub_categories":[],"readme":"# @badrap/valita [![CI](https://github.com/badrap/valita/actions/workflows/ci.yml/badge.svg)](https://github.com/badrap/valita/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/@badrap/valita.svg)](https://npmx.dev/package/@badrap/valita) [![JSR](https://jsr.io/badges/@badrap/valita)](https://jsr.io/@badrap/valita)\n\nA TypeScript library for validating \u0026 parsing structured objects. The API is _heavily_ influenced by [Zod's](https://zod.dev/) excellent API, while the implementation side aims for the impressive performance of [simple-runtypes](https://github.com/hoeck/simple-runtypes).\n\n```ts\nconst vehicle = v.union(\n  v.object({ type: v.literal(\"plane\"), airline: v.string() }),\n  v.object({ type: v.literal(\"train\") }),\n  v.object({ type: v.literal(\"automobile\"), make: v.string() }),\n);\nvehicle.parse({ type: \"bike\" });\n// ValitaError: invalid_literal at .type (expected \"plane\", \"train\" or \"automobile\")\n```\n\n\u003e [!NOTE]\n\u003e While this package is still evolving, we're currently not accepting any new feature requests or suggestions. Please use the issue tracker for bug reports and security concerns, which we highly value and welcome. Thank you for your understanding ❤️\n\n## Goals and Non-Goals\n\n### Goals\n\n1. **Input Validation \u0026 Parsing**: The fundamental goal of the library is to ensure that incoming data, which might not be from a trusted source, aligns with the predetermined format.\n2. **Minimalism**: Deliver a streamlined and concentrated library that offers just the essentials.\n3. **Extensibility**: Allow users to create their own validators and parsers that cater to specific validation scenarios.\n\n### Non-Goals\n\n1. **Data Definition**: The library is designed to validate and parse input data as it enters the program, rather than serving as an exhaustive tool for defining all types within the program after obtaining input.\n2. **Extensive Built-In Formats**: The library does not prioritize having a large array of built-in validation formats out of the box.\n3. **Asynchronous Parsing**: Asynchronous operations are outside the scope for this library.\n\n## Installation\n\n### For Node.js\n\n```sh\npnpm add @badrap/valita\n```\n\n### For Deno\n\n```sh\ndeno add @badrap/valita\n```\n\n## API Reference\n\nThis section contains an overview of all validation methods.\n\n### Primitive Types\n\nLet's start with the basics! Like every validation library we support all primitive types like strings, numbers, booleans and more. For example the `v.string()` primitive can be used like this to check whether some input value is a string:\n\n```ts\nimport * as v from \"@badrap/valita\";\n\nconst t = v.string();\nt.parse(\"Hello, World!\");\n// \"Hello, World!\"\n```\n\nTry to parse anything that's not a string and you get an error:\n\n```ts\nt.parse(1);\n// ValitaError: invalid_type at . (expected string)\n```\n\n`.parse(...)` is typed in a way that it accepts any type of input value, but returns a properly typed value on success:\n\n```ts\nconst u: unknown = \"Hello, World!\";\n\n// TypeScript type checking is happy with this!\nconst s: string = t.parse(u);\n```\n\nThe primitive types are:\n\n- `v.string()`: Check that the value is a string.\n- `v.number()`: Check that the value is a number (i.e. `typeof value === \"number\"`, which includes NaN and ±Infinity).\n- `v.bigint()`: Check that the value is a [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) (i.e. `typeof value === \"bigint\"`)\n- `v.boolean()`: Check that the value is a boolean.\n- `v.null()`: Check that the value is `null`.\n- `v.undefined()`: Check that the value is `undefined`.\n\n### Literal Types\n\nSometimes knowing if a value is of a certain type is not enough. We can use the `.literal()` method to check for actual values, like checking if a string is either `\"red\"`, `\"green\"` or `\"blue\"` and not just any string.\n\n```ts\nconst rgb = v.union(v.literal(\"red\"), v.literal(\"green\"), v.literal(\"blue\"));\n\nrgb.parse(\"green\");\n// \"green\"\n\nrgb.parse(\"magenta\");\n// ValitaError: invalid_literal at . (expected \"red\", \"green\" or \"blue\")\n```\n\nWe can also use this to check for concrete numbers, bigint literals or boolean values:\n\n```ts\nv.literal(1); // must be number 1\nv.literal(1n); // must be bigint 1n\nv.literal(true); // must be true\n```\n\nFor more complex values you can use the `.assert()`-method. Check out [Custom Validators](#custom-validators) to learn more about it.\n\n### Allow Any / Forbid All\n\nValita doesn't contain a built-in equivalent to TypeScript's `any` type. However, `v.unknown()` is analogous to TypeScript's `unknown` type and can be used to accept any input value:\n\n```ts\nconst u = v.unknown();\n\nu.parse(1);\n// 1\n```\n\nThe inverse of `v.unknown()` is `v.never()` that fails for every value. This is analogous to TypeScript's `never` type.\n\n```ts\nconst n = v.never();\n\nn.parse(1);\n// ValitaError: invalid_type at . (expected nothing)\n```\n\nBy themselves `v.unknown()` and `v.never()` are not terribly useful, but they become more relevant with composite types such as [Object Types](#object-types).\n\n### Object Types\n\nValidators can be further combined to larger, arbitrarily complex validators. One such combinator is `v.object(...)`, used to check that the input value is an object that has some named properties, and that those properties have a specific type.\n\n```ts\nconst o = v.object({\n  company: v.string(),\n\n  // Nested objects work fine too!\n  address: v.object({\n    city: v.string(),\n    country: v.string(),\n  }),\n});\n\no.parse({\n  name: \"Acme Inc.\",\n  address: { city: \"Springfield\", country: \"Freedomland\" },\n});\n// {\n//   name: \"Acme Inc.\",\n//   address: { city: \"Springfield\", country: \"Freedomland\" },\n// }\n\no.parse({ name: \"Acme Inc.\" });\n// ValitaError: missing_value at .address (missing value)\no.parse({\n  name: \"Acme Inc.\",\n  ceo: \"Wiley E. Coyote\",\n  address: { city: \"Springfield\", country: \"Freedomland\" },\n});\n// ValitaError: unrecognized_keys at . (unrecognized key \"ceo\")\n```\n\nAs seen above, unexpected keys like `\"ceo\"` are prohibited by default. That default can be changed with [Parsing Modes](#parsing-modes).\n\n#### Parsing Modes\n\nBy default `v.object(...)` throws an error when it encounters an object with unexpected keys. That behavior can be changed by explicitly passing a _parsing mode_ to `.parse(...)`:\n\n```ts\nconst o = v.object({\n  name: v.string(),\n});\n\n// Strip away the extra keys\no.parse({ name: \"Acme Inc.\", ceo: \"Wiley E. Coyote\" }, { mode: \"strip\" });\n// { name: \"Acme Inc.\" }\n\n// Pass the extra keys through as-is\no.parse({ name: \"Acme Inc.\", ceo: \"Wiley E. Coyote\" }, { mode: \"passthrough\" });\n// { name: \"Acme Inc.\", ceo: \"Wiley E. Coyote\" }\n\n// Forbid extra keys. This is the default.\no.parse({ name: \"Acme Inc.\", ceo: \"Wiley E. Coyote\" }, { mode: \"strict\" });\n// ValitaError: unrecognized_keys at . (unrecognized key \"ceo\")\n```\n\nThe possible values are:\n\n- `{ mode: \"strict\" }`: Forbid extra keys. This is the default.\n- `{ mode: \"strip\" }`: Don't fail on extra keys - instead strip them away from the output object.\n- `{ mode: \"passthrough\" }`: Just ignore the extra keys and pretend you didn't see them.\n\nThe parsing mode applies to all levels of your validation hierarcy, even to nested objects.\n\n```ts\nconst o = v.object({\n  company: v.object({\n    name: v.string(),\n  }),\n});\n\no.parse(\n  {\n    company: { name: \"Acme Inc.\", ceo: \"Wiley E. Coyote\" },\n    greeting: \"Hello!\",\n  },\n  { mode: \"strip\" },\n);\n// { company: { name: \"Acme Inc.\" } }\n```\n\n#### Rest Properties \u0026 Records\n\nSometimes you may want to allow extra keys in addition to the defined keys. For that you can use `.rest(...)`, and additionally require the extra keys to have a specific type of value:\n\n```ts\nconst o = v\n  .object({\n    name: v.string(),\n    age: v.number(),\n  })\n  .rest(v.string());\n\no.parse({ name: \"Example McExampleface\", age: 42, socks: \"yellow\" });\n// { name: \"Example McExampleface\", age: 42, socks: \"yellow\" }\n\no.parse({ name: \"Example McExampleface\", age: 42, numberOfDogs: 2 });\n// ValitaError: invalid_type at .numberOfDogs (expected string)\n```\n\nThe `.rest(...)` method is also handy for allowing or forbidding extra keys for a specific parts of your object hierarchy, regardless of the parsing mode.\n\n```ts\nconst lenient = v.object({}).rest(v.unknown()); // *Always* allow extra keys\nlenient.parse({ socks: \"yellow\" }, { mode: \"strict\" });\n// { socks: \"yellow\" }\n\nconst strict = v.object({}).rest(v.never()); // *Never* allow extra keys\nstrict.parse({ socks: \"yellow\" }, { mode: \"strip\" });\n// ValitaError: invalid_type at .socks (expected nothing)\n```\n\nFor always allowing a completely arbitrary number of properties, `v.record(...)` is shorthand for `v.object({}).rest(...)`. This is analogous to the `Record\u003cstring, ...\u003e` type in TypeScript.\n\n```ts\nconst r = v.record(v.number());\n\nr.parse({ a: 1, b: 2 });\n// { a: 1, b: 2 }\n\nr.parse({ a: 1, b: \"hello\" });\n// ValitaError: invalid_type at .b (expected number)\n```\n\nFor allowing any collection of properties of any type, `v.record()` is a shorthand for `v.record(v.unknown())`.\n\n```ts\nconst r = v.record();\n\nr.parse({ a: 1, b: 2 });\n// { a: 1, b: 2 }\nr.parse({ a: 1, b: \"hello\" });\n// { a: 1, b: \"hello\" }\n\n// The input still has to be an object.\nr.parse([]);\n// ValitaError: invalid_type at . (expected object)\n```\n\n#### Optional Properties\n\nOne common API pattern is that some object fields are _optional_, i.e. they can be missing completely or be set to `undefined`. You can allow some keys to be missing by annotating them with `.optional()`.\n\n```ts\nconst person = v.object({\n  name: v.string(),\n  // Not everyone filled in their theme song\n  themeSong: v.string().optional(),\n});\n\nperson.parse({ name: \"Jane Doe\", themeSong: \"Never gonna give you up\" });\n// { name: \"Jane Doe\", themeSong: \"Never gonna give you up\" }\nperson.parse({ name: \"Jane Doe\" });\n// { name: \"Jane Doe\" }\nperson.parse({ name: \"Jane Doe\", themeSong: undefined });\n// { name: \"Jane Doe\", themeSong: undefined }\n```\n\nOptionals are only used with `v.object(...)` and don't work as standalone parsers.\n\n```ts\nconst t = v.string().optional();\n\n// TypeScript error: Property 'parse' does not exist on type 'Optional\u003cstring\u003e'\nt.parse(\"Hello, World!\");\n```\n\nAn optional function can be used to replace a missing or undefined values with some other default value:\n\n```ts\nconst person = v.object({\n  name: v.string(),\n  // Set a sensible default for those unwilling to fill in their theme song\n  themeSong: v.string().optional(() =\u003e \"Tribute\"),\n});\n\nperson.parse({ name: \"Jane Doe\", themeSong: \"Never gonna give you up\" });\n// { name: \"Jane Doe\", themeSong: \"Never gonna give you up\" }\nperson.parse({ name: \"Jane Doe\" });\n// { name: \"Jane Doe\", themeSong: \"Tribute\" }\nperson.parse({ name: \"Jane Doe\", themeSong: undefined });\n// { name: \"Jane Doe\", themeSong: \"Tribute\" }\n```\n\nThe default function is re-evaluated every for every missing or undefined value to avoid accidentally sharing mutable default values like objects or arrays between different parsed values.\n\n### Array Types\n\nThe `v.array(...)` combinator can be used to check that the value is an array, and that its items have a specific type. The validated arrays may be of arbitrary length, including empty arrays.\n\n```ts\nconst a = v.array(v.object({ name: v.string() }));\n\na.parse([{ name: \"Acme Inc.\" }, { name: \"Evil Corporation\" }]);\n// [{ name: \"Acme Inc.\" }, { name: \"Evil Corporation\" }]\na.parse([]);\n// []\n\na.parse({ 0: { name: \"Acme Inc.\" } });\n// ValitaError: invalid_type at . (expected array)\n```\n\nFor allowing any array, `v.array()` is a shorthand for `v.array(v.unknown())`.\n\n```ts\nconst a = v.array();\n\na.parse([\"foo\", 1]);\n// [\"foo\", 1]\na.parse([]);\n// []\n\n// Only arrays are allowed, though.\na.parse({ 0: { name: \"Acme Inc.\" } });\n// ValitaError: invalid_type at . (expected array)\n```\n\n### Tuple Types\n\nDespite JavaScript not having tuple values ([...yet?](https://github.com/tc39/proposal-record-tuple)), many APIs emulate them with arrays. For example, if we needed to encode a range between two numbers we might choose `type Range = [number, number]` as the data type. From JavaScript's point of view it's just an array but TypeScript knows about the value of each position and that the array **must** have two entries.\n\nWe can express this kind of type with `v.tuple(...)`:\n\n```ts\nconst range = v.tuple([v.number(), v.number()]);\n\nrange.parse([1, 2]);\n// [1, 2]\nrange.parse([200, 2]);\n// [200, 2]\n\nrange.parse([1]);\n// ValitaError: invalid_length at . (expected an array with 2 item(s))\nrange.parse([1, 2, 3]);\n// ValitaError: invalid_length at . (expected an array with 2 item(s))\nrange.parse([1, \"2\"]);\n// ValitaError: invalid_type at .1 (expected number)\n```\n\nTuples can be concatenated with other tuples to create new tuple types, and with even arrays to create [variadic tuple types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#variadic-tuple-types).\n\n```ts\nconst twoNumbers = v.tuple([v.number(), v.number()]); // Type\u003c[number number]\u003e\nconst twoStrings = v.tuple([v.string(), v.string()]); // Type\u003c[string, string]\u003e\nconst booleans = v.array(v.boolean()); // Type\u003cboolean[]\u003e\n\ntwoNumbers.concat(twoStrings);\n// Type\u003c[number, number, string, string]\u003e\n\ntwoNumbers.concat(booleans);\n// Type\u003c[number, number, ...boolean[]]\u003e\n\nbooleans.concat(twoStrings);\n// Type\u003c[...boolean[], string, string]\u003e\n\ntwoNumbers.concat(booleans).concat(twoStrings);\n// Type\u003c[number, number, ...boolean[], string, string]\u003e\n```\n\nTwo variable-length tuples or arrays can not be concatenated, though, so this is a type-level error and also throws an error at runtime:\n\n```ts\nv.tuple([]).concat(v.array()).concat(v.array());\n// TypeError: can not concatenate two variadic types\n```\n\n### Union Types\n\nA union type is a value which can have several different representations. Let's imagine we have a value of type `Shape` that can be either a triangle, a circle or a square:\n\n```ts\nconst triangle = v.object({ type: v.literal(\"triangle\") });\nconst square = v.object({ type: v.literal(\"square\") });\nconst circle = v.object({ type: v.literal(\"circle\") });\n\nconst shape = v.union(triangle, square, circle);\n\nshape.parse({ type: \"triangle\" });\n// { type: \"triangle\" }\n\nshape.parse({ type: \"heptagon\" });\n// ValitaError: invalid_literal at .type (expected \"triangle\", \"square\" or \"circle\")\n```\n\nNote that although in this example all representations are objects and have the shared property `type`, it's not necessary at all. Each representation can have completely different base type.\n\n```ts\nconst primitive = v.union(v.number(), v.string(), v.boolean());\n\nprimitive.parse(\"Hello, World!\");\n// \"Hello, World!\"\n\nprimitive.parse({});\n// ValitaError: invalid_type at . (expected number, string or boolean)\n```\n\n#### Nullable Type\n\nWhen working with APIs or databases some types may be nullable. The `t.nullable()` shorthand returns a validator equivalent to `v.union(v.null(), t)`.\n\n```ts\n// type name = null | string\nconst name = v.string().nullable();\n\n// Passes\nname.parse(\"Acme Inc.\");\n// Passes\nname.parse(null);\n```\n\nSimilarly to `.optional()`, a default value function can be used to replace a `null` values with some other default value:\n\n```ts\nconst name = v.string().nullable(() =\u003e \"Unknown\");\n\nname.parse(\"Jane Doe\");\n// \"Jane Doe\"\nname.parse(null);\n// \"Unknown\"\n```\n\nThe default function is re-evaluated every for every `null` value to avoid accidentally sharing mutable default values like objects or arrays between different parsed values.\n\n### Recursive Types\n\nSome types can contain arbitrary nesting, like `type T = string | T[]`. We can express such types with `.lazy(...)`.\n\nNote that TypeScript can not infer return types of recursive functions. That's why `v.lazy(...)` validators need to be explicitly typed with `v.Type\u003cT\u003e`.\n\n```ts\ntype T = string | T[];\nconst myType: v.Type\u003cT\u003e = v.lazy(() =\u003e v.union(v.string(), v.array(myType)));\n```\n\n### Custom Validators\n\nThe `.assert()` method can be used for custom validation logic, like checking that object properties are internally consistent.\n\n```ts\nconst Span = v\n  .object({ start: v.number(), end: v.number() })\n  .assert((obj) =\u003e obj.start \u003c= obj.end);\n\nSpan.parse({ start: 1, end: 2 });\n// { start: 1, end: 2 }\n\nSpan.parse({ start: 2, end: 1 });\n// ValitaError: custom_error at . (validation failed)\n```\n\nYou can also _refine_ the input type by passing in a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates). Note that the type predicate must have a compatible input type.\n\n```ts\nfunction isEventHandlerName(s: string): s is `on${string}` {\n  return s.startsWith(\"on\");\n}\n\nconst e = v.string().assert(isEventHandlerName);\n\nconst name: `on${string}` = e.parse(\"onscroll\");\n// \"onscroll\"\n\ne.parse(\"Steven\");\n// ValitaError: custom_error at . (validation failed)\n```\n\nEach `.assert(...)` returns a new validator, so you can further refine already refined types. You can also pass in a custom failure messages.\n\n```js\nconst Integer = v.number().assert((n) =\u003e Number.isInteger(n), \"not an integer\");\n\nconst Byte = Integer.assert((i) =\u003e i \u003e= 0 \u0026\u0026 i \u003c= 255, \"not between 0 and 255\");\n\nByte.parse(1);\n// 1\n\nByte.parse(1.5);\n// ValitaError: custom_error at . (not an integer)\nByte.parse(300);\n// ValitaError: custom_error at . (not between 0 and 255)\n```\n\nCustom validators can be used like any other built-in validator. This means that you can define helpers tailored to your specific use cases and reuse them over and over.\n\n```js\n// Reusable custom validator\nconst Organization = v\n  .object({\n    name: v.string(),\n    active: v.boolean(),\n  })\n  .assert((org) =\u003e org.active);\n\n// Reuse the custom validator\nconst Transaction = v.object({\n  buyer: Organization,\n  seller: Organization,\n  amount: v.number(),\n});\n```\n\n### Custom Parsers\n\nWhile `.assert(...)` can ensure that a value is valid and even refine the value's type, it can't alter the value itself. Yet sometimes we may want to validate and transform the value in one go.\n\nThe `.map(...)` method is great for cases when you know that the transformation can't fail. The output type doesn't have to stay same:\n\n```ts\nconst l = v.string().map((s) =\u003e s.length);\n\nl.parse(\"Hello, World!\");\n// 13\n\nl.parse(1);\n// ValitaError: invalid_type at . (expected string)\n```\n\nThe `.chain(...)` method is more powerful: it can also be used for cases where the parsing might fail. Imagine a JSON API which outputs dates in the YYYY-MM-DD format and we want to return a valid `Date` from our validation phase:\n\n```json\n{\n  \"created_at\": \"2022-01-01\"\n}\n```\n\n`.chain(...)`, much like map, receives a function to which it will pass the raw value as the first argument. If the transformation fails, we return an error (with an optional message) with `v.err(...)`. If not, then we return the transformed value with `v.ok(...)`.\n\n```js\nconst DateType = v.string().chain((s) =\u003e {\n  const date = new Date(s);\n\n  if (isNaN(+date)) {\n    return v.err(\"invalid date\");\n  }\n\n  return v.ok(date);\n});\n\nconst APIResponse = v.object({\n  created_at: DateType,\n});\n\nAPIResponse.parse({ created_at: \"2022-01-01\" });\n// { created_at: 2022-01-01T00:00:00.000Z }\n\nAPIResponse.parse({ created_at: \"YOLO\" });\n// ValitaError: custom_error at .created_at (invalid date)\n```\n\nFor both `.map(...)` and `.chain(...)` we highly recommend that you avoid mutating the input value. Prefer returning a new value instead.\n\n```ts\nv.object({ name: v.string() }).map((obj) =\u003e {\n  // Mutating the input value like below is highly discouraged:\n  //  obj.id = randomUUID();\n  // Return a new value instead:\n  return { ...obj, id: randomUUID() };\n});\n```\n\n### Parsing Without Throwing\n\nThe `.parse(...)` method used thus far throws a ValitaError when validation or parsing fails. The `.try(...)` method can be used when you'd rather throw only actually exceptional cases such as coding errors. Parsing modes are also supported.\n\n```ts\nconst o = v.object({ name: v.string() });\n\no.try({ name: \"Acme Inc.\" });\n// { ok: true, value: { name: \"Acme Inc.\" } }\no.try({ name: \"Acme Inc.\", country: \"Freedomland\" }, { mode: \"strip\" });\n// { ok: true, value: { name: \"Acme Inc.\" } }\n\no.try({});\n// { ok: false, message: \"missing_value at .name (missing value)\" }\n```\n\nThe `.ok` property can be used to inspect the outcome in a typesafe way.\n\n```ts\n// Fail about 50% of the time\nconst r = o.try(Math.random() \u003c 0.5 ? { name: \"Acme Inc.\" } : {});\n\nif (r.ok) {\n  // r.value is defined within this block\n  console.log(`Success: ${r.value}`);\n} else {\n  // r.message is defined within this block\n  console.log(`Failure: ${r.message}`);\n}\n```\n\nFor allow further composition, `.try(...)`'s return values are compatible with `.chain(...)`. The chained function also receives a second parameter that contains the parsing mode, and can be passed forward to `.try(...)`.\n\n```ts\nconst Company = v.object({ name: v.string() });\n\nconst CompanyString = v.string().chain((json, options) =\u003e {\n  let value: unknown;\n  try {\n    value = JSON.parse(json);\n  } catch {\n    return v.err(\"not valid JSON\");\n  }\n  return Company.try(value, options);\n});\n\nCompanyString.parse('{ \"name\": \"Acme Inc.\" }');\n// { name: \"Acme Inc.\" }\n\nCompanyString.parse('{ \"name\": \"Acme Inc.\", \"ceo\": \"Wiley E. Coyote\" }');\n// ValitaError: unrecognized_keys at . (unrecognized key \"ceo\")\n\n// The parsing mode is forwarded to .try(...)\nCompanyString.parse('{ \"name\": \"Acme Inc.\", \"ceo\": \"Wiley E. Coyote\" }', {\n  mode: \"strip\",\n});\n// { name: 'Acme Inc.' }\n```\n\nTurns out that composing parsers like this is relatively common. Therefore you can pass types to `.chain(...)`. The following is equal to the above definition of `CompanyString`:\n\n```ts\nconst Company = v.object({ name: v.string() });\n\n// We now have a handy common helper for parsing JSON strings!\nconst JsonString = v.string().chain((json) =\u003e {\n  try {\n    return v.ok(JSON.parse(json));\n  } catch {\n    return v.err(\"not valid JSON\");\n  }\n});\n\nconst CompanyString = JsonString.chain(Company);\n```\n\n### Inferring Output Types\n\nThe exact output type of a validator can be _inferred_ from a type validator's using with `v.Infer\u003ctypeof ...\u003e`:\n\n```ts\nconst Person = v.object({\n  name: v.string(),\n  age: v.number().optional(),\n});\n\ntype Person = v.Infer\u003ctypeof Person\u003e;\n// type Person = { name: string, age?: number };\n```\n\n### Type Composition Tips \u0026 Tricks\n\n#### Reduce, Reuse, Recycle\n\nThe API interface of this library is intentionally kept small - for some definition of small. As such we encourage curating a library of helpers tailored for your specific needs. For example a reusable helper for ensuring that a number falls between a specific range could be defined and used like this:\n\n```ts\nfunction between(min: number, max: number) {\n  return (n: number) =\u003e {\n    if (n \u003c min || n \u003e max) {\n      return v.err(\"outside range\");\n    }\n    return v.ok(n);\n  };\n}\n\nconst num = v.number().chain(between(0, 255));\n```\n\n#### Type Inference \u0026 Generics\n\nEvery standalone validator fits the type `v.Type\u003cOutput\u003e`, `Output` being the validator's output type. TypeScript's generics and type inference can be used to define helpers that take in validators and do something with them. For example a `readonly(...)` helper that casts the output type to a readonly (non-recursively) could be defined and used as follows:\n\n```ts\nfunction readonly\u003cT\u003e(t: v.Type\u003cT\u003e): v.Type\u003cReadonly\u003cT\u003e\u003e {\n  return t as v.Type\u003cReadonly\u003cT\u003e\u003e;\n}\n\nconst User = readonly(v.object({ id: v.string() }));\ntype User = v.Infer\u003ctypeof User\u003e;\n// type User = { readonly id: string; }\n```\n\n#### Deconstructed Helpers\n\nSome validator types offer additional properties and methods for introspecting and transforming them further. One such case is `v.object(...)`'s `.shape` property that contains the validators for each property.\n\n```ts\nconst Company = v.object({\n  name: v.string().assert((s) =\u003e s.length \u003e 0, \"empty name\"),\n});\nCompany.shape.name.parse(\"Acme Inc.\");\n// \"Acme Inc.\"\n```\n\nHowever, because `.assert(...)`, `.map(...)` and `.chain(...)` may all restrict and transform the output type almost arbitrarily, their returned validators may not have the properties or methods specific to the original ones. For example a refined `v.object(...)` validator will not have the `.shape` property. Therefore the following will not work:\n\n```ts\nconst Company = v\n  .object({\n    name: v.string().assert((s) =\u003e s.length \u003e 0, \"empty name\"),\n    employees: v.number(),\n  })\n  .assert((c) =\u003e c.employees \u003e= 0);\n\nconst Organization = v.object({\n  // Try to reuse Company's handy name validator\n  name: Company.shape.name,\n});\n// TypeScript error: Property 'shape' does not exist on type 'Type\u003c{ name: string; }\u003e'\n```\n\nThe recommended solution is to deconstruct the original validators enough so that the common pieces can be directly reused:\n\n```ts\nconst NonEmptyString = v.string().assert((s) =\u003e s.length \u003e 0, \"empty\");\n\nconst Company = v\n  .object({\n    name: NonEmptyString,\n    employees: v.number(),\n  })\n  .assert((c) =\u003e c.employees \u003e= 0);\n\nconst Organization = v.object({\n  name: NonEmptyString,\n});\n```\n\n## License\n\nThis library is licensed under the MIT license. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbadrap%2Fvalita","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbadrap%2Fvalita","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbadrap%2Fvalita/lists"}