name: typescript-standards description: TypeScript coding standards. Use when writing TypeScript, reviewing code, or refactoring. Enforces named exports, no dynamic imports, discriminated unions, proper type safety. allowed-tools: [Read, Edit, Grep, Glob]
TypeScript Standards
Named Exports Only
// ❌ Bad
export default function myFunction() {}
// ✅ Good
export function myFunction() {}
Package Exports (Monorepo)
Only export what other packages actually consume:
// ❌ Bad - Exports everything including internal types
export * from "./digest";
// ✅ Good - Explicit public interface
export {
fetchCompoundDigest,
buildCompoundDigestBlocks,
type CompoundDigestEnv,
} from "./digest";
Before adding exports, check what's actually imported by consumer packages.
Import Patterns
Prefer Static Imports
Always use static imports at the top of the file. Only use dynamic imports (await import()) when:
- Conditional loading is required (feature flags, environment checks)
- Code splitting is intentional for performance
- Circular dependency breaking is necessary
Never use dynamic imports just to import something mid-function when a static import would work.
Type-Only Imports
For values used only as types, use explicit type-only imports:
// ❌ Bad - Imports value but only uses type
import { MyClass } from './my-class'
type Instance = MyClass
// ✅ Good - Clear intent, no runtime import
import { type MyClass } from './my-class'
type Instance = MyClass
Naming Conventions
- Files: kebab-case (
my-component.ts) - Variables/functions: camelCase (
myVariable,myFunction()) - Classes/types/interfaces: PascalCase (
MyClass,MyInterface) - Constants/enum values: ALL_CAPS (
MAX_COUNT) - Generic type parameters: Prefix with
T(TKey,TValue)
Type Safety Rules
Derive Types from Zod Schemas
When a Zod schema exists, always derive the TypeScript type from it using z.infer. Never define a separate interface that duplicates the schema's shape:
// ❌ Bad - Manually duplicates the Zod schema, drifts over time
export interface User {
id: string
name: string
email: string
}
// ✅ Good - Single source of truth
import { type User } from "@rudel/api-routes"
// ✅ Good - Extend when the service needs extra internal fields
import { type User as UserBase } from "@rudel/api-routes"
export interface User extends UserBase {
session_count: number
}
If the type needs fields beyond the schema, extend the inferred type rather than redefining it.
No Type Casts
Never use as or as unknown as type assertions. They hide type incompatibilities:
// ❌ Bad - Hides the real problem
const env = context.env as unknown as ExecutorEnv
// ✅ Good - Fix the underlying type mismatch
// Option 1: Properly type the source
const env: ExecutorEnv = context.env
// Option 2: Use type guards for runtime checking
function isExecutorEnv(env: unknown): env is ExecutorEnv {
return typeof env === 'object' && env !== null && 'DB' in env
}
If you encounter code that seems to require a cast:
- Investigate why the types don't match
- Fix the source type definitions
- If copying from existing code with casts, note it needs cleanup
Prefer Discriminated Unions
// ✅ Good - Prevents impossible states
type FetchingState<TData> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: TData }
| { status: 'error'; error: Error }
// Handle with switch
switch (state.status) {
case 'success':
console.log(state.data)
break
}
Optional Properties
Use sparingly. Prefer T | undefined over T? to force explicit passing:
// ✅ Good
type AuthOptions = {
userId: string | undefined
}
Prefer Interfaces for Extending
// ❌ Bad - & operator has terrible performance
type C = A & B
// ✅ Good
interface C extends A, B {}
Don't Use Enums
Use as const objects instead:
const sizes = {
xs: 'EXTRA_SMALL',
sm: 'SMALL',
} as const
type Size = keyof typeof sizes
Installing Libraries
Never rely on training data for versions. Always install latest:
pnpm add -D @typescript-eslint/eslint-plugin
Async Operations
Use Promise.all for parallel operations:
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()])
Immutability
- Use readonly arrays
- Use const assertions
- Create new objects instead of mutating