Quick Start
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
npm install @mionjs/run-types
npm install -D @mionjs/devtools
yarn add @mionjs/run-types
yarn add -D @mionjs/devtools
@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',
}),
],
});
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.
| Import | What it has |
|---|---|
@mionjs/run-types | Validators, JSON round trip functions, reflection and the type helpers |
@mionjs/run-types/formats | Format types like Email and UUIDv4, plus their checks |
@mionjs/run-types/formats/temporal | The TC39 Temporal formats |
@mionjs/run-types/builders | Type builders, for types you assemble at runtime |
@mionjs/run-types/runtime | Pure functions and class serializers you register yourself |
@mionjs/run-types/mocking | Mock data generation |
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',
}),
],
};
| Bundler | Import |
|---|---|
| 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 |
@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,
});
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, '..')},
});