{"id":13727345,"url":"https://github.com/open-draft/reach-schema","last_synced_at":"2026-01-22T19:43:16.562Z","repository":{"id":38394330,"uuid":"198691753","full_name":"open-draft/reach-schema","owner":"open-draft","description":"Functional schema-driven JavaScript object validation library.","archived":false,"fork":false,"pushed_at":"2023-03-03T17:22:06.000Z","size":1084,"stargazers_count":36,"open_issues_count":12,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-14T17:48:12.208Z","etag":null,"topics":["javascript","object","reach-schema","resolver","schema","validate","validation","validation-library","validation-rules","validation-schema"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/reach-schema","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/open-draft.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2019-07-24T18:44:39.000Z","updated_at":"2024-06-30T10:39:38.000Z","dependencies_parsed_at":"2024-01-06T07:58:38.630Z","dependency_job_id":"2a2599c8-63e9-434b-84bc-0a11a44b6e2a","html_url":"https://github.com/open-draft/reach-schema","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-draft%2Freach-schema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-draft%2Freach-schema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-draft%2Freach-schema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/open-draft%2Freach-schema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/open-draft","download_url":"https://codeload.github.com/open-draft/reach-schema/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252965372,"owners_count":21832880,"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":["javascript","object","reach-schema","resolver","schema","validate","validation","validation-library","validation-rules","validation-schema"],"created_at":"2024-08-03T01:03:51.047Z","updated_at":"2026-01-22T19:43:16.524Z","avatar_url":"https://github.com/open-draft.png","language":"TypeScript","readme":"\u003cbr /\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./logo.svg\" alt=\"Reach schema logo\" width=\"200\" /\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\n\u003ch1 align=\"center\"\u003eReach Schema\u003c/h1\u003e\n\u003cp align=\"center\"\u003eFunctional schema-driven JavaScript object validation library.\u003c/p\u003e\n\n## Motivation\n\nIt happens that JavaScript Object validation libraries are often class-based and operate using via a chain of operators. With Reach Schema I would like to take an alternative approach, making validation functional.\n\n**Main concepts of React Schema:**\n\n1. Validation result is a function from schema and data.\n1. Validation result is not coupled with the error messages logic.\n\n\u003e Reach Schema works great together with functional programming libraries like [lodash](https://lodash.com/) or [ramda](https://ramdajs.com/). They allow to make validation declaration shorter and make your life easier. Consider those.\n\n## Validation schema\n\nData validity is described using a _validation schema_. It's a plain Object which keys represent the actual data keys hierarhcy, and values equal to _resolver functions_ that return the validation verdict.\n\n```ts\ninterface Schema {\n  [field: string]: Resolver | NamedResolver | Schema\n}\n\n// A plain resolver function that returns a boolean verdict.\ninterface Resolver\u003cValueType\u003e {\n  (value: ValueType): boolean\n}\n\n// A named resolver function that returns a Record of rules.\ninterface NamedResolver\u003cValueType\u003e {\n  (value: ValueType): {\n    [ruleName: string]: boolean\n  }\n}\n```\n\nApplying a validation schema to the actual data returns the validation result.\n\n```ts\ninterface ValidationResult {\n  errors: Error[]\n}\n```\n\n## Resolver\n\n_Resolver_ is a function that determines a value's validity. A simple resolver accepts a value and returns a boolean verdict. Reach Schema supports more complex resolvers, such as _grouped resolver_, which allows to provide multiple independent criteria to a single value.\n\n### Basic resolver\n\n```js\nuseSchema(\n  {\n    firstName: (value, pointer) =\u003e value === 'John',\n  },\n  {\n    firstName: 'Jessica',\n  },\n)\n```\n\n### Grouped resolver\n\n```js\nuseSchema(\n  {\n    password: (value, pointer) =\u003e ({\n      minLength: value.length \u003e 7,\n      capitalLetter: /[A-Z]/.test(value),\n      oneNumber: /[0-9]/.test(value),\n    }),\n  },\n  {\n    password: 'IshallPass8',\n  },\n)\n```\n\n### Nested schema\n\nResolver may also return an Object, if validating an Object type value, which would be treated as a nested [Validation schema](#validation-schema).\n\n```js\nuseSchema(\n  {\n    billingDetails: {\n      // Nested schema accepts all kinds of resolvers:\n      // basic, grouped, and deeply nested schema.\n      address: (value) =\u003e checkAddressExistance(value),\n      zipCode: (value) =\u003e /\\d{5}/.test(zipCode),\n    },\n  },\n  {\n    billingDetails: {\n      address: 'Sunwell Ave.',\n      zipCode: 56200,\n    },\n  },\n)\n```\n\n## Errors\n\nEach validation error has the following structure:\n\n```ts\ninterface Error {\n  // Pointer to the related property in the actual data.\n  pointer: string[]\n\n  // A property's validation state.\n  // - \"missing\". Expected, but not present in the actual data.\n  // - \"invalid\". Present, but doesn't satisfy the validation resolver.\n  status: 'missing' | 'invalid'\n\n  // A property's value, if present in the actual data.\n  value?: any\n\n  // Name of the rejected validation rule, if applicable.\n  rule?: string\n}\n```\n\n## API\n\n### `useSchema: (schema: Schema, data: Object): ValidationError[]`\n\n#### Basic example\n\nEach key in a schema corresponds to same property in the actual data. Each schema value is a _resolver_ function that accepts an actual data value and returns a `Boolean` verdict.\n\n```js\nimport { useSchema } from 'reach-schema'\n\nuseSchema(\n  {\n    firstName: (value) =\u003e value === 'john',\n    lastName: (value) =\u003e value === 'locke',\n    age: (value) =\u003e value \u003e 17,\n  },\n  {\n    firstName: 'john',\n    age: 16,\n  },\n)\n```\n\n```json\n[\n  {\n    \"pointer\": [\"lastName\"],\n    \"status\": \"missing\"\n  },\n  {\n    \"pointer\": [\"age\"],\n    \"status\": \"invalid\",\n    \"value\": 16\n  }\n]\n```\n\n#### Nested properties\n\nIf a schema key equals an Object literal, that nested Object is expected in the data. This allows to validate deeply nested structures.\n\n```js\nimport { useSchema } from 'reach-schema'\n\nuseSchema(\n  {\n    billingData: {\n      country: (value) =\u003e ['UK', 'ES'].includes(value),\n    },\n  },\n  {\n    billingData: {\n      country: 'US',\n    },\n  },\n)\n```\n\n```json\n[\n  {\n    \"pointer\": [\"billingData\", \"country\"],\n    \"status\": \"invalid\",\n    \"value\": \"US\"\n  }\n]\n```\n\n#### Multiple criteria\n\nA resolver function may also return a map of rules that apply to the corresponding nested properties. By default, the actual value must satisfy all the rules in order to be valid (**see [Optional validation](#optional-validation)**). Each resolver corresponding to a validation criteria is called _named resolver_.\n\n```js\nimport { useSchema } from 'reach-schema'\n\nuseSchema(\n  {\n    password: (value) =\u003e ({\n      minLength: value.length \u003e 5,\n      capitalLetters: /[A-Z]{2}/.test(value),\n      oneNumber: /[0-9]/.test(value),\n    }),\n  },\n  {\n    password: 'DeMo',\n  },\n)\n```\n\n```json\n[\n  {\n    \"pointer\": [\"password\"],\n    \"status\": \"invalid\",\n    \"value\": \"DeMo\",\n    \"rule\": \"minLength\"\n  },\n  {\n    \"pointer\": [\"password\"],\n    \"status\": \"invalid\",\n    \"value\": \"DeMo\",\n    \"rule\": \"oneNumber\"\n  }\n]\n```\n\n## Error messages\n\nReach Schema does not provide any error messages directly. Instead, it treats an error message as an artifact derived from the validation result. To achieve that it provides all the relevant information in the validation result to construct an error message.\n\nHowever, there is a common logic that can be integrated into such error messages construction (i.e. resolving due to priority, fallback messages). Declaring such logic each time would be lengthy, time-consuming, and prone to human error.\n\n## Recipes\n\n### Property existence\n\nTo check that a property exists in the actual data provide a resolver function that always returns `true`. Ideologically it marks the property as \"always valid\", but since the default validation behavior asserts all the keys present in the Validation Schema, it also implies that the property must be present.\n\n```js\nimport { useSchema } from 'reach-schema'\n\nuseSchema(\n  {\n    // The property \"email\" is required in the data Object,\n    // but is always valid, no matter the value.\n    email: () =\u003e true,\n  },\n  {\n    email: 'admin@example.com',\n  },\n)\n```\n\n### Optional validation\n\nTo apply an optional (_weak_) validation to a property wrap its resolver in the `optional` helper function. This way the property's value will be validated only if present in the actual data. If the property is missing in the actual data it's never validated and considered as valid.\n\n```js\nimport { useSchema, optional } from 'reach-schema'\n\nuseSchema(\n  {\n    firstName: optional((value) =\u003e value.length \u003e 1),\n    billingData: optional({\n      address: (value) =\u003e value.includes('st.'),\n      firstName: optional((value) =\u003e value.length \u003e 1),\n    }),\n  },\n  {\n    billingData: {\n      address: 'Invalid address',\n      firstName: 'J',\n    },\n  },\n)\n```\n\n```json\n[\n  {\n    \"pointer\": [\"billingData\", \"address\"],\n    \"status\": \"invalid\",\n    \"value\": \"Invalid address\"\n  },\n  {\n    \"pointer\": [\"billingData\", \"firstName\"],\n    \"status\": \"invalid\",\n    \"value\": \"J\"\n  }\n]\n```\n\n### Usage with TypeScript\n\n`useSchema` will infer the type of the given data automatically. However, to type guard the data itself it's useful to describe the data separately and provide to schema:\n\n```ts\ninterface UserDetails {\n  firstName: string\n  lastName: string\n  age: number\n}\n\nuseSchema\u003cUserDetails\u003e(\n  {\n    firstName: (value) =\u003e value.length \u003e 2,\n    lastName: (value) =\u003e value.length \u003e 2,\n    age: (value) =\u003e value \u003e 17,\n  },\n  {\n    firstName: 'John',\n    lastName: 'Maverick',\n    age: 31,\n  },\n)\n```\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-draft%2Freach-schema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopen-draft%2Freach-schema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-draft%2Freach-schema/lists"}