{"id":15408106,"url":"https://github.com/benmerckx/cito","last_synced_at":"2025-04-18T14:54:54.246Z","repository":{"id":150757535,"uuid":"612150511","full_name":"benmerckx/cito","owner":"benmerckx","description":"🍂 Check types at runtime","archived":false,"fork":false,"pushed_at":"2024-09-24T12:40:08.000Z","size":942,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T06:22:31.208Z","etag":null,"topics":["bun","node","types","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benmerckx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2023-03-10T10:01:58.000Z","updated_at":"2025-02-24T06:39:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"5a7f0cfa-3377-4119-8014-8d4178b034b8","html_url":"https://github.com/benmerckx/cito","commit_stats":{"total_commits":47,"total_committers":1,"mean_commits":47.0,"dds":0.0,"last_synced_commit":"1b565df8e2769a88d36297c1d2813933b6b2ab58"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmerckx%2Fcito","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmerckx%2Fcito/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmerckx%2Fcito/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benmerckx%2Fcito/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benmerckx","download_url":"https://codeload.github.com/benmerckx/cito/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249507518,"owners_count":21283221,"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":["bun","node","types","typescript"],"created_at":"2024-10-01T16:31:30.070Z","updated_at":"2025-04-18T14:54:54.228Z","avatar_url":"https://github.com/benmerckx.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cito\n\nCheck types at runtime\n\n- Small: 1/4 of superstruct, 1/15 of zod\n- Support recursive type declarations\n- Descriptive error messages\n- Full TypeScript support\n- JIT compilation for fast validation checks\n- Only loose assertions: does not warn on extra keys\n\n\u003cpre\u003enpm install \u003ca href=\"https://www.npmjs.com/package/cito\"\u003ecito\u003c/a\u003e\u003c/pre\u003e\n\n## Usage\n\n```ts\nimport {assert, is, object, number, string, array} from 'cito'\n\ntype Post = typeof Post.infer\nconst Post = object({\n  id: number,\n  title: string,\n  link: string.optional,\n  author: object({id: number}),\n  tags: array(string)\n})\n\nconst data = {\n  id: 42,\n  title: 'Hello world',\n  author: {id: 42},\n  tags: ['hello']\n}\n\n// Throws if data is invalid, data is type as Post\nassert(data, Post)\n\n// data is typed as Post in the block, does not throw\nif (is(data, Post)) {\n  // use data\n}\n\n// Assert and name the input data\nconst post = Post(data)\n```\n\n## Defining objects using a class\n\nObject types can be declared using a class, which has the following advantages\nover plain objects:\n\n- **Optional properties**\n\n  It is possible to mark properties as optional which will be reflected in the\n  inferred type:\n\n  ```ts\n  import {object, string} from 'cito'\n  const WithOptional = object(\n    class {\n      required = string\n      present = string.optional\n      optional? = string.optional\n    }\n  )\n  type WithOptional = typeof WithOptional.infer\n  //   ^? {required: string, present: string | undefined, optional?: string | undefined}\n  ```\n\n- **Recursive types**\n\n  Declare recursive types with full type inference without having to resort to\n  manual type definition.\n\n  \u003e _Note:_ recursive types cannot be JIT compiled\n\n  ```ts\n  import {any, object} from 'cito'\n  type Node = typeof Node.infer\n  const Node = object(\n    class Node {\n      next = object(Node)\n      prev = object(Node)\n      data = any\n    }\n  )\n  type List = typeof List.infer\n  const List = object({\n    head: Node.optional\n  })\n  ```\n\n## Api\n\nCito exports the following public members.\n\n```ts\nconst string: Type\u003cstring\u003e\nconst number: Type\u003cnumber\u003e\nconst bigint: Type\u003cbigint\u003e\nconst boolean: Type\u003cboolean\u003e\nconst symbol: Type\u003csymbol\u003e\nconst date: Type\u003cDate\u003e\nconst any: Type\u003cany\u003e\nconst func: Type\u003cFunction\u003e\nfunction literal\u003cT\u003e(value: T): Type\u003cT\u003e\nfunction nullable\u003cT\u003e(inner: Type\u003cT\u003e): Type\u003cT | null\u003e\nfunction optional\u003cT\u003e(inner: Type\u003cT\u003e): Type\u003cT | undefined\u003e\nfunction instance\u003cT\u003e(constructor: new (...args: any[]) =\u003e T): Type\u003cT\u003e\nfunction tuple\u003cT\u003e(...types: T): Type\u003cTuple\u003cT\u003e\u003e\nfunction record\u003cT\u003e(inner: Type\u003cT\u003e): Type\u003cRecord\u003cstring, T\u003e\u003e\nfunction object\u003cT\u003e(definition: T): Type\u003cObject\u003cT\u003e\u003e\nfunction union\u003cT\u003e(...types: T): Type\u003cUnion\u003cT\u003e\u003e\nfunction array\u003cT\u003e(inner: Type\u003cT\u003e): Type\u003cArray\u003cT\u003e\u003e\nfunction enums\u003cT\u003e(types: Record\u003cstring, T\u003e): Type\u003cT\u003e\nfunction lazy\u003cT\u003e(fn: () =\u003e Type\u003cT\u003e): Type\u003cT\u003e\nfunction assert\u003cT\u003e(value: unknown, type: Type\u003cT\u003e): asserts value is T\nfunction is\u003cT\u003e(value: unknown, type: Type\u003cT\u003e): value is T\nfunction compile\u003cT\u003e(type: T): {check: (value) =\u003e value is T}\ntype Infer\u003cT\u003e = T extends Type\u003cinfer U\u003e ? U : ...\n```\n\nA Type has the following api:\n\n```ts\ninterface Validator\u003cT\u003e {\n  (value: any): value is T\n}\n\ninterface Type\u003cT\u003e {\n  // This special property allows you to infer the type `T`\n  infer: T\n\n  // Call the instance to type check a value and return it if valid\n  (value: any): T\n\n  // A type that includes `T` and `null`\n  nullable: Type\u003cT | null\u003e\n\n  // A type that includes `T` and `undefined`\n  optional: Type\u003cT | undefined\u003e\n\n  // Returns a new type that narrows `T` to a subtype `E`\n  narrow\u003cE extends T\u003e(): Type\u003cE\u003e\n\n  // Create a new instance of type `T`, does not type check at runtime\n  new (value: any): T\n\n  // Returns a boolean indicating whether input is of type `T`\n  is(input: any): input is T\n\n  // Returns a new type which validates both `T` and `E`\n  and\u003cE\u003e(validate: Validator\u003cE\u003e): Type\u003cT \u0026 E\u003e\n\n  // Returns a new type which validates either `T` or `E`\n  or\u003cE\u003e(validate: Validator\u003cE\u003e): Type\u003cT | E\u003e\n}\n```\n\nCustom types can be created using the type function:\n\n```ts\nimport {type} from 'cito'\n\nconst regex = type((value): value is RegExp =\u003e value instanceof RegExp)\n\nregex(/(.*?)/g)\nregex('this will throw')\n```\n\n## Benchmarks\n\nMaking the comparison with superstruct and zod:\n\n\u003e The benchmark code is adapted from [typed](https://github.com/brielov/typed/tree/master/benchmark), which is MIT License Copyright (c) 2022 CodBot\n\n```ts\nbenchmark        time (avg)             (min … max)       p75       p99      p995\n--------------------------------------------------- -----------------------------\nzod           68.25 µs/iter     (59.4 µs … 1.19 ms)   66.5 µs  173.3 µs  203.6 µs\nsuperstruct  252.25 µs/iter   (217.9 µs … 779.4 µs)  239.4 µs  513.5 µs  548.3 µs\ncito           21.1 µs/iter    (19.4 µs … 283.9 µs)   20.2 µs   33.8 µs   44.3 µs\n\nsummary for validate\n  cito\n   3.23x faster than zod\n   11.96x faster than superstruct\n\ncito jit     381.57 ns/iter (349.56 ns … 723.29 ns) 378.48 ns 720.73 ns 723.29 ns\ntypebox jit  767.31 ns/iter   (726.39 ns … 1.11 µs) 768.16 ns   1.11 µs   1.11 µs\n\nsummary for validate jit\n  cito jit\n   2.01x faster than typebox jit\n```\n\n\u003e _The data used in the benchmarks is from SpaceX's GraphQL API._\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenmerckx%2Fcito","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenmerckx%2Fcito","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenmerckx%2Fcito/lists"}