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.
npm install 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.
- Install
- Example
- Why?
- Benchmarks
- Documentation
* Intro
* Usage Examples
+ Strict Property Checks
+ Optional Properties
+ Nesting
+ Discriminating Unions
+ Custom Runtypes
* Reference
* Roadmap / Todos
npm install simple-runtypes or yarn add simple-runtypes
1. Define the Runtype:
``typescript
import * as st from 'simple-runtypes'
const userRuntype = st.record({
id: st.integer(),
name: st.string(),
email: st.optional(st.string()),
})
`
now, ReturnType is equivalent to
`typescript`
interface {
id: number,
name: string,
email?: string
}
2. Use the runtype to validate untrusted data
`typescript
userRuntype({id: 1, name: 'matt'})
// => {id: 1, name: 'matt'}
userRuntype({id: 1, name: 'matt', isAdmin: true})
// throws an st.RuntypeError: "invalid field 'isAdmin' in data"
`
Invoke a runtype with use to get a plain value back instead of throwing errors:
`typescript
st.use(userRuntype, {id: 1, name: 'matt'})
// => {ok: true, result: {id: 1, name: 'matt'}}
st.use(userRuntype, {id: 1, name: 'matt', isAdmin: true})
// => {ok: false, error: FAIL}
st.getFormattedError(FAIL)
// => 'invalid keys in record: ["isAdmin"] at in {"id":1,"name": "matt", ... }'`
Not throwing errors is way more efficient and less obscure.
Throwing errors and catching them outside is more convenient.
Why should I use this over the plethora of other runtype validation libraries available?
1. Strict: by default safe against proto injection attacks and unwanted properties
2. Fast: check the benchmark
3. Friendly: no use of eval, a small footprint and no dependencies
4. Flexible: optionally modify the data while it's being checked: trim strings, convert numbers, parse dates
@moltar has done a great job comparing existing runtime type-checking libraries in moltar/typescript-runtime-type-benchmarks.
@pongo has benchmarked simple-runtypes against io-ts in pongo/benchmark-simple-runtypes.
A Runtype is a function that:
1. receives an unknown value
2. returns that value or a copy if all validations pass
3. throws a RuntypeError when validation fails
or returns ValidationResult when passed to use
`typescript`
interface Runtype
(v: unknown) => T
}
Runtypes are constructed by calling factory functions.
For instance, string creates and returns a string runtype.
Check the factory functions documentation for more details.
#### Strict Property Checks
When using record, any properties which are not defined in the runtype will cause the runtype to fail:
`typescript
const strict = st.record({name: st.string()})
strict({name: 'foo', other: 123})
// => RuntypeError: Unknown attribute 'other'
`
To ignore single properties, use ignore, unknown or any:
`typescript
const strict = st.record({name: st.string(), other: st.ignore()})
strict({name: 'foo', other: 123})
// => {name: foo, other: undefined}
`
Use sloppyRecord to only validate known properties and remove everything else:
`typescript
const sloppy = st.sloppyRecord({name: st.string()})
sloppy({name: 'foo', other: 123, bar: []})
// => {name: foo}
`
Using any of record or sloppyRecord will keep you safe from any __proto__ injection or overriding attempts.
#### Optional Properties
Use the optional runtype to create optional properties:
`typescript`
const squareConfigRuntype = st.record({
color: st.optional(st.string()),
width?: st.optional(st.number()),
})
#### Nesting
Collection runtypes such as record, array, tuple take runtypes as their parameters:
`typescript
const nestedRuntype = st.record({
name: st.string(),
items: st.array(st.record({ id: st.integer, label: st.string() })),
})
nestedRuntype({
name: 'foo',
items: [{ id: 3, label: 'bar' }],
}) // => returns the same data
`
#### Discriminating Unions
simple-runtypes supports Discriminating Unions via the union runtype.
The example found in the TypeScript Handbook translated to simple-runtypes:
`typescript
const networkLoadingState = st.record({
state: st.literal('loading'),
})
const networkFailedState = st.record({
state: st.literal('failed'),
code: st.number(),
})
const networkSuccessState = st.record({
state: st.literal('success'),
response: st.record({
title: st.string(),
duration: st.number(),
summary: st.string(),
})
})
const networdStateRuntype = st.union(
networkLoadingState,
networkFailedState,
networkSuccessState,
)
type NetworkState = ReturnType
`
Finding the runtype to validate a specific discriminating union with is done efficiently with a Map.
#### Custom Runtypes
Write your own runtypes as plain functions, e.g. if you want to turn a string into a BigInt:
`typescript
const bigIntStringRuntype = st.string({match: /^-?[0-9]+n$/})
const bigIntRuntype = st.runtype((v) => {
const stringCheck = st.use(bigIntStringRuntype, v)
if (!stringCheck.ok) {
return stringCheck.error
}
return BigInt(stringCheck.result.slice(0, -1))
})
bigIntRuntype("123n") // => 123n
bigIntRuntype("2.2") // => error: "expected string to match ..."
`
Basic runtypes that match JavaScript/TypeScript types:
- number
- string
- boolean
- null
- undefined
- enum
- literal
Meta runtypes:
- integer
- stringAsInteger
- ignore
- unknown
- any
- json
Objects and Array Runtypes:
- tuple
- array
- record
- optional
- sloppyRecord
- dictionary
Combinators:
- union
- intersection
- omit
- pick
- partial
- TODO: get - similar to Type[key]
Shortcuts:
- size - a meta-runtype that imposes a size limit on types, maybe via convert-to-json and .length on the value passed to itstringLiteralUnion
- rename to literals or literalUnion and make it workliteral
on all types that acceptssimple-runtypes
- rename record to object: #69
- nonStrict modifier instead of sloppy: #68
- improve docs:
- preface: what is a runtype and why is it useful
- why: explain or link to example that shows "strict by default"
- show that is feature complete because it cansimple-runtypes
1. express all TypeScript types
2. is extendable with custom runtypes (add documentation)
- add small frontend and backend example projects that show how to use in productionRuntype
- test all types with tsd
- add more combinators: partial, required, get, ...
- separate and InternalRuntype` and type runtype internals
(see this comment)