RunTypes

August 17, 2026 · View on GitHub

Compile-time runtime types for TypeScript 7 / typescript-go (tsgo).

TypeScript throws your types away before your code ever runs. RunTypes reads them first, at build time, and hands the runtime back what it lost: validators, JSON and binary (de)serializers, mock data, and reflection.

A native Go binary reaches into the real tsgo type checker and answers call-site type queries; a cross-bundler plugin rewrites each marked call and emits deduplicated, tree-shakeable type-metadata modules. Your types are the schema, so there is no second dialect to learn and nothing to keep in sync.

Why

TypeScript 7 ships the compiler as a compiled Go binary. The legacy custom-transformer hook that runtime-reflection libraries relied on (microsoft/typescript-go#516) was never ported, and the compiler can no longer be monkey-patched from Node. Libraries that patched tsc need a new, native side-channel into the checker.

RunTypes is that channel:

  • Driven by TypeScript, nothing else. The runtime model is exactly what the type system can express. Your types are the schema; there is no parallel schema dialect to learn or keep in sync.
  • Build time, not run time. Every createValidateFn<T>() is a specialized function written out ahead of time. No reflection when your app is live, and no first-call cost.
  • Zero runtime dependencies. The only thing in your bundle is the small @ts-runtypes/core runtime plus the functions you actually call.
  • Native tree-shaking. Every cache entry is its own module, so bundlers code-split and drop what you never use.
  • One type, one id. Two types with the same shape collapse to a single stable id and a single cache entry, so a validator and a serializer generated from the same type can never disagree about what it means.

Use

Install the runtime package and the build-time plugin:

pnpm add @ts-runtypes/core
pnpm add -D @ts-runtypes/devtools

Wire the plugin into your bundler (Vite shown; Rollup, webpack, Rspack, and esbuild are also supported) and point it at your existing tsconfig.json:

import {defineConfig} from 'vite';
import runtypes from '@ts-runtypes/devtools/vite';

export default defineConfig({
  plugins: [runtypes({tsconfig: 'tsconfig.json'})],
});

Then write a normal TypeScript type and ask for a validator. The build generates it:

import {createValidateFn, createGetValidationErrorsFn} from '@ts-runtypes/core';

type User = {
  id: number;
  name: string;
  email: string;
  roles: ('admin' | 'user')[];
};

// A real, specialized function — no schema, no runtime reflection.
const isUser = createValidateFn<User>();

const data: unknown = JSON.parse('{"id":1,"name":"Ada","email":"ada@x.io","roles":["admin"]}');
if (isUser(data)) {
  // data is narrowed to User here.
  console.log(data.name);
}

// Need the reasons, not just a yes/no?
const getUserErrors = createGetValidationErrorsFn<User>();

The same type drives more than validation: JSON and binary serialization (createJsonEncoderFn / createJsonDecoderFn, createBinaryEncoderFn / createBinaryDecoderFn), reflection (getRunTypeId), and realistic mock data (createMockDataFn). See the documentation for the full factory reference.

License

Proprietary — all rights reserved. No use, copying, or distribution without prior written authorization. See LICENSE.

Documentation

Full guides, API reference, suites, and benchmarks live at runtypes.pages.dev.

Working on RunTypes itself:

RunTypes