Bringing TypeScript to runtimeHigh Perf Validation

Your types are now functionality.
RunTypes generates validation, JSON round trip, mocking and reflection from your types at build time. No schemas to write.

Built for compiled TypeScript.

Just-in-time (JIT) validators like Zod 4 only learn your schema when your app runs, then turn it into a string of code with new Function. An ahead-of-time compiler cannot compile that string, so they drop to a slow path or stop working.

RunTypes reads the shape of your data from your types and compiles its functions at build time, before your app starts.

Built on typescript-go →

JIT validators won't be enough
Ahead-of-time TypeScript compilers can't compile code an app builds from strings while it runs.
compile to a native binary
AOT compilerIn the binary
JIT validators, like Zod 4build their code while the app runs
schema objectz.object({…})
string of code"return …"
made into a functionnew Function(…)
"…"
✕
not compiledslow path, or stops working
RunTypesbuilds its code before the app starts
TypeScript typetype User
compiled at build timecreateValidateFn<User>()
isUser
native codecompiled into the binary
Early TypeScript AOT compilers: scriptc, Porffor, Perry, Bun's AOT mode. All experimental.@mionjs/run-types

One type, many functions.

The whole toolbelt, in one box

Stop gluing many libraries together. Every function RunTypes generates comes from the same type, so they all agree on what your data looks like.

One type in, multiple compiled functions out →

type Order = {
  id: TF.UUIDv4;
  customer: {name: string; email: TF.Email};
  items: {sku: string; qty: number; price: number}[];
  total: number;
  placedAt: Date;
  status: 'pending' | 'paid' | 'shipped';
};

Validation

Like any other validation library, but here TypeScript is the source of truth. RunTypes generates highly optimized functions at build time, so it keeps up with the fastest validators with no runtime dependencies. See the benchmarks →

The same type is also a Standard Schema, the ~standard contract that tRPC, TanStack Form and Router, Hono and more accept directly. One call, no adapter to write.

Type guard and error list

const isOrder = createValidateFn<Order>();
isOrder(order); // true

const orderErrors = createGetValidationErrorsFn<Order>();
orderErrors({...order, total: 'free'}); // [{path: ['total'], expected: 'number'}]

How validation works →

Standard Schema

const orderSchema = createStandardSchema<Order>();

// a Standard Schema v1 object for any tool that supports the spec
orderSchema['~standard'].validate(order); // {value: order}
orderSchema['~standard'].validate({}); // {issues: [{message, path}, …]}

One spec, every framework →

JSON round trip

No hand written coercion, no transforms to keep in sync. It also makes RPC calls in JavaScript just work.

Your type compiles into two functions, one that writes JSON and one that reads it back. Date, URL, bigint, Set, Temporal and more come back as real values.

RunTypes

type Session = {
  user: string;
  expiresAt: Date;
  roles: Set<string>;
};

const toJson = createJsonEncoderFn<Session>();
const fromJson = createJsonDecoderFn<Session>();

const wire = toJson({
  user: 'ada',
  expiresAt: new Date(),
  roles: new Set(['admin']),
})!;
const back = fromJson(wire);

const expiresAt: Date = back.expiresAt; // a real Date again
const roles: Set<string> = back.roles; // a real Set again

Zod (hand maintained coercion and transform)

const sessionSchema = z.object({
  user: z.string(),
  // manually revive the Date
  expiresAt: z.coerce.date(),
  // manual Set revive
  roles: z.array(z.string()).transform((a) => new Set(a)), 
});

// encoding is also yours: JSON.stringify writes a Set as {}
const toJson = (session: Session) =>
  JSON.stringify({...session, roles: [...session.roles]});

const wire = toJson({user: 'ada', expiresAt: new Date(), roles: new Set(['admin'])});
const back = sessionSchema.parse(JSON.parse(wire));

// nothing checks toJson and sessionSchema agree:
// Devs must manually keep the two directions in sync

JSON that keeps your types →

Mocking that conforms to your types

Sample data generated from your type, so every value has the right shape. Great for tests, demos and seeding a database.

const mockOrder = createMockDataFn<Order>();
const fake = mockOrder(); // a valid, randomized Order for your tests

Mock data from your types →

Two ways to describe a shape, one source of truth.

We support native TypeScript types (fastest, zero ceremony) or the RT.* type builders if you like the Zod / TypeBox feel. Both compile to the exact same validator, so pick whichever you fancy and mix them in the same file. And when you only take the type back out of a builder, the schema itself adds nothing to your bundle.

import type * as TF from '@mionjs/run-types/formats';
import {createValidateFn} from '@mionjs/run-types';

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

// a specialized validator, generated at build time
const isUser = createValidateFn<User>();

isUser({id: 1, name: 'Ada', email: '[email protected]', roles: ['admin']}); // true
isUser({id: '1', name: 'Ada'}); // false
import * as TF from '@mionjs/run-types/formats';
import {createValidateFn, type InferType} from '@mionjs/run-types';
import * as RT from '@mionjs/run-types/builders';

const userRunType = RT.object({
  id: TF.number(),
  name: TF.string(),
  email: TF.email(),
  roles: RT.array(RT.union([RT.literal('admin'), RT.literal('user')])),
});

// take the type back out, then generate from it as usual
type User = InferType<typeof userRunType>;
const isUser = createValidateFn<User>();

isUser({id: 1, name: 'Ada', email: '[email protected]', roles: ['admin']}); // true

Types and type builders, side by side →

Formats baked into your types

TypeFormats

Ensure type safety with formats like:
email, uuidv4, ipv4, int32, positive and more.

The validator checks its exact shape, not just its kind. No regex to wire up, no separate schema to keep in sync.

Temporal Support

Full TC39 Temporal (PlainDate, ZonedDateTime, Duration and the rest), with validation and the JSON round trip like any built-in.

Every format you can use →

import type * as TF from '@mionjs/run-types/formats';
import {createValidateFn} from '@mionjs/run-types';

// a format brands a string or number
type Account = {
  id: TF.UUIDv4;
  email: TF.Email;
  ip: TF.IPv4;
  logins: TF.PositiveInt;
};

const isAccount = createValidateFn<Account>();
isAccount({id: 'nope', email: '[email protected]', ip: '10.0.0.1', logins: 3}); // false, id isn't a uuid
import * as TF from '@mionjs/run-types/formats';
import {createValidateFn, type InferType} from '@mionjs/run-types';
import * as RT from '@mionjs/run-types/builders';

const account = RT.object({
  id: TF.uuidv4(),
  email: TF.email(),
  ip: TF.ipv4(),
  logins: TF.positiveInt(),
});

// Recover the TypeScript type from the run-type.
type Account = InferType<typeof account>;

const isAccount = createValidateFn(account);

Your types, available at runtime

import {RunTypeKind} from '@mionjs/run-types';
import {getRunType} from '@mionjs/run-types';

type Order = {
  id: string;
  total: number;
  items: {sku: string; qty: number}[];
};

// the type graph TypeScript erased
const orderRT = getRunType<Order>();

console.log(orderRT.kind === RunTypeKind.objectLiteral); // true
console.log(orderRT.children?.map((prop) => prop.name)); // ['id', 'total', 'items']

Walk your type's structure

Get back a RunType node you can walk, the same one the library uses internally: kind, property names, nested children, format annotations and more. Bring a type or infer it from a value, then read it however you need, to drive codegen, build forms, or power your own tooling.


Reflection you can actually walk →

const order: Order = {id: 'A-1', total: 42, items: [{sku: 'WIDGET', qty: 2}]};

const orderRT = getRunType(order);
console.log(orderRT.children?.map((prop) => prop.name)); // ['id', 'total', 'items']

Infer types from values

You don't have to write the type out. Hand getRunType any value and it reflects that value's static type, so getRunType(order) returns the same node as getRunType<Order>(). Use it when you already hold the data and just want its shape.

High performance validation

Every library says they are the fastest, we back it up!

Our performance matches and surpasses the fastest validators (AJV, TypeBox, Typia) and we have the most comprehensive benchmark suite to back it up.

$ loading benchmarks…

Compile time code generation

Ship only what you use

Generated code is demand-driven and every entry is its own module, so bundlers split and tree-shake natively. A file that only reflects an id ships zero validation code, and the Vite plugin adds zero runtime dependencies.


How RunTypes extends typescript-go →

type User = {
  id: string;
  name: string;
  email: string;
};

const isUser = createValidateFn<User>();
import {__rt_Eq2V_rN5ExvN} from './.mion/types/Eq2V_rN5ExvN.js';

type User = {
  id: string;
  name: string;
  email: string;
};

const isUser = createValidateFn<User>(undefined, undefined, __rt_Eq2V_rN5ExvN);
// shown as a function for clarity, the real emit is a positional
// tuple: faster to initialise, fewer bytes on the wire
export function __rt_Eq2V_rN5ExvN(value) {
  return typeof value === "object" && value !== null &&
  typeof value.id === "string" &&
  typeof value.name === "string" &&
  typeof value.email === "string";
}

 

Copyright © 2026