{"id":25728299,"url":"https://github.com/traversable/schema","last_synced_at":"2025-05-07T11:08:34.478Z","repository":{"id":278091982,"uuid":"931580384","full_name":"traversable/schema","owner":"traversable","description":"🍊 Squeeze more juice from your TypeScript schemas","archived":false,"fork":false,"pushed_at":"2025-02-25T03:03:38.000Z","size":395,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-25T04:18:45.861Z","etag":null,"topics":["json-schema","reflection","schema-validation","typelevel-programming","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/traversable.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-12T14:17:08.000Z","updated_at":"2025-02-24T01:25:50.000Z","dependencies_parsed_at":"2025-02-18T00:28:03.504Z","dependency_job_id":"7c9fc795-66e1-40cf-8659-df4904f96484","html_url":"https://github.com/traversable/schema","commit_stats":null,"previous_names":["traversable/schema"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/traversable%2Fschema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/traversable%2Fschema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/traversable%2Fschema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/traversable%2Fschema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/traversable","download_url":"https://codeload.github.com/traversable/schema/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240766608,"owners_count":19854119,"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":["json-schema","reflection","schema-validation","typelevel-programming","typescript"],"created_at":"2025-02-26T00:17:13.172Z","updated_at":"2025-05-07T11:08:34.465Z","avatar_url":"https://github.com/traversable.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr\u003e\n\u003ch1 align=\"center\"\u003eᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮\u003c/h1\u003e\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  A lightweight, modular schema library with opt-in power tools. \n  Extensible in userland via \n  \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only\" target=\"_blank\"\u003eside-effect imports\u003c/a\u003e \n  + \u003ca href=\"https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation\" target=\"_blank\"\u003emodule augmentation\u003c/a\u003e.\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg alt=\"NPM Version\" src=\"https://img.shields.io/npm/v/%40traversable%2Fschema?style=flat-square\u0026logo=npm\u0026label=npm\u0026color=blue\"\u003e\n  \u0026nbsp;\n  \u003cimg alt=\"TypeScript\" src=\"https://img.shields.io/badge/TypeScript-5.5%2B-blue?style=flat-square\u0026logo=TypeScript\u0026logoColor=4a9cf6\"\u003e\n  \u0026nbsp;\n  \u003cimg alt=\"Static Badge\" src=\"https://img.shields.io/badge/license-MIT-a094a2?style=flat-square\"\u003e\n  \u0026nbsp;\n  \u003cimg alt=\"npm\" src=\"https://img.shields.io/npm/dt/@traversable/schema?style=flat-square\"\u003e\n  \u0026nbsp;\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg alt=\"npm bundle size (scoped)\" src=\"https://img.shields.io/bundlephobia/minzip/%40traversable/schema?style=flat-square\u0026label=size\"\u003e\n  \u0026nbsp;\n  \u003cimg alt=\"Static Badge\" src=\"https://img.shields.io/badge/ESM-supported-2d9574?style=flat-square\u0026logo=JavaScript\"\u003e\n  \u0026nbsp;\n  \u003cimg alt=\"Static Badge\" src=\"https://img.shields.io/badge/CJS-supported-2d9574?style=flat-square\u0026logo=Node.JS\"\u003e\n  \u0026nbsp;\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://stackblitz.com/edit/traversable?file=src%2Fsandbox.tsx\" target=\"_blank\"\u003eDemo (StackBlitz)\u003c/a\u003e\n  \u003cspan\u003e\u0026nbsp;\u0026nbsp;•\u0026nbsp;\u0026nbsp;\u003c/span\u003e\n  \u003ca href=\"https://tsplay.dev/w2y29W\" target=\"_blank\"\u003eTypeScript Playground\u003c/a\u003e\n  \u003cspan\u003e\u0026nbsp;\u0026nbsp;•\u0026nbsp;\u0026nbsp;\u003c/span\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@traversable/schema\" target=\"_blank\"\u003enpm\u003c/a\u003e\n  \u003cbr /\u003e\n\u003c/div\u003e\n\u003cbr /\u003e\n\n\u003cbr /\u003e\n\n`@traversable/schema` exploits a TypeScript feature called\n[inferred type predicates](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#inferred-type-predicates)\nto do what libaries like `zod` do, without the additional runtime overhead or abstraction.\n\n\u003e **Note:**\n\u003e\n\u003e These docs are a W.I.P.\n\u003e\n\u003e We recommend jumping straight to the [demo](https://stackblitz.com/edit/traversable?file=src%2Fsandbox.tsx) \n\u003e or [playground](https://tsplay.dev/w2y29W).\n\n## Requirements\n\nThe only hard requirement is [TypeScript 5.5](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/).\nSince the core primitive that `@traversable/schema` is built on top of is\n[inferred type predicates](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#inferred-type-predicates),\nwe do not have plans to backport to previous versions.\n\n## Quick start\n\n```typescript\nimport { t } from '@traversable/schema'\n\ndeclare let ex_01: unknown\n\nif (t.bigint(ex_01)) {\n    ex_01\n    // ^? let ex_01: bigint\n}\n\nconst schema_01 = t.object({\n  abc: t.optional(t.string),\n  def: t.tuple(\n    t.eq(1),\n    t.optional(t.eq(2)), // `t.eq` can be used to match any literal JSON value\n    t.optional(t.eq(3)),\n  )\n})\n\nif (schema_01(ex_01)) {\n    ex_01\n    // ^? let ex_01: { abc?: string, def: [ᵃ: 1, ᵇ?: 2, ᶜ?: 3] }\n    //                                     ^ tuples are labeled to support optionality\n}\n```\n\n\n## Features\n\n`@traversable/schema` is modular by schema (like valibot), but takes it a step further by making its feature set opt-in by default.\n\nThe ability to add features like this is a knock-on effect of traversable's extensible core.\n\n### First-class support for inferred type predicates\n\n\u003e **Note:** This is the only feature on this list that is built into the core library.\n\nThe motivation for creating another schema library was to add native support for inferred type predicates,\nwhich no other schema library currently does (although please file an issue if that has changed!).\n\nThis is possible because the traversable schemas are themselves just type predicates with a few additional properties\nthat allow them to also be used for reflection.\n\n- **Instructions:** To use this feature, define a predicate inline and `@traversable/schema` will figure out the rest.\n\n#### Example\n\nYou can play with this example in the \u003ca href=\"https://tsplay.dev/WkJD2m\" target=\"_blank\"\u003eTypeScript Playground\u003c/a\u003e.\n\n```typescript\nimport { t } from '@traversable/schema'\n\nexport let Classes = t.object({\n  promise: (v) =\u003e v instanceof Promise,\n  set: (v) =\u003e v instanceof Set,\n  map: (v) =\u003e v instanceof Map,\n  weakMap: (v) =\u003e v instanceof WeakMap,\n  date: (v) =\u003e v instanceof Date,\n  regex: (v) =\u003e v instanceof RegExp,\n  error: (v) =\u003e v instanceof Error,\n  typeError: (v) =\u003e v instanceof TypeError,\n  syntaxError: (v) =\u003e v instanceof SyntaxError,\n  buffer: (v) =\u003e v instanceof ArrayBuffer,\n  readableStream: (v) =\u003e v instanceof ReadableStream,\n})\n\ntype Classes = t.typeof\u003ctypeof Classes\u003e\n//   ^? type Classes = {\n//   promise: Promise\u003cany\u003e\n//   set: Set\u003cany\u003e\n//   map: Map\u003cany, any\u003e\n//   weakMap: WeakMap\u003cobject, any\u003e\n//   date: Date\n//   regex: RegExp\n//   error: Error\n//   typeError: TypeError\n//   syntaxError: SyntaxError\n//   buffer: ArrayBuffer\n//   readableStream: ReadableStream\u003cany\u003e\n// }\n\nlet Values = t.object({\n  function: (v) =\u003e typeof v === 'function',\n  successStatus: (v) =\u003e v === 200 || v === 201 || v === 202 || v === 204,\n  clientErrorStatus: (v) =\u003e v === 400 || v === 401 || v === 403 || v === 404,\n  serverErrorStatus: (v) =\u003e v === 500 || v === 502 || v === 503,\n  teapot: (v) =\u003e v === 418,\n  true: (v) =\u003e v === true,\n  false: (v) =\u003e v === false,\n  mixed: (v) =\u003e Array.isArray(v) || v === true,\n  startsWith: (v): v is `bill${string}` =\u003e typeof v === 'string' \u0026\u0026 v.startsWith('bill'),\n  endsWith: (v): v is `${string}murray` =\u003e typeof v === 'string' \u0026\u0026 v.endsWith('murral'),\n})\n\ntype Values = t.typeof\u003ctypeof Values\u003e\n//   ^? type Values = {\n//   function: Function\n//   successStatus: 200 | 201 | 202 | 204\n//   clientErrorStatus: 400 | 401 | 403 | 404\n//   serverErrorStatus: 500 | 502 | 503\n//   teapot: 418\n//   true: true\n//   false: false\n//   mixed: true | any[]\n//   startsWith: `bill${string}`\n//   endsWith: `${string}murray`\n// }\n\nlet Shorthand = t.object({\n    nonnullable: Boolean,\n    unknown: () =\u003e true,\n    never: () =\u003e false,\n})\n\ntype Shorthand = t.typeof\u003ctypeof Shorthand\u003e\n//   ^? type Shorthand = {\n//   nonnullable: {}\n//   unknown: unknown\n//   never?: never\n// }\n```\n\n### `.validate`\n\n`.validate` is similar to `z.safeParse`, except more than an order of magnitude faster*.\n\n- **Instructions:** To install the `.validate` method to all schemas, simply import `@traversable/derive-validators/install`.\n- [ ] TODO: add benchmarks + write-up\n\n#### Example\n\nPlay with this example in the [TypeScript playground](https://tsplay.dev/NaBEPm).\n\n```typescript\nimport { t } from '@traversable/schema'\nimport '@traversable/derive-validators/install'\n//      ↑↑ importing `@traversable/derive-validators/install` adds `.validate` to all schemas\n\nlet schema_01 = t.object({ \n  product: t.object({ \n    x: t.integer, \n    y: t.integer \n  }), \n  sum: t.union(\n    t.tuple(t.eq(0), t.integer), \n    t.tuple(t.eq(1), t.integer),\n  ),\n})\n\nlet result = schema_01.validate({ product: { x: null }, sum: [2, 3.141592]})\n//                     ↑↑ .validate is available\n\nconsole.log(result)\n// =\u003e \n// [\n//   { \"kind\": \"TYPE_MISMATCH\", \"path\": [ \"product\", \"x\" ], \"expected\": \"number\", \"got\": null },\n//   { \"kind\": \"REQUIRED\", \"path\": [ \"product\" ], \"msg\": \"Missing key 'y'\" },\n//   { \"kind\": \"TYPE_MISMATCH\", \"path\": [ \"sum\", 0 ], \"expected\": 0, \"got\": 2 },\n//   { \"kind\": \"TYPE_MISMATCH\", \"path\": [ \"sum\", 1 ], \"expected\": \"number\", \"got\": 3.141592 },\n//   { \"kind\": \"TYPE_MISMATCH\", \"path\": [ \"sum\", 0 ], \"expected\": 1, \"got\": 2 },\n//   { \"kind\": \"TYPE_MISMATCH\", \"path\": [ \"sum\", 1 ], \"expected\": \"number\", \"got\": 3.141592 },\n// ]\n```\n\n### `.toString`\n\nOne of `@traversable/schema`'s primary goals is to remove as much friction from the code generation / metaprogramming workflow\nas possible.\n\nTo support that goal, all schemas shipped by the `@traversable/schema` package come with a `.toString` method that, when called,\nwill return the schema _as code_.\n\nThis is also useful if you're ever in a situation where you're working with generated schemas, and you need to trouble shoot.\n\n\n#### Example\n\n```typescript\nimport { t } from '@traversable/schema'\n\nconst CreateTodoAction = t.object({ type: t.eq('CREATE_TODO') })\nconst DeleteTodoAction = t.object({ type: t.eq('DELETE_TODO'), id: t.integer })\nconst TodoAction = t.union(\n  CreateTodoAction,\n  DeleteTodoAction,\n)\n\nconsole.log(TodoAction + '') \n// =\u003e t.union(t.object({ type: t.eq('CREATE_TODO') }), t.object({ type: t.eq('DELETE_TODO'), id: t.integer }))\n```\n\n\n### `.toType`\n\nThe `.toType` method prints a stringified version of the type that the schema represents.\n\nWorks on both the term- and type-level.\n\n- **Instructions:** To install the `.toType` method on all schemas, simply import `@traversable/schema-to-string/install`.\n\n- Caveat: type-level functionality is provided as a heuristic only; since object keys are unordered in the TS type system, the order that the\nkeys are printed at runtime might differ from the order they appear on the type-level.\n\n#### Example\n\nPlay with this example in the [TypeScript playground](https://tsplay.dev/W49jew)\n\n```typescript\nimport { t } from '@traversable/schema'\nimport '@traversable/schema-to-string/install'\n//      ↑↑ importing `@traversable/schema-to-string/install` adds the upgraded `.toType` method on all schemas\n\nconst schema_02 = t.intersect(\n  t.object({\n    bool: t.optional(t.boolean),\n    nested: t.object({\n      int: t.integer,\n      union: t.union(t.tuple(t.string), t.null),\n    }),\n    key: t.union(t.string, t.symbol, t.number),\n  }),\n  t.object({\n    record: t.record(t.string),\n    maybeArray: t.optional(t.array(t.string)),\n    enum: t.enum('x', 'y', 1, 2, null),\n  }),\n)\n\nlet ex_02 = schema_02.toType()\n//  ^? let ex_02: \"({ \n//       'bool'?: (boolean | undefined), \n//       'nested': { 'int': number, 'union': ([string] | null) }, \n//       'key': (string | symbol | number) } \n//     \u0026 { \n//        'record': Record\u003cstring, string\u003e, \n//        'maybeArray'?: ((string)[] | undefined), \n//        'enum': 'x' | 'y' | 1 | 2 | null \n//     })\"\n```\n\n### `.toJsonSchema`\n\n- **Instructions:** To install the `.toJsonSchema` method on all schemas, simply import `@traversable/schema-to-json-schema/install`.\n\n#### Example\n\nPlay with this example in the [TypeScript playground](https://tsplay.dev/NB98Vw).\n\n```typescript\nimport * as vi from 'vitest'\n\nimport { t } from '@traversable/schema'\nimport '@traversable/schema-to-json-schema/install'\n//      ↑↑ importing `@traversable/schema-to-json-schema/install` adds `.toJsonSchema` on all schemas\n\nconst schema_02 = t.intersect(\n  t.object({\n    stringWithMaxExample: t.optional(t.string.max(255)),\n    nestedObjectExample: t.object({\n      integerExample: t.integer,\n      tupleExample: t.tuple(\n        t.eq(1),\n        t.optional(t.eq(2)),\n        t.optional(t.eq(3)),\n      ),\n    }),\n    stringOrNumberExample: t.union(t.string, t.number),\n  }),\n  t.object({\n    recordExample: t.record(t.string),\n    arrayExample: t.optional(t.array(t.string)),\n    enumExample: t.enum('x', 'y', 1, 2, null),\n  }),\n)\n\nvi.assertType\u003c{\n  allOf: [\n    {\n      type: \"object\"\n      required: (\"nestedObjectExample\" | \"stringOrNumberExample\")[]\n      properties: {\n        stringWithMaxExample: { type: \"string\", minLength: 3 }\n        stringOrNumberExample: { anyOf: [{ type: \"string\" }, { type: \"number\" }] }\n        nestedObjectExample: {\n          type: \"object\"\n          required: (\"integerExample\" | \"tupleExample\")[]\n          properties: {\n            integerExample: { type: \"integer\" }\n            tupleExample: {\n              type: \"array\"\n              minItems: 1\n              maxItems: 3\n              items: [{ const: 1 }, { const: 2 }, { const: 3 }]\n              additionalItems: false\n            }\n          }\n        }\n      }\n    },\n    {\n      type: \"object\"\n      required: (\"recordExample\" | \"enumExample\")[]\n      properties: {\n        recordExample: { type: \"object\", additionalProperties: { type: \"string\" } }\n        arrayExample: { type: \"array\", items: { type: \"string\" } }\n        enumExample: { enum: [\"x\", \"y\", 1, 2, null] }\n      }\n    }\n  ]\n}\u003e(schema_02.toJsonSchema())\n//           ↑↑ importing `@traversable/schema-to-json-schema` installs `.toJsonSchema`\n```\n\n### Codec (`.pipe`, `.extend`, `.parse`, `.decode` \u0026 `.encode`)\n\n- **Instructions:** to install the `.codec` method on all schemas, all you need to do is import `@traversable/derive-codec`.\n  - To create a covariant codec (similar to zod's `.transform`), use `.codec.pipe`\n  - To create a contravariant codec (similar to zod's `.preprocess`), use `.codec.extend` (WIP)\n\n#### Example\n\nPlay with this example in the [TypeScript playground](https://tsplay.dev/mbbv3m).\n\n```typescript\nimport { t } from '@traversable/schema'\nimport '@traversable/derive-codec/install'\n//      ↑↑ importing `@traversable/derive-codec/install` adds `.codec` on all schemas\n\nlet User = t\n  .object({ name: t.optional(t.string), createdAt: t.string })\n  .codec // \u003c-- notice we're pulling off the `.codec` property\n  .pipe((user) =\u003e ({ ...user, createdAt: new Date(user.createdAt) }))\n  .unpipe((user) =\u003e ({ ...user, createdAt: user.createdAt.toISOString() }))\n\nlet fromAPI = User.parse({ name: 'Bill Murray', createdAt: new Date().toISOString() })\n//   ^?  let fromAPI: Error | { name?: string, createdAt: Date}\n\nif (fromAPI instanceof Error) throw fromAPI\nfromAPI\n// ^? { name?: string, createdAt: Date }\n\nlet toAPI = User.encode(fromAPI)\n//  ^? let toAPI: { name?: string, createdAt: string }\n```\n\n## Dependency graph\n\n```mermaid\nflowchart TD\n    registry(registry)\n    json(json) -.-\u003e registry(registry)\n    schema(schema) -.-\u003e registry(registry)\n    derive-codec(derive-codec) -.-\u003e registry(registry)\n    derive-codec(derive-codec) -.-\u003e schema(schema)\n    derive-equals(derive-equals) -.-\u003e json(json)\n    derive-equals(derive-equals) -.-\u003e registry(registry)\n    derive-equals(derive-equals) -.-\u003e schema(schema)\n    derive-validators(derive-validators) -.-\u003e json(json)\n    derive-validators(derive-validators) -.-\u003e registry(registry)\n    derive-validators(derive-validators) -.-\u003e schema(schema)\n    schema-errors(schema-errors) -.-\u003e json(json)\n    schema-errors(schema-errors) -.-\u003e registry(registry)\n    schema-errors(schema-errors) -.-\u003e schema(schema)\n    schema-seed(schema-seed) -.-\u003e json(json)\n    schema-seed(schema-seed) -.-\u003e registry(registry)\n    schema-seed(schema-seed) -.-\u003e schema(schema)\n    schema-to-json-schema(schema-to-json-schema) -.-\u003e registry(registry)\n    schema-to-json-schema(schema-to-json-schema) -.-\u003e schema(schema)\n    schema-to-string(schema-to-string) -.-\u003e registry(registry)\n    schema-to-string(schema-to-string) -.-\u003e schema(schema)\n    schema-valibot-adapter(schema-valibot-adapter) -.-\u003e json(json)\n    schema-valibot-adapter(schema-valibot-adapter) -.-\u003e registry(registry)\n    schema-zod-adapter(schema-zod-adapter) -.-\u003e json(json)\n    schema-zod-adapter(schema-zod-adapter) -.depends on.-\u003e registry(registry)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftraversable%2Fschema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftraversable%2Fschema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftraversable%2Fschema/lists"}