RunTypes

Caveats

Common pitfalls when using RunTypes and how to avoid them with ESLint rules.

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));
Use the @mionkit/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);
Use the @mionkit/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>();
Use the @mionkit/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 };
Use the @mionkit/no-unreachable-union-types ESLint rule to catch this issue.
This rule will only check Types that are used in routes/middleFns.

To catch all these issues automatically, add the mion ESLint plugin to your project:

npm install @mionkit/eslint-plugin -D

Then use the recommended configuration:

.eslintrc.json
{
  "extends": ["plugin:@mionkit/recommended"]
}

This enables all the rules mentioned above with error severity.