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
npm install simple-runtypes
# yarn
yarn add simple-runtypes- 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:
try {
  ... // code that uses runtypes
} catch (e) {
  if (st.isRuntypeError(e)) {
    console.error(getFormattedError(e))
    return
  }
  throw e
}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, and a small footprint with no dependencies - 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:
- 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.
Collection runtypes such as record, array, and 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 dataWhen 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'Using record will keep you safe from any __proto__ injection or overriding attempts.
To ignore individual 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 the optional runtype to create optional properties:
const strict = st.record({
  color: st.optional(st.string()),
  width: st.optional(st.number()),
})Use nonStrict to only validate known properties and remove everything else:
const nonStrictRecord = st.nonStrict(st.record({ name: st.string() }))
nonStrictRecord({ name: 'foo', other: 123, bar: [] })
// => {name: foo}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.
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 ..."Basic runtypes that match JavaScript/TypeScript types:
Meta runtypes:
Objects and Array Runtypes:
Combinators:
Shortcuts:
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
 - 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) 
- check that intersection & union tests do properly test the distribution stuff
 - make getMetadata public
 - maybe make metadata typed and include all options so that you can walk the tree to create testdata orjson-schemas from types
 - maybe add a 
serializefunction to each runtype too? to use instead of JSON.stringify and to provide a full-service library? - maybe make 
anya forbidden type of a runtype