Built for compiled TypeScript.
Compiled TypeScript is on its way and we are ready for it
TypeScript is starting to compile ahead of time, to native binaries:
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.
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.
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'}]
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}, …]}
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
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
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
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.
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.
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.
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.
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";
}