{"id":17169013,"url":"https://github.com/sant123/runtypes","last_synced_at":"2025-04-06T04:43:55.361Z","repository":{"id":39846032,"uuid":"443236401","full_name":"sant123/runtypes","owner":"sant123","description":"Deno runtime validation for static types","archived":false,"fork":false,"pushed_at":"2022-05-25T01:07:33.000Z","size":51,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-14T07:17:10.348Z","etag":null,"topics":["deno","runtype","static","typescript"],"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/sant123.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}},"created_at":"2021-12-31T02:47:55.000Z","updated_at":"2022-01-24T16:10:39.000Z","dependencies_parsed_at":"2022-08-23T02:33:00.292Z","dependency_job_id":null,"html_url":"https://github.com/sant123/runtypes","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sant123%2Fruntypes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sant123%2Fruntypes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sant123%2Fruntypes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sant123%2Fruntypes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sant123","download_url":"https://codeload.github.com/sant123/runtypes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247436141,"owners_count":20938532,"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":["deno","runtype","static","typescript"],"created_at":"2024-10-14T23:24:40.364Z","updated_at":"2025-04-06T04:43:55.201Z","avatar_url":"https://github.com/sant123.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Runtypes [![Deno](https://github.com/sant123/runtypes/actions/workflows/deno.yml/badge.svg)](https://github.com/sant123/runtypes/actions/workflows/deno.yml)\n\nThis is a port from https://github.com/pelotom/runtypes for Deno.\n\n## Unsupported features\n\n### Legacy Decorators\n\nSince its use is discouraged as they are incompatible with the future\n[decorators](https://github.com/tc39/proposal-decorators) standard in\nJavaScript.\n\n### Safely bring untyped data into the fold\n\nRuntypes allow you to take values about which you have no assurances and check\nthat they conform to some type `A`. This is done by means of composable type\nvalidators of primitives, literals, arrays, tuples, records, unions,\nintersections and more.\n\n## Example\n\nSuppose you have objects which represent asteroids, planets, ships and crew\nmembers. In TypeScript, you might write their types like so:\n\n```ts\ntype Vector = [number, number, number];\n\ntype Asteroid = {\n  type: \"asteroid\";\n  location: Vector;\n  mass: number;\n};\n\ntype Planet = {\n  type: \"planet\";\n  location: Vector;\n  mass: number;\n  population: number;\n  habitable: boolean;\n};\n\ntype Rank = \"captain\" | \"first mate\" | \"officer\" | \"ensign\";\n\ntype CrewMember = {\n  name: string;\n  age: number;\n  rank: Rank;\n  home: Planet;\n};\n\ntype Ship = {\n  type: \"ship\";\n  location: Vector;\n  mass: number;\n  name: string;\n  crew: CrewMember[];\n};\n\ntype SpaceObject = Asteroid | Planet | Ship;\n```\n\nIf the objects which are supposed to have these shapes are loaded from some\nexternal source, perhaps a JSON file, we need to validate that the objects\nconform to their specifications. We do so by building corresponding `Runtype`s\nin a very straightforward manner:\n\n```ts\nimport {\n  Array,\n  Boolean,\n  Literal,\n  Number,\n  Record,\n  String,\n  Tuple,\n  Union,\n} from \"https://deno.land/x/runtypes/mod.ts\";\n\nconst Vector = Tuple(Number, Number, Number);\n\nconst Asteroid = Record({\n  type: Literal(\"asteroid\"),\n  location: Vector,\n  mass: Number,\n});\n\nconst Planet = Record({\n  type: Literal(\"planet\"),\n  location: Vector,\n  mass: Number,\n  population: Number,\n  habitable: Boolean,\n});\n\nconst Rank = Union(\n  Literal(\"captain\"),\n  Literal(\"first mate\"),\n  Literal(\"officer\"),\n  Literal(\"ensign\"),\n);\n\nconst CrewMember = Record({\n  name: String,\n  age: Number,\n  rank: Rank,\n  home: Planet,\n});\n\nconst Ship = Record({\n  type: Literal(\"ship\"),\n  location: Vector,\n  mass: Number,\n  name: String,\n  crew: Array(CrewMember),\n});\n\nconst SpaceObject = Union(Asteroid, Planet, Ship);\n```\n\n(See the [examples](examples) directory for an expanded version of this.)\n\nNow if we are given a putative `SpaceObject` we can validate it like so:\n\n```ts\n// spaceObject: SpaceObject\nconst spaceObject = SpaceObject.check(obj);\n```\n\nIf the object doesn't conform to the type specification, `check` will throw an\nexception.\n\n## Static type inference\n\nIn TypeScript, the inferred type of `Asteroid` in the above example is\n\n```ts\nRuntype\u003c{\n  type: \"asteroid\";\n  location: [number, number, number];\n  mass: number;\n}\u003e;\n```\n\nThat is, it's a `Runtype\u003cAsteroid\u003e`, and you could annotate it as such. But we\ndon't really have to define the `Asteroid` type in TypeScript at all now,\nbecause the inferred type is correct. Defining each of your types twice, once at\nthe type level and then again at the value level, is a pain and not very\n[DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself). Fortunately you can\ndefine a static `Asteroid` type which is an alias to the `Runtype`-derived type\nlike so:\n\n```ts\nimport type { Static } from \"https://deno.land/x/runtypes/mod.ts\";\n\ntype Asteroid = Static\u003ctypeof Asteroid\u003e;\n```\n\nwhich achieves the same result as\n\n```ts\ntype Asteroid = {\n  type: \"asteroid\";\n  location: [number, number, number];\n  mass: number;\n};\n```\n\n## Type guards\n\nIn addition to providing a `check` method, runtypes can be used as\n[type guards](https://basarat.gitbook.io/typescript/type-system/typeguard):\n\n```ts\nfunction disembark(obj: {}) {\n  if (SpaceObject.guard(obj)) {\n    // obj: SpaceObject\n    if (obj.type === \"ship\") {\n      // obj: Ship\n      obj.crew = [];\n    }\n  }\n}\n```\n\n## Pattern matching\n\nThe `Union` runtype offers the ability to do type-safe, exhaustive case analysis\nacross its variants using the `match` method:\n\n```ts\nconst isHabitable = SpaceObject.match(\n  (asteroid) =\u003e false,\n  (planet) =\u003e planet.habitable,\n  (ship) =\u003e true,\n);\n\nif (isHabitable(spaceObject)) {\n  // ...\n}\n```\n\nThere's also a top-level `match` function which allows testing an ad-hoc\nsequence of runtypes. You should use it along with `when` helper function to\nenable type inference of the parameters of the case functions:\n\n```ts\nconst makeANumber = match(\n  when(Number, (n) =\u003e n * 3),\n  when(Boolean, (b) =\u003e (b ? 1 : 0)),\n  when(String, (s) =\u003e s.length),\n);\n\nmakeANumber(9); // = 27\n```\n\nTo allow the function to be applied to anything and then handle match failures,\nsimply use an `Unknown` case at the end:\n\n```ts\nconst makeANumber = match(\n  when(Number, (n) =\u003e n * 3),\n  when(Boolean, (b) =\u003e (b ? 1 : 0)),\n  when(String, (s) =\u003e s.length),\n  when(Unknown, () =\u003e 42),\n);\n```\n\n## Constraint checking\n\nBeyond mere type checking, we can add arbitrary runtime constraints to a\n`Runtype`:\n\n```ts\nconst Positive = Number.withConstraint((n) =\u003e n \u003e 0);\n\nPositive.check(-3); // Throws error: Failed constraint check\n```\n\nYou can provide more descriptive error messages for failed constraints by\nreturning a string instead of `false`:\n\n```ts\nconst Positive = Number.withConstraint((n) =\u003e n \u003e 0 || `${n} is not positive`);\n\nPositive.check(-3); // Throws error: -3 is not positive\n```\n\nYou can set a custom name for your runtype, which will be used in default error\nmessages and reflection, by using the `name` prop on the optional `options`\nparameter:\n\n```typescript\nconst C = Number.withConstraint((n) =\u003e n \u003e 0, { name: \"PositiveNumber\" });\n```\n\nTo change the type, there are two ways to do it: passing a type guard function\nto a new `Runtype.withGuard()` method, or using the familiar\n`Runtype.withConstraint()` method. (Both methods also accept an `options`\nparameter to optionally set the name.)\n\nUsing a type guard function is the easiest option to change the static type,\nbecause TS will infer the desired type from the return type of the guard\nfunction.\n\n```typescript\n// use Buffer.isBuffer, which is typed as: isBuffer(obj: any): obj is Buffer;\nconst B = Unknown.withGuard(Buffer.isBuffer);\ntype T = Static\u003ctypeof B\u003e; // T is Buffer\n```\n\nHowever, if you want to return a custom error message from your constraint\nfunction, you can't do this with a type guard because these functions can only\nreturn boolean values. Instead, you can roll your own constraint function and\nuse the `withConstraint\u003cT\u003e()` method. Remember to specify the type parameter for\nthe `Constraint` because it can't be inferred from your check function!\n\n```typescript\nconst check = (o: any) =\u003e Buffer.isBuffer(o) || \"Dude, not a Buffer!\";\nconst B = Unknown.withConstraint\u003cBuffer\u003e(check);\ntype T = Static\u003ctypeof B\u003e; // T will have type of `Buffer`\n```\n\nOne important choice when changing `Constraint` static types is choosing the\ncorrect underlying type. The implementation of `Constraint` will validate the\nunderlying type _before_ running your constraint function. So it's important to\nuse a lowest-common-denominator type that will pass validation for all expected\ninputs of your constraint function or type guard. If there's no obvious\nlowest-common-denominator type, you can always use `Unknown` as the underlying\ntype, as shown in the `Buffer` examples above.\n\nSpeaking of base types, if you're using a type guard function and your base type\nis `Unknown`, then there's a convenience runtype `Guard` available, which is a\nshorthand for `Unknown.withGuard`.\n\n```typescript\n// use Buffer.isBuffer, which is typed as: isBuffer(obj: any): obj is Buffer;\nconst B = Guard(Buffer.isBuffer);\ntype T = Static\u003ctypeof B\u003e; // T will have type of `Buffer`\n```\n\n## Template literals\n\nThe `Template` runtype validates that a value is a string that conforms to the\ntemplate.\n\nYou can use the familiar syntax to create a `Template` runtype:\n\n```ts\nconst T = Template`foo${Literal(\"bar\")}baz`;\n```\n\nBut then the type inference won't work:\n\n```ts\ntype T = Static\u003ctypeof T\u003e; // inferred as string\n```\n\nBecause TS doesn't provide the exact string literal type information\n(`[\"foo\", \"baz\"]` in this case) to the underlying function. See the issue\n[microsoft/TypeScript#33304](https://github.com/microsoft/TypeScript/issues/33304),\nespecially this comment\n[microsoft/TypeScript#33304 (comment)](https://github.com/microsoft/TypeScript/issues/33304#issuecomment-697977783)\nwe hope to be implemented.\n\nIf you want the type inference rather than the tagged syntax, you have to\nmanually write a function call:\n\n```ts\nconst T = Template([\"foo\", \"baz\"] as const, Literal(\"bar\"));\ntype T = Static\u003ctypeof T\u003e; // inferred as \"foobarbaz\"\n```\n\nAs a convenient solution for this, it also supports another style of passing\narguments:\n\n```ts\nconst T = Template(\"foo\", Literal(\"bar\"), \"baz\");\ntype T = Static\u003ctypeof T\u003e; // inferred as \"foobarbaz\"\n```\n\nYou can pass various things to the `Template` constructor, as long as they are\nassignable to `string | number | bigint | boolean | null | undefined` and the\ncorresponding `Runtype`s:\n\n```ts\n// Equivalent runtypes\nTemplate(Literal(\"42\"));\nTemplate(42);\nTemplate(Template(\"42\"));\nTemplate(4, \"2\");\nTemplate(Literal(4), \"2\");\nTemplate(String.withConstraint((s) =\u003e s === \"42\"));\nTemplate(\n  Intersect(\n    Number.withConstraint((n) =\u003e n === 42),\n    String.withConstraint((s) =\u003e s.length === 2),\n    // `Number`s in `Template` accept alternative representations like `\"0x2A\"`,\n    // thus we have to constraint the length of string, to accept only `\"42\"`\n  ),\n);\n```\n\nTrivial items such as bare literals, `Literal`s, and single-element `Union`s and\n`Intersect`s are all coerced into strings at the creation time of the runtype.\nAdditionally, `Union`s of such runtypes are converted into `RegExp` patterns\nlike `(?:foo|bar|...)`, so we can assume `Union` of `Literal`s is a fully\nsupported runtype in `Template`.\n\n### Caveats\n\nA `Template` internally constructs a `RegExp` to parse strings. This can lead to\na problem if it contains multiple non-literal runtypes:\n\n```ts\nconst UpperCaseString = Constraint(String, (s) =\u003e s === s.toUpperCase(), {\n  name: \"UpperCaseString\",\n});\nconst LowerCaseString = Constraint(String, (s) =\u003e s === s.toLowerCase(), {\n  name: \"LowerCaseString\",\n});\nTemplate(UpperCaseString, LowerCaseString);\n```\n\nThe only thing we can do for parsing such strings correctly is brute-forcing\nevery single possible combination until it fulfills all the constraints, which\nmust be hardly done. Actually `Template` treats `String` runtypes as the\nsimplest `RegExp` pattern `.*` and the “greedy” strategy is always used, that\nis, the above runtype won't work expectedly because the entire pattern is just\n`^(.*)(.*)$` and the first `.*` always wins. You have to avoid using\n`Constraint` this way, and instead manually parse it using a single `Constraint`\nwhich covers the entire string.\n\n## Function contracts\n\nRuntypes along with constraint checking are a natural fit for enforcing function\ncontracts. You can construct a contract from `Runtype`s for the parameters and\nreturn type of the function:\n\n```ts\nconst divide = Contract(\n  // Parameters:\n  Number,\n  Number.withConstraint((n) =\u003e n !== 0 || \"division by zero\"),\n  // Return type:\n  Number,\n).enforce((n, m) =\u003e n / m);\n\ndivide(10, 2); // 5\n\ndivide(10, 0); // Throws error: division by zero\n```\n\n## Branded types\n\nBranded types is a way to emphasize the uniqueness of a type. This is useful\n[until we have nominal types](https://github.com/microsoft/TypeScript/pull/33038):\n\n```ts\nconst Username = String.withBrand(\"Username\");\nconst Password = String.withBrand(\"Password\").withConstraint(\n  (str) =\u003e str.length \u003e= 8 || \"Too short password\",\n);\n\nconst signIn = Contract(Username, Password, Unknown).enforce(\n  (username, password) =\u003e {\n    /*...*/\n  },\n);\n\nconst username = Username.check(\"someone@example.com\");\nconst password = Password.check(\"12345678\");\n\n// Static type OK, runtime OK\nsignIn(username, password);\n\n// Static type ERROR, runtime OK\nsignIn(password, username);\n\n// Static type ERROR, runtime OK\nsignIn(\"someone@example.com\", \"12345678\");\n```\n\nBranded types are like\n[opaque types](https://flow.org/en/docs/types/opaque-types) and work as\nexpected, except it is impossible to use as a key of an object type:\n\n```ts\nconst StringBranded = String.withBrand(\"StringBranded\");\ntype StringBranded = Static\u003ctypeof StringBranded\u003e;\n// Then the type `StringBranded` is computed as:\n// string \u0026 { [RuntypeName]: \"StringBranded\" }\n\n// TS1023: An index signature parameter type must be either `string` or `number`.\ntype SomeObject1 = { [K: StringBranded]: number };\n\n// Both of these result in empty object type i.e. `{}`\ntype SomeObject2 = { [K in StringBranded]: number };\ntype SomeObject3 = Record\u003cStringBranded, number\u003e;\n\n// You can do like this, but...\nconst key = StringBranded.check(\"key\");\nconst SomeRecord = Record({ [key]: Number });\n// This type results in { [x: string]: number }\ntype SomeRecord = Static\u003ctypeof SomeRecord\u003e;\n\n// So you have to use `Map` to achieve strongly-typed branded keys\ntype SomeMap = Map\u003cStringBranded, number\u003e;\n```\n\n## Optional values\n\nRuntypes can be used to represent a variable that may be undefined.\n\n```ts\n// For variables that might be `string | undefined`\nUnion(String, Undefined);\nString.Or(Undefined); // shorthand syntax for the above\nOptional(String); // equivalent to the above two basically\nString.optional(); // shorthand syntax for the above\n```\n\nThe last syntax is not any shorter than writing `Optional(String)` when you\nimport `Optional` directly from `runtypes`, but if you use scoped import i.e.\n`import * as rt from 'runtypes'`, it would look better to write\n`rt.String.optional()` rather than `rt.Optional(rt.String)`.\n\nIf a `Record` may or may not have some properties, we can declare the optional\nproperties using `Record({ x: Optional(String) })` (or formerly\n`Partial({ x: String })`). Optional properties validate successfully if they are\nabsent or `undefined` or the type specified.\n\n```ts\n// Using `Ship` from above\nconst RegisteredShip = Ship.And(\n  Record({\n    // All registered ships must have this flag\n    isRegistered: Literal(true),\n\n    // We may or may not know the ship's classification\n    shipClass: Optional(Union(Literal(\"military\"), Literal(\"civilian\"))),\n\n    // We may not know the ship's rank (so we allow it to be undefined via `Optional`),\n    // we may also know that a civilian ship doesn't have a rank (e.g. null)\n    rank: Optional(Rank.Or(Null)),\n  }),\n);\n```\n\nThere's a difference between `Union(String, Undefined)` and `Optional(String)`\niff they are used within a `Record`; the former means \"_**it must be present**,\nand must be `string` or `undefined`_\", while the latter means \"_**it can be\npresent or missing**, but must be `string` or `undefined` if present_\".\n\n**_Prior to v5.2, `Union(..., Undefined)` in a `Record` was passing even if the\nproperty was missing. Although some users considered this behavior was a\n[bug](https://github.com/pelotom/runtypes/issues/182) especially for the sake of\nmirroring TS behavior, it was a long-standing thing, and some other users have\nbeen surprised with this fix. So the v5.2 release has been marked\n[deprecated on npm](https://www.npmjs.com/package/runtypes/v/5.2.0), due to the\nbreaking change._**\n\nNote that `null` is a quite different thing than `undefined` in JS and TS, so\n`Optional` doesn't take care of it. If your `Record` has properties which can be\n`null`, then use the `Null` runtype explicitly.\n\n```ts\nconst MilitaryShip = Ship.And(\n  Record({\n    shipClass: Literal(\"military\"),\n\n    // Must NOT be undefined, but can be null\n    lastDeployedTimestamp: Number.Or(Null),\n  }),\n);\n```\n\nYou can save an import by using `nullable` shorthand instead. All three below\nare equivalent things.\n\n```ts\nUnion(Number, Null);\nNumber.Or(Null);\nNumber.nullable();\n```\n\n## Readonly records and arrays\n\nArray and Record runtypes have a special function `.asReadonly()`, that creates\na new runtype where the values are readonly.\n\nFor example:\n\n```typescript\nconst Asteroid = Record({\n  type: Literal(\"asteroid\"),\n  location: Vector,\n  mass: Number,\n}).asReadonly();\ntype Asteroid = Static\u003ctypeof Asteroid\u003e;\n// { readonly type: 'asteroid', readonly location: Vector, readonly mass: number }\n\nconst AsteroidArray = Array(Asteroid).asReadonly();\ntype AsteroidArray = Static\u003ctypeof AsteroidArray\u003e;\n// ReadonlyArray\u003cAsteroid\u003e\n```\n\n## Helper functions for `Record`\n\n`Record` runtype has the methods `.pick()` and `.omit()`, which will return a\nnew `Record` with or without specified fields (see [Example](#example) section\nfor detailed definition of `Rank` and `Planet`):\n\n```ts\nconst CrewMember = Record({\n  name: String,\n  age: Number,\n  rank: Rank,\n  home: Planet,\n});\n\nconst Visitor = CrewMember.pick(\"name\", \"home\");\ntype Visitor = Static\u003ctypeof Visitor\u003e; // { name: string; home: Planet; }\n\nconst Background = CrewMember.omit(\"name\");\ntype Background = Static\u003ctypeof Background\u003e; // { age: number; rank: Rank; home: Planet; }\n```\n\nAlso you can use `.extend()` to get a new `Record` with extended fields:\n\n```ts\nconst PetMember = CrewMember.extend({\n  species: String,\n});\ntype PetMember = Static\u003ctypeof PetMember\u003e;\n// { name: string; age: number; rank: Rank; home: Planet; species: string; }\n```\n\nIt is capable of reporting compile-time errors if any field is not assignable to\nthe base runtype. You can suppress this error by using `@ts-ignore` directive or\n`.omit()` before, and then you'll get an incompatible version from the base\n`Record`.\n\n```ts\nconst WrongMember = CrewMember.extend({\n  rank: Literal(\"wrong\"),\n  // Type '\"wrong\"' is not assignable to type '\"captain\" | \"first mate\" | \"officer\" | \"ensign\"'.\n});\n```\n\n## Related libraries\n\n- [generate-runtypes](https://github.com/cobraz/generate-runtypes#readme)\n  Generates runtypes from structured data. Useful for code generators\n- [json-to-runtypes](https://github.com/runeh/json-to-runtypes#readme) Generates\n  runtypes by parsing example JSON data\n- [rest.ts](https://github.com/hmil/rest.ts) Allows building type safe and\n  runtime-checked APIs\n- [runtypes-generate](https://github.com/typeetfunc/runtypes-generate) Generates\n  random data by `Runtype` for property-based testing\n- [runtyping](https://github.com/johngeorgewright/runtyping) Generate runtypes\n  from static types \u0026 JSON schema\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsant123%2Fruntypes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsant123%2Fruntypes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsant123%2Fruntypes/lists"}