@mionkit/run-types is a powerful JIT (Just-In-Time) compilation library that generates optimized validation, serialization, and mocking functions directly from TypeScript types. Unlike schema-based libraries like Zod or AJV, run-types leverages TypeScript's type system at compile time to generate highly efficient runtime code.
createIsTypeFn<T>()Returns a function that checks if a value matches the type. Returns true or false.
import {createIsTypeFn} from '@mionkit/run-types';
interface User {
name: string;
age: number;
}
async function example() {
const isUser = await createIsTypeFn<User>();
isUser({name: 'John', age: 30}); // true
isUser({name: 'John'}); // false (missing age)
isUser({name: 'John', age: '30'}); // false (age is string)
}
createTypeErrorsFn<T>()Returns a function that returns detailed error information when validation fails.
import {createTypeErrorsFn} from '@mionkit/run-types';
interface User {
name: string;
age: number;
}
async function example() {
const getUserErrors = await createTypeErrorsFn<User>();
const errors = getUserErrors({name: 123, age: 'invalid'});
// Returns: [
// { path: ['name'], expected: 'string', actual: 'number' },
// { path: ['age'], expected: 'number', actual: 'string' }
// ]
}
createPrepareForJsonFn<T>()Converts JavaScript values to JSON-compatible format. Handles special types like Date, BigInt, Map, Set.
import {createPrepareForJsonFn} from '@mionkit/run-types';
interface Event {
name: string;
timestamp: Date;
metadata: Map<string, any>;
}
async function example() {
const prepareEvent = await createPrepareForJsonFn<Event>();
const event = {
name: 'Click',
timestamp: new Date('2025-01-15'),
metadata: new Map([['source', 'web']]),
};
const jsonReady = prepareEvent(event);
// { name: 'Click', timestamp: '2025-01-15T00:00:00.000Z', metadata: [['source', 'web']] }
JSON.stringify(jsonReady); // Now works correctly!
}
prepareForJson mutates the original object for performance. This avoids creating unnecessary copies in request/response pipelines.
createStringifyJsonFn instead — it does not mutate the input.createRestoreFromJsonFn<T>()Restores JavaScript types from JSON-parsed data. The inverse of prepareForJson.
import {createRestoreFromJsonFn} from '@mionkit/run-types';
interface Event {
name: string;
timestamp: Date;
metadata: Map<string, any>;
}
async function example() {
const restoreEvent = await createRestoreFromJsonFn<Event>();
const jsonString = '{"name":"Click","timestamp":"2025-01-15T00:00:00.000Z","metadata":[["source","web"]]}';
const parsed = JSON.parse(jsonString);
const event = restoreEvent(parsed);
// event.timestamp is now a Date object
// event.metadata is now a Map
}
createStringifyJsonFn<T>()Directly parses types into JSON strings, does not modify the original object.
import {createStringifyJsonFn} from '@mionkit/run-types';
interface Event {
name: string;
timestamp: Date;
metadata: Map<string, any>;
}
async function example() {
const event = {
name: 'Click',
timestamp: new Date('2025-01-15'),
metadata: new Map([['source', 'web']]),
};
const stringifyEvent = await createStringifyJsonFn<Event>();
const jsonString = stringifyEvent(event);
// Equivalent to: JSON.stringify(prepareForJson(event)) but faster
}
createStringifyJsonFn uses javascript to traverse the objects and convert them into JSON string. It does not use JSON.stringify directly!
prepareForJson => JSON.stringify but does not mutate the input.For performance-critical scenarios, binary serialization provides compact encoding.
createToBinaryFn<T>()Serializes a value to a compact binary format (Uint8Array).
async function toBinaryExample() {
const toBinary = await createToBinaryFn<User>();
const buffer = toBinary({name: 'John', age: 30});
// Returns Uint8Array with optimized binary encoding
}
createFromBinaryFn<T>()Deserializes a binary buffer back to the original type.
async function fromBinaryExample() {
const fromBinary = await createFromBinaryFn<User>();
const buffer = new Uint8Array(); // from previous example
const user = fromBinary(buffer);
// user is now { name: 'John', age: 30 }
}
createMockTypeFn<T>()Generates valid mock data for any type. Perfect for testing.
interface User {
id: string;
name: string;
email: string;
age: number;
createdAt: Date;
}
async function basicMockExample() {
const mockUser = await createMockTypeFn<User>();
const user = mockUser();
// {
// id: 'abc123xyz',
// name: 'mockString',
// email: 'test@example.com',
// age: 42,
// createdAt: Date('2025-01-15T12:00:00.000Z')
// }
}
When using Type Formats, mock data respects format constraints:
import {StrEmail} from '@mionkit/type-formats/FormatsString';
import {NumPositiveInt} from '@mionkit/type-formats/FormatsNumber';
interface ValidatedUser {
email: StrEmail;
followersCount: NumPositiveInt;
}
async function formatsMockExample() {
const mockValidatedUser = await createMockTypeFn<ValidatedUser>();
const user = mockValidatedUser();
// { email: 'user@example.com', followersCount: 150 }
}
import {
createIsTypeFn,
createTypeErrorsFn,
createStringifyJsonFn,
createRestoreFromJsonFn,
createMockTypeFn,
} from '@mionkit/run-types';
interface BlogPost {
id: string;
title: string;
content: string;
author: {
name: string;
email: string;
};
tags: string[];
publishedAt: Date;
metadata: Map<string, any>;
}
async function completeExample() {
// Create all needed functions
const isPost = await createIsTypeFn<BlogPost>();
const getErrors = await createTypeErrorsFn<BlogPost>();
const stringify = await createStringifyJsonFn<BlogPost>();
const restore = await createRestoreFromJsonFn<BlogPost>();
const mockPost = await createMockTypeFn<BlogPost>();
// Generate mock data
const post = mockPost();
// Validate
if (isPost(post)) {
// Serialize to JSON (does not mutate original)
const json = stringify(post);
// Deserialize
const parsed = JSON.parse(json);
const restored = restore(parsed);
// restored.publishedAt is a Date
// restored.metadata is a Map
} else {
const errors = getErrors(post);
console.log('Validation failed:', errors);
}
}
For advanced use cases, you can access the underlying RunType instance directly.
runType<T>()Creates a RunType instance from any TypeScript type. Provides access to type metadata and low-level operations.
function runTypeExample() {
const userRunType = runType<User>();
// Access type metadata, children, etc.
}
reflectFunction<Fn>()Reflects a function to get type information about its parameters and return type.
function createUser(name: string, age: number): User {
return {id: '123', name, createdAt: new Date()};
}
function reflectFunctionExample() {
const fnReflection = reflectFunction(createUser);
// Access parameter types, return type, etc.
}
any TypeWhen an any type is encountered during serialization, run-types uses a best-effort approach:
JSON Serialization:
JSON.stringify to serialize the valueJSON.parse to deserialize the valueBinary Serialization:
JSON.stringifyJSON.parse is used to restore the valueimport {createToBinaryFn, createFromBinaryFn} from '@mionkit/run-types';
type FlexibleData = {
id: number;
payload: any; // Can contain any JSON-compatible value
};
async function anyTypeExample() {
const toBinary = await createToBinaryFn<FlexibleData>();
const fromBinary = await createFromBinaryFn<FlexibleData>();
const data: FlexibleData = {
id: 1,
payload: {nested: {deeply: [1, 2, 3]}, flag: true},
};
const binary = toBinary(data);
const restored = fromBinary(binary);
// restored.payload is parsed back from JSON
}
any type has performance implications since JSON serialization is less efficient than typed binary encoding. When possible, define specific types for better performance and type safety.Union types allow a value to be one of several possible types. Run-types provides full support for union validation and serialization.
any or unknown types are not allowed in unions, ie: any | User will alway match as any and it is not practical.When validating a union type, run-types checks each member of the union in declaration order using a first-match strategy. The value is tested against each type until a match is found:
type MyUnion = string | number | boolean;
// Validation order:
// 1. Check if value is string → matches "hello"
// 2. Check if value is number → matches 42
// 3. Check if value is boolean → matches true
For object types in unions, run-types uses loose matching:
type Cat = {name: string; meow?: true};
type Dog = {name: string; bark?: true};
type Pet = Cat | Dog;
// {name: 'Fluffy', meow: true} matches Cat first
// {name: 'Rex', bark: true} matches Dog first
// {name: 'Unknown', age: 5} matches Cat (extra 'age' is allowed)
@mionkit/no-unreachable-union-types and @mionkit/no-mixed-union-properties to detect overlapping union types at compile time.Union types are serialized as a tuple [unionTypeIndex, value] where:
unionTypeIndex is the 0-based index of the matching type in the union declarationvalue is the serialized value according to that type's serialization rulesExample:
type MyUnion = string | number | bigint;
const val1: MyUnion = "hello"; // Encoded as: [0, "hello"]
const val2: MyUnion = 42; // Encoded as: [1, 42]
const val3: MyUnion = 123n; // Encoded as: [2, "123n"] (bigint as string)
This encoding is necessary because some types cannot be distinguished after serialization. For example, both string and bigint serialize to strings in JSON, so the discriminator index ensures correct deserialization.
import {createPrepareForJsonFn, createRestoreFromJsonFn} from '@mionkit/run-types';
type Result = string | number | {error: string};
async function unionTypeExample() {
const prepareForJson = await createPrepareForJsonFn<Result>();
const restoreFromJson = await createRestoreFromJsonFn<Result>();
// String value (index 0)
const json1 = prepareForJson('hello');
// Returns: [0, 'hello']
// Number value (index 1)
const json2 = prepareForJson(42);
// Returns: [1, 42]
// Object value (index 2)
const json3 = prepareForJson({error: 'not found'});
// Returns: [2, {error: 'not found'}]
// Deserialization restores the correct type
const restored = restoreFromJson(json2);
// restored === 42
}
Unions cannot contain the following types:
any or unknown - These types match everything at runtime, making union discrimination impossibleSymbol or Function - These cannot be properly serialized// ❌ Invalid: Union with 'any' or 'unknown'
type BadUnion = any | string; // Error: Union can not have 'any' or 'unknown' types
type BadUnion2 = unknown | number; // Error: Union can not have 'any' or 'unknown' types
// ✅ Valid: Union with concrete types
type GoodUnion = string | number | boolean;
RunTypes relies on @deepkit/type-compiler to generate type metadata at compile time. This TypeScript transformer embeds bytecode into the compiled JavaScript, enabling runtime access to type information without requiring separate schema definitions.
To enable type metadata generation, add reflection: true to your tsconfig.json:
{
"compilerOptions": {
// ... your compiler options
},
"reflection": true
}
This setting instructs the type compiler to generate bytecode metadata for all types in your project.
In some cases, you may want to exclude specific functions, classes, or types from metadata generation. Use the @reflection JSDoc tag to control this:
/** @reflection never */
export function myInternalFunction() {
// No type metadata will be generated for this function
return function innerFn(value: string): boolean {
return value.length > 0;
};
}
The @reflection tag accepts the following values:
| Value | Effect |
|---|---|
never, no, false, disabled, 0 | Disables metadata generation |
true, default, enabled, 1, or empty | Enables metadata generation |
This is particularly useful for:
Pure functions are methods that do not have side effect, don't depend on variables outside their scope or module imports and can be embedded into JIT-compiled code.
They must use @reflection never to avoid including type compiler artifacts.
Pure functions are registered with a namespace to organize and group related functions. This allows different libraries or modules to register their own pure functions without naming conflicts:
import {GenericPureFunction, registerPureFnClosure} from '@mionkit/core';
/** @reflection never */
export function isOdd() {
return function _isOdd(value: string): boolean {
return value.length > 0;
} as GenericPureFunction<any>;
}
// Register the pure function with a namespace for use in JIT compilation
registerPureFnClosure('myNamespace', isOdd);