{"id":15640273,"url":"https://github.com/richardscarrott/ok-computer","last_synced_at":"2025-04-14T03:14:50.587Z","repository":{"id":37033200,"uuid":"378622529","full_name":"richardscarrott/ok-computer","owner":"richardscarrott","description":"λ \"Functions all the way down\" data validation for JavaScript and TypeScript.","archived":false,"fork":false,"pushed_at":"2022-11-09T10:27:47.000Z","size":7798,"stargazers_count":78,"open_issues_count":12,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-27T17:15:26.861Z","etag":null,"topics":["functional","javascript","typescript","validation"],"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/richardscarrott.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-20T10:56:22.000Z","updated_at":"2025-03-06T13:30:51.000Z","dependencies_parsed_at":"2023-01-20T12:05:37.884Z","dependency_job_id":null,"html_url":"https://github.com/richardscarrott/ok-computer","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Fok-computer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Fok-computer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Fok-computer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardscarrott%2Fok-computer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/richardscarrott","download_url":"https://codeload.github.com/richardscarrott/ok-computer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248372698,"owners_count":21093138,"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":["functional","javascript","typescript","validation"],"created_at":"2024-10-03T11:33:32.086Z","updated_at":"2025-04-14T03:14:50.558Z","avatar_url":"https://github.com/richardscarrott.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OK Computer\n\n[![GitHub package.json version](https://img.shields.io/github/package-json/v/richardscarrott/ok-computer.svg)](https://www.npmjs.com/package/ok-computer)\n[![GitHub license](https://github.com/richardscarrott/ok-computer/actions/workflows/node.js.yml/badge.svg)](https://github.com/richardscarrott/ok-computer/actions/workflows/node.js.yml)\n[![GitHub license](https://img.shields.io/github/license/richardscarrott/ok-computer.svg)](https://github.com/richardscarrott/ok-computer/blob/master/LICENSE) [![deno land](http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno\u0026labelColor=black)](https://deno.land/x/ok_computer)\n\nλ \"Functions all the way down\" **data validation for JavaScript and TypeScript**.\n\n🥞 Designed for frontend and backend.\n\n💂 Advanced type inference, type guards and assertion functions.\n\n🗣 First class support for custom error messages / bring your own i18n.\n\n🔌 Don't like something? Need extra functionality? Write a function.\n\n☕ Zero dependencies (it's \u003c 500 lines of code).\n\n📦 Available on [npm](https://www.npmjs.com/package/ok-computer) and [deno.land](https://deno.land/x/ok_computer). Runs anywhere.\n\n![Alt Text](ok-computer-demo.gif)\n\n[Install](#install) | [Example](#example) | [Concepts](#✨-concepts) | [Type Inference](#type-inference) | [API Docs](#api)\n\n## Install\n\n### npm\n\n```\nnpm install ok-computer\n```\n\n### Yarn\n\n```\nyarn add ok-computer\n```\n\n### Deno\n\n```\nimport * as ok from \"https://deno.land/x/ok_computer/ok-computer.ts\";\n```\n\n## Example\n\n[Try on CodeSandbox](https://codesandbox.io/s/ok-computer-forked-yghenq?file=/src/index.ts)\n\n```js\nimport {\n  object,\n  string,\n  or,\n  nullish,\n  and,\n  length,\n  integer,\n  Infer,\n  hasError,\n  assert\n} from 'ok-computer';\n\nconst user = object({\n  firstName: string,\n  lastName: or(nullish, string),\n  picture: object({\n    url: and(string, length(1, 255)),\n    width: integer\n  })\n});\n\ntype User = Infer\u003ctypeof user\u003e;\n// {\n//   firstName: string;\n//   lastName?: string | null | undefined;\n//   picture: {\n//     url: string;\n//     width: number;\n//   };\n// };\n\nconst value: unknown = { lastName: 44, picture: {} };\n\nconst errors = user(value);\n// {\n//   firstName: 'Expected typeof string',\n//   lastName: '(Expected nullish or expected typeof string)',\n//   picture: {\n//     url: '(Expected typeof string and expected length between 1 and 255)',\n//     width: 'Expected integer'\n//   }\n// };\n\nhasError(errors); // true\n\nassert(value, user); // throw new AssertError('Invalid: first of 4 errors: firstName: Expected typeof string')\ntypeof value; // User\n```\n\n## ✨ Concepts\n\nEverything in OK Computer is a validation function, also known as a \"validator\".\n\n```ts\ntype Validator\u003cValidType, Err = unknown\u003e = (value: unknown) =\u003e Err | undefined;\n```\n\nA validator has 3 rules:\n\n1. **Returns `undefined` if the value is _valid_**\n\n2. **Returns an error (_anything_ other than `undefined`) if the value is _invalid_**\n\n3. **Returns an error if the value is `Symbol.for('ok-computer.introspect')`**\n\n```ts\nconst fortyFour: Validator\u003cnumber, string\u003e = (value) =\u003e\n  value !== 44 ? 'Expected the number 44' : undefined;\n\nfortyFour(44); // undefined\nfortyFour(43); // 'Expected the number 44'\nfortyFour(Symbol.for('ok-computer.introspect')); // 'Expected the number 44'\n```\n\nAll built-in validators work in this way, for example this is how `string` is implemented.\n\n```ts\nconst string: Validator\u003cstring, string\u003e = (value) =\u003e\n  typeof value !== 'string' ? 'Expected string' : undefined;\n\nstring('cat'); // undefined\nstring(10); // 'Expected string'\nstring(Symbol.for('ok-computer.introspect')); // 'Expected string'\n```\n\nThe above validators implicitly handle rule 3 due to the nature of the validation logic. In some cases you need to explicitly handle it.\n\n```ts\n// 🚨 Bad\nconst symbol: Validator\u003csymbol, string\u003e = (value) =\u003e\n  typeof value !== 'symbol' ? 'Expected symbol' : undefined;\n\nsymbol(Symbol.for('cat')); // undefined ✅\nsymbol('cat'); // 'Expected symbol' ✅\nsymbol(Symbol.for('ok-computer.introspect')); // undefined ❌\n```\n\n```ts\n// 👌 Better\nconst symbol: Validator\u003csymbol, string\u003e = (value) =\u003e\n  typeof value !== 'symbol' || value === Symbol.for('ok-computer.introspect')\n    ? 'Expected symbol'\n    : undefined;\n\nsymbol(Symbol.for('cat')); // undefined ✅\nsymbol('cat'); // 'Expected symbol' ✅\nsymbol(Symbol.for('ok-computer.introspect')); // 'Expected symbol' ✅\n```\n\n```ts\n// 👍 Best\nimport { create } from 'ok-computer';\n\nconst symbol = create\u003csymbol\u003e((value) =\u003e typeof value === 'symbol')(\n  'Expected symbol'\n);\n\nsymbol(Symbol.for('cat')); // undefined ✅\nsymbol('cat'); // 'Expected symbol' ✅\nsymbol(Symbol.for('ok-computer.introspect')); // 'Expected symbol' ✅\n```\n\n\u003e NOTE: It's recommended to use `create` for all custom validators.\n\nErrors don't have to be string values, as per rule 2 **an error can be _anything_ other than `undefined`**. So yes, this means `''`, `0`, `null` and `false` are all considered to be an error.\n\n```ts\nimport { create } from 'ok-computer';\n\nconst string = create\u003cstring\u003e((value) =\u003e typeof value === 'string')(\n  new Error('Expected string')\n);\nstring('cat'); // undefined\nstring(44); // new Error('Expected string')\n\nconst number = create\u003cnumber\u003e((value) =\u003e typeof value === 'number')(false);\nnumber(44); // undefined\nnumber('cat'); // false\n\nconst never = create\u003cnever\u003e((value) =\u003e false)(0);\nnever('cat'); // 0\nnever(44); // 0\n\nconst always = create((value) =\u003e true)({ id: 'foo.bar' });\nalways('cat'); // undefined\nalways(44); // undefined\nalways(Symbol.for('ok-computer.introspect')); // { id: 'foo.bar' }\n```\n\nSo far so good, however nothing particularly useful is going on as you don't need a library to write a function which conditionally returns undefined.\n\nThe real utility comes from [higher order validators](https://en.wikipedia.org/wiki/Higher-order_function) which accept arguments (in many cases arguments are themselves validators) and return new validators, allowing you to compose simple validators into more complex logic.\n\n```ts\nimport { length } from 'ok-computer';\n\nconst length3 = length(3);\n\nlength3('cat'); // undefined\nlength3([1, 2, 3]); // undefined\nlength3('catamaran'); // 'Expected length 3'\nlength3([1, 2]); // 'Expected length 3'\n```\n\n```ts\nimport { length, string, and } from 'ok-computer';\n\nconst name = and(string, length(3));\n\nname('cat'); // undefined\nname([1, 2, 3]); // (Expected typeof string and expected length 3)\nname('catamaran'); // (Expected typeof string and expected length 3)\n```\n\n```ts\nimport {\n  length,\n  string,\n  and,\n  or,\n  nullish,\n  pattern,\n  not,\n  oneOf\n} from 'ok-computer';\n\nconst username = or(\n  nullish,\n  and(\n    string,\n    length(4, 30),\n    pattern(/^[\\w\\.]*$/),\n    not(oneOf('lewis.hamilton', 'kanye.west'))\n  )\n);\n\nusername('catamaran'); // undefined\nusername(null); // undefined\nusername('cat'); // (Expected nullish or (Expected typeof string and expected length between 4 and 30 and expected to match pattern /^[\\\\w\\\\.]*$/ and not(\"Expected one of lewis.hamilton, kanye.west\")))\nusername('lewis.hamilton'); // (Expected nullish or (Expected typeof string and expected length between 4 and 30 and expected to match pattern /^[\\\\w\\\\.]*$/ and not(\"Expected one of lewis.hamilton, kanye.west\")))\n```\n\nYou can implement your own higher order validators in the same way.\n\n```ts\nimport { create } from 'ok-computer';\n\nconst endsWith = (suffix: string) =\u003e\n  create\u003cstring\u003e(\n    (value) =\u003e typeof value === 'string' \u0026\u0026 value.endsWith(suffix)\n  )(`Expected string to end with \"${suffix}\"`);\n\nconst jpeg = endsWith('.jpeg');\n\njpeg('cat.jpeg'); // undefined\njpeg('cat.png'); // 'Expected string to end with \".jpeg\"'\n```\n\nSome commonly used higher order validators return structural data which, like `undefined`, can also be considered valid.\n\n```ts\nimport { object, string } from 'ok-computer';\n\nconst user = object({\n  name: string\n});\n\nuser({ name: 'Hamilton' }); // { name: undefined, [Symbol('ok-computer.structure')]: true }\nuser({ name: 44 }); // { name: 'Expected typeof string', [Symbol('ok-computer.structure')]: true }\n```\n\n```ts\nimport { array, string } from 'ok-computer';\n\nconst names = array(string);\n\nnames(['Hamilton']); // Array { 0: undefined, [Symbol('ok-computer.structure')]: true }\nnames(['Hamilton', 44]); // Array { 0: undefined, 1: 'Expected typeof string', [Symbol('ok-computer.structure')]: true }\n```\n\nThis exposes a richer interface to consume more complex validation errors. The tradeoff being you can't simply check if the error is `undefined` to determine if it's valid. Instead you must use a dedicated `isError` function.\n\n```js\nimport { object, string, isError } from 'ok-computer';\n\nconst user = object({\n  name: string\n});\n\nconst error = user({ name: 'Hamilton' }); // { name: undefined, [Symbol('ok-computer.structure')]: true }\nisError(error); // false\n```\n\nThere are a number of other functions to help consume errors.\n\n```js\nimport {\n  object,\n  string,\n  isError,\n  hasError,\n  listErrors,\n  okay,\n  assert\n} from 'ok-computer';\n\nconst user = object({\n  firstName: string,\n  lastName: string\n});\n\nconst value: unknown = { firstName: 44 };\n\nconst error = user(value); // { firstName: 'Expected typeof string', lastName: 'Expected typeof string', [Symbol('ok-computer.structure')]: true }\n\nisError(error); // true\n\nhasError(error); // true (alias for `isError`)\n\nlistErrors(error); // [{ path: 'firstName', err: 'Expected typeof string' }, { path: 'lastName', err: 'Expected typeof string' }]\n\nif (okay(value, user)) {\n  typeof value; // { firstName: string; lastName: string }\n}\n\nassert(value, user); // throw new AssertError(`Invalid: first of 2 errors: firstName: Expected typeof string`)\ntypeof value; // { firstName: string; lastName: string }\n```\n\nSometimes validation depends on sibling values. By convention all validators receive parent values as subsequent arguments.\n\n```js\nimport { object, string, create } from 'ok-computer';\n\nconst user = object({\n  password: string,\n  repeatPassword: create((value, parent) =\u003e value === parent.password)(\n    'Expected to match password'\n  ),\n  nested: object({\n    repeatPassword: create(\n      (value, parent, grandParent) =\u003e value === grandParent.password\n    )('Expected to match password')\n  })\n});\n```\n\nAlthough all out-the-box validators return pre-baked errors, you can override them with the `err` higher order validator.\n\n```ts\nimport { err, string } from 'ok-computer';\n\nstring(10); // 'Expected typeof string'\n\nconst str = err(string, 'No really, I expected a string');\nstr(10); // 'No really, I expected a string'\n```\n\n```ts\nimport { err, nullish, string, or } from 'ok-computer';\n\nconst firstName = or(nullish, string);\nfirstName(10); // ORError(['Expected nullish', 'Expected typeof string'])\n\nconst forename = err(or(nullish, string), 'Expected nullish or string');\nforename(10); // 'Expected nullish or string'\n\nconst vorname = err(or(nullish, string), 'Null oder Zeichenfolge erwartet');\nvorname(10); // 'Null oder Zeichenfolge erwartet'\n```\n\nMany errors returned from higher order validators such as `ORError`, `ANDError`, `XORError`, `PeerError` and `NegateError` serialize into string errors when possible.\n\n```ts\nimport { nullish, string, or } from 'ok-computer';\n\nconst firstName = or(nullish, string);\n\nconst err = firstName(44); // ORError(['Expected nullish', 'Expected typeof string'])\nJSON.stringify(err); // '(Expected nullish or expected typeof string)'\n```\n\n```ts\nimport { nullish, string, or, and, minLength } from 'ok-computer';\n\nconst firstName = or(nullish, and(string, minLength(1)));\nconst err = firstName(44); // ORError(['Expected nullish', ANDError(['Expected typeof string', 'Expected min length 1'])])\n\nJSON.stringify(err); // '(Expected nullish or (Expected typeof string and expected min length 1))'\n```\n\n```ts\nimport { nullish, string, or, object } from 'ok-computer';\n\nconst firstName = or(nullish, object({ name: string }));\nconst err = firstName(44);\n// ORError([\n//   'Expected nullish',\n//   {\n//     name: 'Expected typeof string',\n//     [Symbol('ok-computer.object-root')]: 'Expected object',\n//     [Symbol('ok-computer.structure')]: true\n//   }\n// ])\n\n// NOTE: Cannot be serialized into string\nJSON.stringify(err); // { type: 'ORError', operator: 'OR', errors: ['Expected nullish', { name: 'Expected typeof string' }] }\n```\n\n## Type Inference\n\nOK Computer offers the ability to automatically infer a type from any given validator.\n\n```ts\nimport { and, string, length, okay, assert } from 'ok-computer';\n\nconst validator = and(string, length(3));\n\ntype Type = Infer\u003ctypeof validator\u003e; // string\n\nconst value: unknown = 'foo';\n\nif (okay(value, validator)) {\n  typeof value; // string\n}\n\nassert(value, validator); // throw new AssertError('Invalid: first of 1 error: (Expected typeof string and expected length 3)')\ntypeof value; // string\n```\n\n```ts\nimport { object, string, or, nullish, Infer, okay, assert } from 'ok-computer';\n\nconst user = object({\n  firstName: string,\n  lastName: or(nullish, string)\n});\n\ntype User = Infer\u003ctypeof user\u003e;\n// { firstName: string; lastName?: string | null | undefined; }\n\nconst value: unknown = {};\n\nif (okay(value, user)) {\n  typeof value; // User\n}\n\nassert(value, user); // throw new AssertError('Invalid: first of 4 errors: firstName: (Expected typeof string and expected length 3)')\ntypeof value; // User\n```\n\nWhen creating your own validators, you can define an appropriate valid type up front.\n\n```ts\nimport { create, Infer } from 'ok-computer';\n\nconst fortyFour = create\u003c44\u003e((value) =\u003e value === 44)('Expected 44');\n\ntype FortyFour = Infer\u003ctypeof fortyFour\u003e; // 44\n\nconst thirtyThree: Validator\u003c33, string\u003e = (value) =\u003e\n  value !== 33 ? 'Expected 33' : undefined;\n\ntype ThirtyThree = Infer\u003ctypeof thirtyThree\u003e; // 33\n```\n\nHowever you can override the valid type on any validator using `annotate`.\n\n```ts\nimport { annotate, and, integer, min, max } from 'ok-computer';\n\nconst num = annotate\u003c1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10\u003e()(\n  and(integer, min(1), max(10))\n);\n\ntype Num = Infer\u003ctypeof num\u003e; // 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10\n```\n\n\u003e NOTE: The double fn signature is required to ensure the error type can continue to be inferred. https://medium.com/@nandin-borjigin/partial-type-argument-inference-in-typescript-and-workarounds-for-it-d7c772788b2e\n\n### Type-first validator\n\nIn cases where you already have a type or interface defined, you can ensure the validator adheres to the type.\n\n```ts\nimport { object, string, or, undef, Validator, ExtractErr } from 'ok-computer';\n\ninterface User {\n  readonly firstName: string;\n  readonly lastName?: string;\n}\n\nconst _user = object({\n  firstName: string,\n  lastName: or(undef, string)\n});\n\nconst user: Validator\u003cUser, ExtractErr\u003ctypeof _user\u003e\u003e = _user;\n```\n\n## API\n\nComing soon... for now you can:\n\n1.  [Discover the full API using TypeScript](https://codesandbox.io/s/ok-computer-forked-yghenq?file=/src/index.ts) (TIP: `import * as ok from 'ok-computer'`)\n2.  [Browse the source](https://github.com/richardscarrott/ok-computer/blob/master/src/ok-computer.ts) (there isn't much code)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardscarrott%2Fok-computer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichardscarrott%2Fok-computer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardscarrott%2Fok-computer/lists"}