Caveats
RunTypes relies on TypeScript's type metadata being available at runtime. There are common pitfalls that can prevent this from working correctly. mion provides ESLint rules to catch these issues at development time.
Type-Only Imports
When you use import type { X } or import { type X }, TypeScript completely erases these imports at compile time. This means the type metadata is not available at runtime, and RunTypes cannot generate validation or serialization functions.
// ❌ WRONG: Type-only import - erased at runtime
import type { User } from './types';
route((ctx, id: number): User => getUser(id));
// ✅ CORRECT: Regular import - type metadata preserved
import { User } from './types';
route((ctx, id: number): User => getUser(id));
@mionjs/no-type-imports ESLint rule to catch this issue.This Rule will only check Types that are used in routes/middleFns.
Missing Type Annotations
RunTypes needs explicit type annotations on route/middleFn parameters and return types. Without them, TypeScript cannot emit the type metadata needed for runtime validation.
// ❌ WRONG: Missing type annotations
route((ctx, user) => user.name);
// ✅ CORRECT: Explicit type annotations
route((ctx, user: User): string => user.name);
@mionjs/strong-typed-routes ESLint rule to catch this issue.Using typeof with RunType Functions
Using typeof with runtime values can lead to incorrect type inference.
The type metadata emitted at compile time is directly attached to type definitions (User type in bellow example).
So using typeof user loses the type metadata.
// ❌ WRONG: typeof infers from current value
const user = { id: '1', name: 'John' };
const userRunType = runType<typeof user>();
// ✅ CORRECT: Explicit type definition
type User = { id: string; name: string };
const userRunType = runType<User>();
@mionjs/no-typeof-runtype ESLint rule to catch this issue.Unreachable Union Types
When deserializing union types, RunTypes tries to match against each type in order. If a less specific type appears before a more specific type, the more specific type will never be matched.
// ❌ WRONG: {id: string} matches first, {id: string; name: string} is unreachable
type User = { id: string } | { id: string; name: string };
// ✅ CORRECT: Most specific type first
type User = { id: string; name: string } | { id: string };
@mionjs/no-unreachable-union-types ESLint rule to catch this issue.This rule will only check Types that are used in routes/middleFns.
Recommended ESLint Configuration
To catch all these issues automatically, add the mion ESLint plugin to your project:
npm install @mionjs/eslint-plugin -D
Then use the recommended configuration:
{
"extends": ["plugin:@mionjs/recommended"]
}
This enables all the rules mentioned above with error severity.