Configuration
RunTypes reads its settings from the plugins list in your tsconfig.json. Configure a project once, and every build tool (Vite, Rollup, webpack, Rspack, esbuild) picks it up.
Option Reference
Every option is optional; leave one out and the default applies. Set them in your tsconfig plugin entry, shown under Setting Options.
| Option | Default | What it does |
|---|---|---|
genDir | inferred from tsconfig | The folder for generated files. Build output goes under types/ (ignored by git); the enrichment CLI writes its committed files under enriched/ (friendly/, mock/, i18n/<locale>/). Each folder has a README. When omitted, it is .mion in your source folder: rootDir if set, else the folder all your files share (the working directory for mion compile), a dot-folder that tsconfig include globs skip, so the cache never enters your program. CLI flag: --gen-dir. |
emitMode | code | How each generated function ships. code ships the function body as a string and builds it on first use (smallest output). functions ships ready functions. both ships both, for runtimes that block dynamic code (a strict CSP, Cloudflare Workers). ts ships the both output as typed .ts modules (Typed Output). A mion client that bundles its routes always ships ready functions for them. |
moduleMode | default | How the generated functions group into modules. default gives each source file its own module, so a bundle holds only the types of the files it imports (best code splitting). A type several files use is written once in a shared module (a very small one is copied instead). allSingle groups each feature into one module for the whole program (fewest files). The mion presets reject it: a client bundle would carry the server's types. allModules gives every entry its own module (rarely useful). |
hashLength | 7 | Length of the short ids in generated names, always exactly this long. If two types get the same id, the build stops and prints both types, the shared id and the length to try next (marker-type-id-collision). That is the only reason to raise it; each extra character gives 62 times more ids. |
downgradeErrors | unset | A RuntimeError (a type that can never be encoded, a contradictory format rule) stops your build: no bundle, and the mion command line exits with a failure code. See Error Levels. List the names to report as warnings instead, for example ['validate-symbol-root']; every other one still stops the build. A downgraded error still prints, marked (downgraded). Set in the tsconfig, it also lowers them in the linter. '*' downgrades all of them, for a project adopting mion that cannot list them yet. An Error cannot be downgraded, '*' included; naming one here is refused with the reason. For a bad call site in your own source, prefer a @mion-expect-error comment on the line above: it is precise, and an unused one is reported as a warning. |
levels | unset | Info messages are hidden: a method a validator skips, a validator on a type you wrote as any. 'all' shows them in the build and the mion command line. They never stop the build. The linter shows them with its mion/info rule. |
logStyle | grouped | How the build, the dev server and the mion command line print messages. grouped prints each message once, then one line per place, with the values that change. lines prints one full line per message, the form CI tools read. See Grouping Messages. |
pureFnReport | off | A report of every pure function this build generated, for tooling that moves pure function bodies between two builds of one codebase (a framework that declares a mapper in client code but runs it on the server). On the bundler plugin: 'file' writes it to <genDir>/types/pure-fns-report.json on every build (a fixed, gitignored path); 'callback' passes it in process to onPureFnReport and writes no file; off (the default) does neither. Each record carries the call site, the pure function's id, the function and package that declared the registrar, and the function body. The shape is the same for every moduleMode. In tsconfig, pureFnReport: true writes the file (the handler is plugin only); the CLI flags are --pure-fn-report-file (writes the JSON) and --pure-fn-report-wire (sends it to the calling plugin, no file). |
jsonMaxBytes | true | For every reflected type with a fully bounded size (strings with a maximum length, lists with a maximum number of items, and so on), emit the largest JSON a valid value can be. mion turns it into per route request size limits. Set false to emit nothing, for a project with its own limits. CLI flag: --json-max-bytes=false. |
i18n | unset | Translation settings for the friendly maps, detailed below. Left out, translations are off. |
markers | unset | Which packages may declare the RunTypes marker types, detailed below. Left out, only @mionjs/run-types is trusted, which suits almost every project. |
tsconfig | found like tsc | Path to your tsconfig, relative to the project root. It cannot live inside the tsconfig: set it on the bundler plugin, the lint settings, or with the CLI --tsconfig flag. When not set, it is found the way tsc finds it, searching upward from the working directory. A config you name must load, or the tool stops with an error. |
clientRoutes (CLI flag --client-routes). A client bundles its routes with 'bundle', the default, or fetches them from the server with 'fetch'.onPureFnReport(sites, phase) callback, with the same records as the JSON file. It fires once with the whole report after the build scan (phase 'build'), and again under Vite with just the changed file when you edit a pure function body (phase 'update'). Setting it without pureFnReport implies 'callback': the report is on and no file is written.Marker Packages
The markers object says where the marker types (InjectRunTypeId, InjectTypeFnArgs, CompTimeArgs, PureFunction) may come from. You need it only when a library declares them itself: the compiler trusts a marker only from a package it knows, so an unknown package leaves your type as unknown. The build reports this as marker-untrusted-package and names the package to add.
{
"compilerOptions": {
"plugins": [
{
"name": "mion",
"markers": {"packages": ["@my-org/runtypes-markers"]}
}
]
}
}
| Key | Default | What it does |
|---|---|---|
packages | none | Extra package names allowed to declare the marker types. It adds to @mionjs/run-types, never replaces it, so its markers keep working alongside your own. |
checkPackage | true | When false, a type counts as a marker on its name alone, whatever package declared it: a type you happen to name InjectRunTypeId then drives rewrites too. Prefer naming your package in packages. |
export type {InjectRunTypeId} from '@mionjs/run-types') needs no configuration, because the declaration still belongs to RunTypes.Translations
The i18n object configures translations for your FriendlyText maps:
| Key | Default | What it does |
|---|---|---|
sourceLocale | en | The language your friendly maps are written in. It decides which plural forms enrich scaffolds in them. |
locales | none | The locales you translate into; enrich --i18n all and its --no-emit check walk this list. Don't list the source locale. |
strict | false | When true, an incomplete or out-of-date translation fails the i18n check by default, the same as passing --require-complete to a single run. Rendering at runtime stays lenient either way. |
Setting Options
Add an entry named mion under compilerOptions.plugins in your tsconfig.json. A @mionjs/devtools bundler plugin option or a CLI flag overrides that entry, which overrides the built-in default. Two exceptions: options that describe the build itself (such as tsconfig) are plugin only, and the i18n object, which belongs to the enrich command, is tsconfig only.
{
"compilerOptions": {
"plugins": [
{
"name": "mion",
"emitMode": "code",
"moduleMode": "default",
"i18n": {"sourceLocale": "en", "locales": ["es", "pl"]}
}
]
}
}
import {defineConfig} from 'vite';
import runtypes from '@mionjs/devtools/runtypes/vite';
export default defineConfig({
plugins: [runtypes({emitMode: 'functions'})],
});
@mionjs/devtools first among your enforce: 'pre' plugins, so it sees your source before other plugins rewrite it.Typed Output
emitMode: 'ts' writes the generated functions as typed .ts modules, for compilers that turn TypeScript into native code and need the types to optimize it. Each type gets a data-only copy (no methods) in its own module under <genDir>/types/t/. The modules pass the strictest tsconfig and run the same logic as the both output. The code sent to clients does not change.
/** User */
export type T_OMOvTMe = {id: number; name: string};
function g_Eq2V_OMOvTMe(_utl: RTUtils): (v: unknown) => v is T_OMOvTMe {
function Eq2V_OMOvTMe(v: any): v is T_OMOvTMe {
return typeof v === 'object' && v !== null && Number.isFinite(v.id) && typeof v.name === 'string';
}
return Eq2V_OMOvTMe;
}
mion compile then writes your sources to the tsconfig outDir as .ts instead of .js, so it needs an outDir. Imports between the files keep their .ts extension, so your tsconfig needs allowImportingTsExtensions (or rewriteRelativeImportExtensions when tsc emits).Syncing Enrichment Files During Dev
The @mionjs/devtools bundler plugin can scaffold and sync your committed FriendlyText and MockData maps while your dev server runs, instead of you running the enrichment CLI. Turn it on with an enrich object on the plugin (never in your tsconfig); it runs only in dev and watch. Like enrich --update, it updates the file structure but never fills in translated strings or any other value you wrote.
import {defineConfig} from 'vite';
import runtypes from '@mionjs/devtools/runtypes/vite';
export default defineConfig({
plugins: [
runtypes({
enrich: {
friendly: true,
mock: true,
i18n: {sourceLocale: 'en', locales: ['es', 'pl']},
suppressHmr: true,
},
}),
],
});
| Key | Default | What it does |
|---|---|---|
friendly | false | Scaffold and sync the enriched/friendly/ maps as your types change. |
mock | false | Scaffold and sync the enriched/mock/ maps as your types change. |
i18n | unset | Sync the per-locale enriched/i18n/<locale>/ mirrors. Takes sourceLocale, locales and strict, the same shape as the tsconfig i18n object; its presence turns the locale sync on. |
suppressHmr | on when any key above is on | Skip HMR for writes under <genDir>/enriched, so the dev server does not reload when the plugin (or you) touch a mirror file. |
Enrichment Check on Production Builds
With enrich set, a production build never writes. The plugin runs a read-only check instead (the build equivalent of enrich --require-complete), and fails the build if any file is out of date, missing, still carries an unfilled blank, or holds a stale @rtOrphan entry.
The Build Cache
RunTypes keeps a cache under node_modules/.cache/mion so a rebuild can skip types that did not change. It is on when your tsconfig sets incremental (or composite), and off otherwise; there is no separate option.
{
"compilerOptions": {
"incremental": true
}
}
node_modules/.cache clears it.