Introduction

Quick Start

Install the package + Vite plugin and generate your first validator from a TypeScript type.

1. Install the Packages

Install the runtime package and the build plugin that reads your types.

pnpm add @mionjs/run-types
pnpm add -D @mionjs/devtools
Only RunTypes ends up in your bundle, and it has no dependencies. @mionjs/devtools runs at build time and ships nothing.

2. Add the Vite Plugin

Add the plugin to your vite.config.ts. It reads your types during the build.

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

export default defineConfig({
  plugins: [
    // the binary for your platform installs as an optional dependency
    runtypes({
      tsconfig: 'tsconfig.json',
    }),
  ],
});
No tsconfig.json flag to turn on: point the plugin at your existing tsconfig.json.

3. Write a Type and Validate It

Write a normal TypeScript type and ask for a validator: no schema, no decorators, no reflection at runtime.

import {createValidateFn, createGetValidationErrorsFn} from '@mionjs/run-types';

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

// generated from User at build time
const isUser = createValidateFn<User>();

const maybeUser: unknown = JSON.parse(
  '{"id":1,"name":"Ada","email":"[email protected]","roles":["admin"]}'
);

if (isUser(maybeUser)) {
  // narrowed to User here
  console.log(maybeUser.name);
}

// lists what failed, not just yes or no
const getUserErrors = createGetValidationErrorsFn<User>();
getUserErrors({id: '1', name: 'Ada'}); // [{path: ['id'], ...}, ...]

export {isUser, getUserErrors};

Import Paths

The main import has the everyday API. The rest is split into subpaths, so you only bundle what you import.

ImportWhat it has
@mionjs/run-typesValidators, JSON round trip functions, reflection and the type helpers
@mionjs/run-types/formatsFormat types like Email and UUIDv4, plus their checks
@mionjs/run-types/formats/temporalThe TC39 Temporal formats
@mionjs/run-types/buildersType builders, for types you assemble at runtime
@mionjs/run-types/runtimePure functions and class serializers you register yourself
@mionjs/run-types/mockingMock data generation
Import mocking only from test files. A browser bundle that imports it ships the generator.

Supported Bundlers

The same plugin ships for other bundlers, one entry point each. Every entry takes the same options and generates the same code. The Vite entry also adds hot reload: editing a type refreshes every file that uses it, even a type only import or a global declared in a .d.ts.

import runtypes from '@mionjs/devtools/runtypes/rollup';

export default {
  plugins: [
    runtypes({
      tsconfig: 'tsconfig.json',
    }),
  ],
};
BundlerImport
Vite@mionjs/devtools/runtypes/vite
Rollup@mionjs/devtools/runtypes/rollup
Rolldown@mionjs/devtools/runtypes/rolldown
webpack@mionjs/devtools/runtypes/webpack
Rspack@mionjs/devtools/runtypes/rspack
esbuild@mionjs/devtools/runtypes/esbuild
Bun@mionjs/devtools/runtypes/bun
Next.js@mionjs/devtools/runtypes/next
For a tool not on the list, @mionjs/devtools/unplugin exposes the unplugin instance the others are built from.

Bundling With Bun

Pass the Bun entry to Bun.build:

import runtypes from '@mionjs/devtools/runtypes/bun';

await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',
  plugins: [runtypes()],
});

Running and Testing With Bun

bun run and bun test have no bundle step, so register the plugin from a preload file. Point bunfig.toml at it and every file is compiled as Bun imports it:

preload = ["./rt-preload.ts"]

[test]
preload = ["./rt-preload.ts"]
// rt-preload.ts
import {plugin} from 'bun';
import runtypes from '@mionjs/devtools/runtypes/bun';

plugin(runtypes());
await plugin(...) works too, but is not needed: no file loads before the plugin is ready.

Setting Up Next.js

Turbopack, the bundler Next 16 uses by default, takes no plugins, so Next.js has its own entry. One wrapper covers Turbopack and webpack, so next dev --webpack and next build --webpack keep working. Options go in a second argument, the same ones every other entry takes: withRunTypes({reactStrictMode: true}, {tsconfig: 'tsconfig.json'}).

import {withRunTypes} from '@mionjs/devtools/runtypes/next';

export default await withRunTypes({
  reactStrictMode: true,
});
The await matters: RunTypes reads your types before Turbopack starts compiling.

Next.js in a Monorepo

Point Turbopack at the folder that holds your node_modules and any shared code you import. Turbopack will not compile files outside that folder.

import path from 'node:path';

export default await withRunTypes({
  turbopack: {root: path.resolve(import.meta.dirname, '..')},
});
Copyright © 2026