typescript-standards

star 281

TypeScript coding standards. Use when writing TypeScript, reviewing code, or refactoring. Enforces named exports, no dynamic imports, discriminated unions, proper type safety.

obsessiondb By obsessiondb schedule Updated 2/24/2026

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:

  1. Investigate why the types don't match
  2. Fix the source type definitions
  3. 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
Install via CLI
npx skills add https://github.com/obsessiondb/rudel --skill typescript-standards
Repository Details
star Stars 281
call_split Forks 16
navigation Branch main
article Path SKILL.md
More from Creator