Installations
npm install runtypes
Developer Guide
Typescript
Yes
Module System
CommonJS, ESM
Node Version
20.11.1
NPM Version
10.2.4
Score
100
Supply Chain
99.6
Quality
89.9
Maintenance
100
Vulnerability
100
License
Releases
Contributors
Languages
TypeScript (99.61%)
Just (0.3%)
Shell (0.1%)
Developer
runtypes
Download Statistics
Total Downloads
31,935,716
Last Day
43,313
Last Week
188,421
Last Month
848,042
Last Year
10,730,321
GitHub Statistics
2,627 Stars
685 Commits
89 Forks
13 Watching
4 Branches
42 Contributors
Bundle Size
25.89 kB
Minified
7.12 kB
Minified + Gzipped
Package Meta Information
Latest Version
7.0.2
Package Id
runtypes@7.0.2
Unpacked Size
304.79 kB
Size
46.62 kB
File Count
221
NPM Version
10.2.4
Node Version
20.11.1
Publised On
27 Jan 2025
Total Downloads
Cumulative downloads
Total Downloads
31,935,716
Last day
-1.4%
43,313
Compared to previous day
Last week
-15.7%
188,421
Compared to previous week
Last month
7.3%
848,042
Compared to previous month
Last year
23.4%
10,730,321
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
No dependencies detected.
Runtypes allow you to take values about which you have no assurances and check that they conform to some type A
. This is done by means of composable type validators of primitives, literals, arrays, tuples, objects, unions, intersections and more.
Example
Suppose you have objects which represent asteroids, planets, ships and crew members. In TypeScript, you might write their types like so:
1type Vector = [number, number, number] 2 3type Asteroid = { 4 type: "asteroid" 5 location: Vector 6 mass: number 7} 8 9type Planet = { 10 type: "planet" 11 location: Vector 12 mass: number 13 population: number 14 habitable: boolean 15} 16 17type Rank = "captain" | "first mate" | "officer" | "ensign" 18 19type CrewMember = { 20 name: string 21 age: number 22 rank: Rank 23 home: Planet 24} 25 26type Ship = { 27 type: "ship" 28 location: Vector 29 mass: number 30 name: string 31 crew: CrewMember[] 32} 33 34type SpaceObject = Asteroid | Planet | Ship
If the objects which are supposed to have these shapes are loaded from some external source, perhaps a JSON file, we need to validate that the objects conform to their specifications. We do so by building corresponding Runtype
s in a very straightforward manner:
1import { Boolean, Number, String, Literal, Array, Tuple, Object, Union } from "runtypes" 2 3const Vector = Tuple(Number, Number, Number) 4 5const Asteroid = Object({ 6 type: Literal("asteroid"), 7 location: Vector, 8 mass: Number, 9}) 10 11const Planet = Object({ 12 type: Literal("planet"), 13 location: Vector, 14 mass: Number, 15 population: Number, 16 habitable: Boolean, 17}) 18 19const Rank = Union(Literal("captain"), Literal("first mate"), Literal("officer"), Literal("ensign")) 20 21const CrewMember = Object({ 22 name: String, 23 age: Number, 24 rank: Rank, 25 home: Planet, 26}) 27 28const Ship = Object({ 29 type: Literal("ship"), 30 location: Vector, 31 mass: Number, 32 name: String, 33 crew: Array(CrewMember), 34}) 35 36const SpaceObject = Union(Asteroid, Planet, Ship)
(See the examples directory for an expanded version of this.)
Now if we are given a putative SpaceObject
we can validate it like so:
1// spaceObject: SpaceObject
2const spaceObject = SpaceObject.check(value)
If the object doesn't conform to the type specification, check
will throw an exception.
Error information
When it fails to validate, your runtype emits a ValidationError
object that contains detailed information that describes what's the problem. Following properties are available in the object:
name
: Always"ValidationError"
message
: Astring
that summarizes the problem overallfailure
: AFailure
that describes the problem in a structured way
If you want to inspect why the validation failed, look into the failure
object:
code
: AFailcode
that roughly categorizes the problemmessage
: Astring
that summarizes the problem overallexpected
: The runtype that yielded this failurereceived
: The value that caused this failuredetails
: An object that describes which property was invalid precisely. Only available for complex runtypes (e.g.Object
,Array
, and the like;Union
andIntersect
also emit this enumerating a failure for each member)detail
: An object that describes the failure of the inner runtype. Only available forBrand
and contextual failures (e.g. failures inRecord
keys, in boundaries ofContract
/AsyncContract
, etc.)thrown
: A thrown value, which is typically an error message, if any. Only available for runtypes that involve user-provided validation functions (e.g.Constraint
,Parser
, andInstanceOf
) or constraint-like failures like about the length ofTuple
What shapes of failures there might actually be is documented on the JSDoc comment of each runtype.
If you want to inform your users about the validation error, it's strongly discouraged to rely on the format of the message
property, as it may change across minor versions for readability thoughts. Instead of parsing message
, you should use other properties to handle further tasks such as i18n.
Static type inference
The inferred type of Asteroid
in the above example is a subtype of
1Runtype<{ 2 type: "asteroid" 3 location: [number, number, number] 4 mass: number 5}>
That is, it's a Runtype<Asteroid>
, and you could annotate it as such. But we don't really have to define the Asteroid
type at all now, because the inferred type is correct. Defining each of your types twice, once at the type level and then again at the value level, is a pain and not very DRY. Fortunately you can define a static Asteroid
type which is an alias to the Runtype
-derived type like so:
1type Asteroid = Static<typeof Asteroid>
which achieves the same result as
1type Asteroid = { 2 type: "asteroid" 3 location: [number, number, number] 4 mass: number 5}
Conforming to predefined static type
Instead of getting it to be inferred, you should be able to create a runtype that corresponds to a static type predefined somewhere. In such case you can statically ensure that your runtype conforms to the specification, by using .conform<T>()
:
1type Specification = { 2 foo: string 3 bar?: string 4} 5const Correct = Object({ 6 foo: String, 7 bar: String.optional(), 8}).conform<Specification>() 9// @ts-expect-error: should fail 10const Wrong = Object({ 11 foo: String, 12 bar: String, 13}).conform<Specification>()
The error message on the wrong definition might be verbose like below, but you'll eventually find it contains where is the wrong piece if you scroll down the wall of text.
1The 'this' context of type 'Object<{ foo: String; bar: String; }>' is not assignable to method's 'this' of type 'Conform<Specification>'. 2 Type 'Object<{ foo: String; bar: String; }>' is not assignable to type 'Conformance<Specification>'. 3 Types of property '[RuntypeConformance]' are incompatible. 4 Type '(StaticTypeOfThis: { foo: string; bar: string; }) => { foo: string; bar: string; }' is not assignable to type '(StaticTypeOfThis: Specification) => Specification'. 5 Types of parameters 'StaticTypeOfThis' and 'StaticTypeOfThis' are incompatible. 6 Type 'Specification' is not assignable to type '{ foo: string; bar: string; }'. 7 Property 'bar' is optional in type 'Specification' but required in type '{ foo: string; bar: string; }'.
Guard function
Runtypes provide a guard function as the guard
method:
1const disembark = (value: unknown) => { 2 if (SpaceObject.guard(value)) { 3 // value: SpaceObject 4 if (value.type === "ship") { 5 // value: Ship 6 value.crew = [] 7 } 8 } 9}
Assertion function
Runtypes provide an assertion function as the assert
method:
1const disembark = (value: unknown) => { 2 try { 3 SpaceObject.assert(value) 4 // value: SpaceObject 5 if (value.type === "ship") { 6 // value: Ship 7 value.crew = [] 8 } 9 } catch (error) {} 10}
This might be uncomfortable that TypeScript requires you to manually write the type annotation for your runtype.
Constraint checking
Beyond mere type checking, we can add arbitrary runtime constraints to a Runtype
:
1const PositiveNumber = Number.withConstraint(n => n > 0)
2PositiveNumber.check(-3) // Throws error: Failed constraint check
You can provide more descriptive error messages for failed constraints by returning a string instead of false
:
1const PositiveNumber = Number.withConstraint(n => n > 0 || `${n} is not positive`)
2PositiveNumber.check(-3) // Throws error: -3 is not positive
Narrowing the static type
Constraint checking narrows down the original type to a subtype of it. This should be reflected on the static type. You can pass the desired type as the type argument:
1const TheAnswer = Literal(42) 2const WithConstraint = Number.withConstraint<42>(TheAnswer.guard) 3type WithConstraint = Static<typeof WithConstraint> // 42
Alternatively, you can directly wire up the TypeScript's own facility to narrow down types: guard functions and assertion functions. There're corresponding methods on a runtype, so choose the most concise one:
1const WithGuard = Number.withGuard(TheAnswer.guard) 2type WithGuard = Static<typeof WithGuard> // 42 3const WithAssertion = Number.withAssertion(TheAnswer.assert) 4type WithAssertion = Static<typeof WithAssertion> // 42
If you want to provide custom error messages while narrowing static types, you can throw string
or Error
from a constraint, guard, or assertion function. Actually, returning a string from a function passed to withConstraint
is supported by this exception handling internally.
Too often there might be cases you can't express desired types exactly in TypeScript, such as the type for positive numbers. In such cases you should at least express them as branded types.
1const PositiveNumber = Number.withConstraint(n => n > 0).withBrand("PositiveNumber")
withBrand
modifier is also useful when you want to give your runtype a custom name, which will be used in error messages.
Template literals
The Template
runtype validates that a value is a string that conforms to the template.
You can use the familiar syntax to create a Template
runtype:
1const T = Template`foo${Literal("bar")}baz`
But then the type inference won't work:
1type T = Static<typeof T> // string
Because TS doesn't provide the exact string literal type information (["foo", "baz"]
in this case) to the underlying function. See the issue microsoft/TypeScript#33304, especially this comment microsoft/TypeScript#33304 (comment) we hope to be implemented.
If you want the type inference rather than the tagged syntax, you have to manually write a function call:
1const T = Template(["foo", "baz"] as const, Literal("bar")) 2type T = Static<typeof T> // "foobarbaz"
As a convenient solution for this, it also supports another style of passing arguments:
1const T = Template("foo", Literal("bar"), "baz") 2type T = Static<typeof T> // "foobarbaz"
You can pass various things to the Template
constructor, as long as they are assignable to string | number | bigint | boolean | null | undefined
and the corresponding Runtype
s:
1// Equivalent runtypes 2Template(Literal("42")) 3Template(42) 4Template(Template("42")) 5Template(4, "2") 6Template(Literal(4), "2") 7Template(String.withConstraint(s => s === "42")) 8Template( 9 Intersect( 10 Number.withConstraint(n => n === 42), 11 String.withConstraint(s => s.length === 2), 12 // `Number`s in `Template` accept alternative representations like `"0x2A"`, 13 // thus we have to constraint the length of string, to accept only `"42"` 14 ), 15)
Trivial items such as bare literals, Literal
s, and single-element Union
s and Intersect
s are all coerced into strings at the creation time of the runtype. Additionally, Union
s of such runtypes are converted into RegExp
patterns like (?:foo|bar|...)
, so we can assume Union
of Literal
s is a fully supported runtype in Template
.
Caveats
A Template
internally constructs a RegExp
to parse strings. This can lead to a problem if it contains multiple non-literal runtypes:
1const UpperCaseString = String.withConstraint(s => s === s.toUpperCase(), {
2 name: "UpperCaseString",
3})
4const LowerCaseString = String.withConstraint(s => s === s.toLowerCase(), {
5 name: "LowerCaseString",
6})
7Template(UpperCaseString, LowerCaseString) // DON'T DO THIS!
The only thing we can do for parsing such strings correctly is brute-forcing every single possible combination until it fulfills all the constraints, which must be hardly done. Actually Template
treats String
runtypes as the simplest RegExp
pattern .*
and the “greedy” strategy is always used, that is, the above runtype won't work expectedly because the entire pattern is just ^(.*)(.*)$
and the first .*
always wins. You have to avoid using Constraint
this way, and instead manually parse it using a single Constraint
which covers the entire string.
Variadic tuples
You can spread a Tuple
or an Array
within arguments of Tuple
.
1const T = Tuple(Literal(0), ...Tuple(Literal(1), Literal(2)), Literal(3)) 2type T = Static<typeof T> // [0, 1, 2, 3] 3 4const U = Tuple(Literal(0), ...Array(Literal(1)), Literal(2)) 5type U = Static<typeof U> // [0, ...1[], 2]
Component runtypes are expectedly inferred like this:
1const T = Tuple(Literal(0), ...Tuple(Literal(1), Literal(2)), Literal(3)) 2const literal0: Literal<0> = T.components[0] 3const literal1: Literal<1> = T.components[1] 4const literal2: Literal<2> = T.components[2] 5const literal3: Literal<3> = T.components[3]
Nested spreading is also supported:
1const T = Tuple(Literal(0), ...Tuple(Literal(1), ...Array(Literal(2)), Literal(3)), Literal(4)) 2const leading0: Literal<0> = T.components.leading[0] 3const leading1: Literal<1> = T.components.leading[1] 4const rest: Array<Literal<2>> = T.components.rest 5const trailing0: Literal<3> = T.components.trailing[0] 6const trailing1: Literal<4> = T.components.trailing[1]
instanceof
wrapper
If you have access to the class that you want to test values with the instanceof
operator, then the InstanceOf
runtype is exactly what you're looking for. Usage is straightforward:
1class ObjectId { ... }; 2const ObjectIdChecker = InstanceOf(ObjectId); 3ObjectIdChecker.check(value);
Branded types
Branded types is a way to emphasize the uniqueness of a type. This is useful until we have nominal types:
1const Username = String.withBrand("Username")
2const Password = String.withBrand("Password").withConstraint(
3 str => str.length >= 8 || "Too short password",
4)
5
6const signIn = Contract({
7 receives: Tuple(Username, Password),
8 returns: Unknown,
9}).enforce((username, password) => {
10 /*...*/
11})
12
13const username = Username.check("someone@example.com")
14const password = Password.check("12345678")
15
16// Static type OK, runtime OK
17signIn(username, password)
18
19// Static type ERROR, runtime OK
20signIn(password, username)
21
22// Static type ERROR, runtime OK
23signIn("someone@example.com", "12345678")
Optional properties
Object
runtypes should be able to express optional properties. There's a modifier to do that: .optional()
.
1Object({ x: Number.optional() })
You must be aware of the difference between Object({ x: Union(String, Undefined) })
and Object({ x: String.optional() })
; the former means “x
must be present, and must be string
or undefined
”, while the latter means “x
can be present or absent, but must be string
if present”.
It's strongly discouraged to disable "exactOptionalPropertyTypes"
in the tsconfig; if you do so, the correspondence between runtypes and the inferred static types get lost. We can't respect tsconfig at runtime, so runtypes
always conform the behavior "exactOptionalPropertyTypes": true
, in favor of the expressiveness.
Exact object validation
Object
has a modifier to perform exact object validation: .exact()
.
1const O = Object({ x: Number }).exact() 2O.guard({ x: 42 }) // true 3O.guard({ x: 42, y: 24 }) // false
Note that TypeScript doesn't have exact types at the moment, so it's recommended to wrap your exact Object
runtype within a Brand
to at least prevent the unexpected behavior of the inferred static type:
1const x0 = { x: 42 } 2const x1 = { x: 42, y: 24 } 3 4const O = Object({ x: Number }).exact() 5type O = Static<typeof O> 6const o0: O = x0 7const o1: O = x1 // You would not want this to be possible. 8globalThis.Object.hasOwn(o1, "y") === true 9 10const P = O.withBrand("P") 11type P = Static<typeof P> 12const p0: P = P.check(x0) // Branded types require explicit assertion. 13const p1: P = P.check(x1) // So this won't accidentally pass at runtime.
You should beware that Object
validation only respects enumerable own keys; thus if you want to completely eliminate extra properties that may be non-enumerable or inherited, use parse
method.
Parsing after validation
Every runtype has the withParser
and parse
methods that offer the functionality to transform validated values automatically.
1const O = Object({ x: String.withParser(parseInt).default(42) }) 2type OStatic = Static<typeof O> // { x: string } 3type OParsed = Parsed<typeof O> // { x: number } 4O.parse({ x: "42" }).x === 42
The .default(...)
modifier works the same as .optional()
for mere validation, but for parsing, it works as falling back to the value if the property was absent.
1O.parse({}).x === 42
Extraneous properties are not copied to the resulting value.
1"y" in O.parse({ y: "extra" }) === false
While parse
returns a new value, traditional validation methods such as check
don't change their semantics even with parsers.
1const o: OStatic = { x: "42" } 2o === O.check(o)
Semantics in complex runtypes
In an Object
, an Array
, and a Tuple
, Parser
s will work just as you'd expect.
In a Template
, parsing can work like this:
1const TrueToFalse = Literal("true").withParser(() => "false" as const) 2const Value = Template("value: ", TrueToFalse) 3Value.parse("value: true") === "value: false"
In a Union
, the first succeeding runtype returns a value and further alternatives are not executed at all:
1const Flip = Union( 2 Boolean.withParser(b => !b), 3 Boolean.withParser(b => !!b), 4) 5Flip.parse(true) === false
In an Intersect
, the last runtype returns a value and preceding intersectees are executed but results are just discarded:
1const FlipFlip = Intersect( 2 Boolean.withParser(b => !b), 3 Boolean.withParser(b => !!b), 4) 5FlipFlip.parse(true) === true
Where the members are Object
s, this behavior doesn't match with TypeScript; this is to avoid unsound type inference.
1const A = Object({ n: String.withParser(() => 1 as const) }) 2const B = Object({ n: String.withParser(parseInt) }) 3const AB = A.and(B) 4type A = Parsed<typeof A> 5type B = Parsed<typeof B> 6type AB0 = A & B // { n: 1 } & { n: number } 7type AB1 = Parsed<typeof AB> // { n: number }
Readonly objects and arrays
Array
and Object
runtypes have a special function .asReadonly()
, that returns the same runtype but the static counterpart is readonly.
For example:
1const Asteroid = Object({ 2 type: Literal("asteroid"), 3 location: Vector, 4 mass: Number, 5}).asReadonly() 6type Asteroid = Static<typeof Asteroid> 7// { readonly type: 'asteroid', readonly location: Vector, readonly mass: number } 8 9const AsteroidArray = Array(Asteroid).asReadonly() 10type AsteroidArray = Static<typeof AsteroidArray> 11// readonly Asteroid[]
Helper functions for Object
Object
runtype has the methods .pick()
and .omit()
, which will return a new Object
with or without specified fields (see Example section for detailed definition of Rank
and Planet
):
1const CrewMember = Object({
2 name: String,
3 age: Number,
4 rank: Rank,
5 home: Planet,
6})
7
8const Visitor = CrewMember.pick("name", "home")
9type Visitor = Static<typeof Visitor> // { name: string; home: Planet; }
10
11const Background = CrewMember.omit("name")
12type Background = Static<typeof Background> // { age: number; rank: Rank; home: Planet; }
Also you can use .extend()
to get a new Object
with extended fields:
1const PetMember = CrewMember.extend({ 2 species: String, 3}) 4type PetMember = Static<typeof PetMember> 5// { name: string; age: number; rank: Rank; home: Planet; species: string; }
It is capable of reporting compile-time errors if any field is not assignable to the base runtype. You can suppress this error by using @ts-ignore
directive or .omit()
before, and then you'll get an incompatible version from the base Object
.
1const WrongMember = CrewMember.extend({ 2 rank: Literal("wrong"), 3 // Type '"wrong"' is not assignable to type '"captain" | "first mate" | "officer" | "ensign"'. 4})
Pattern matching
The Union
runtype offers the ability to do type-safe, exhaustive case analysis across its variants using the match
method:
1const isHabitable = SpaceObject.match(
2 asteroid => false,
3 planet => planet.habitable,
4 ship => true,
5)
6
7if (isHabitable(spaceObject)) {
8 // ...
9}
There's also a top-level match
function which allows testing an ad-hoc sequence of runtypes. You should use it along with when
helper function to enable type inference of the parameters of the case functions:
1const makeANumber = match( 2 when(Number, n => n * 3), 3 when(Boolean, b => (b ? 1 : 0)), 4 when(String, s => s.length), 5) 6 7makeANumber(9) // = 27
To allow the function to be applied to anything and then handle match failures, simply use an Unknown
case at the end:
1const makeANumber = match( 2 when(Number, n => n * 3), 3 when(Boolean, b => (b ? 1 : 0)), 4 when(String, s => s.length), 5 when(Unknown, () => 42), 6)
Adding additional properties
You may want to provide additional properties along with your runtype, such as the default value and utility functions. This can be easily achieved by the with
method.
1const Seconds = Number.withBrand("Seconds").with({ 2 toMilliseconds: (seconds: Seconds) => (seconds * 1000) as Milliseconds, 3}) 4type Seconds = Static<typeof Seconds> 5 6const Milliseconds = Number.withBrand("Milliseconds").with({ 7 toSeconds: (milliseconds: Milliseconds) => (milliseconds / 1000) as Seconds, 8}) 9type Milliseconds = Static<typeof Milliseconds>
Sometimes defining additional properties requires access to the original runtype itself statically or dynamically:
1// Bummer, this won't work because of the circular reference. 2const pH = Number.withBrand("pH").with({ default: 7 as pH }) 3type pH = Static<typeof pH>
In such cases, you have to receive the original runtype by passing a function instead:
1const pH = Number.withBrand("pH").with(self => ({ 2 default: 7 as Static<typeof self>, 3})) 4type pH = Static<typeof pH>
Function contracts
Runtypes along with constraint checking are a natural fit for enforcing function contracts. You can construct a contract from runtypes for the parameters and return type of the function:
1const divide = Contract({
2 // This must be a runtype for arrays, just like annotating a rest parameter in TS.
3 receives: Tuple(
4 Number,
5 Number.withConstraint(n => n !== 0 || "division by zero"),
6 ),
7 returns: Number,
8}).enforce((n, m) => n / m)
9
10divide(10, 2) // 5
11
12divide(10, 0) // Throws error: division by zero
Contracts can work with Parser
runtypes:
1const ParseInt = String.withParser(parseInt) 2const contractedFunction = Contract({ 3 receives: Array(ParseInt), 4 returns: Array(ParseInt), 5}).enforce((...args) => args.map(globalThis.String)) 6contractedFunction("42", "24") // [42, 24]
Miscellaneous tips
Annotating runtypes
There might be cases that you have to annotate the type of a runtype itself, not of the checked or parsed value. Basically you should use Runtype.Core
for it to reduce the type inference cost. Runtype.Core
is a slim version of Runtype
; it omits the utility methods that are only used to define a runtype from it.
On the boundaries of a function, the “accept broader, return narrower” principle applies to runtypes of course; it's necessary to use Runtype.Core
in the parameter types, and it's better to use Runtype
in the return type.
When you're to introspect the contents of a variable x
typed as Runtype.Core
, you'd want to narrow it to Runtype.Interfaces
first by Runtype.isRuntype(x)
.
Related libraries
- generate-runtypes Generates runtypes from structured data. Useful for code generators
- json-to-runtypes Generates runtypes by parsing example JSON data
- rest.ts Allows building type safe and runtime-checked APIs
- runtypes-generate Generates random data by
Runtype
for property-based testing - runtyping Generate runtypes from static types & JSON schema
- schemart Generate runtypes from your database schema.
No vulnerabilities found.
No security vulnerabilities found.
Other packages similar to runtypes
@enre/pop-runtypes
runtypes for validating data using simple-runtypes
runtypes-to-jsonschema
convert runtypes schemas to jsonschema
simple-runtypes
I said I want **SIMPLE** runtypes. Just functions that validate and return data. Combine them into complex types and TypeScript knows their structure. That's how runtypes work.
@farfetched-canary/runtypes
Read documentation [here](https://ff.effector.dev/api/contracts/runtypes.html).