simple-runtypes v7.1.3
Preface
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
npm install simple-runtypes or yarn add simple-runtypes
Example
- Define the Runtype:
import * as st from 'simple-runtypes'
const userRuntype = st.record({
id: st.integer(),
name: st.string(),
email: st.optional(st.string()),
})now, ReturnType<typeof userRuntype> is equivalent to
interface {
id: number,
name: string,
email?: string
}- Use the runtype to validate untrusted data
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:
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 `<value>` 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?
Why should I use this over the plethora of other runtype validation libraries available?
- Strict: by default safe against proto injection attacks and unwanted properties
- Fast: check the benchmark
- Friendly: no use of
eval, a small footprint and no dependencies - Flexible: optionally modify the data while it's being checked: trim strings, convert numbers, parse dates
Benchmarks
@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.
Documentation
Intro
A Runtype is a function that:
- receives an unknown value
- returns that value or a copy if all validations pass
- throws a
RuntypeErrorwhen validation fails or returnsValidationResultwhen passed touse
interface Runtype<T> {
(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.
Usage Examples
Strict Property Checks
When using record, any properties which are not defined in the runtype will cause the runtype to fail:
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:
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:
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:
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:
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 dataDiscriminating Unions
simple-runtypes supports Discriminating Unions via the union runtype.
The example found in the TypeScript Handbook translated to simple-runtypes:
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<typeof networkStateRuntype>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:
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 ..."Reference
Basic runtypes that match JavaScript/TypeScript types:
Meta runtypes:
Objects and Array Runtypes:
Combinators:
Shortcuts:
Roadmap / Todos
size- a meta-runtype that imposes a size limit on types, maybe via convert-to-json and .length on the value passed to it- rename
stringLiteralUniontoliteralsorliteralUnionand make it work on all types thatliteralaccepts - 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
simple-runtypesis feature complete because it can- express all TypeScript types
- is extendable with custom runtypes (add documentation)
- add small frontend and backend example projects that show how to use
simple-runtypesin production
- test all types with tsd
- add more combinators: partial, required, get, ...
- separate
RuntypeandInternalRuntypeand type runtype internals (see this comment)
3 years ago
3 years ago
3 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago