agentleFS
AlteriomAlteriom/ai-dev-skills/skills/typescript-pro/SKILL.md

typescript-pro

Skill0 starsChanged 3 months agoMITView on GitHub

Master advanced TypeScript features including generics, conditional types, type guards, utility types, and full-stack type safety

  • Installs packages

34 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use ui-ux-pro-max and frontend-design.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • StrayDragonSkill

    ui-ux-pro-max

    UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient.

    Also in 3 projects that use this

  • BlockSkill

    frontend-design

    Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

6 rules. 0 are common, 4 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 33 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when:

    line 26
  2. Distinctive:

    Don't use: when you need:

    line 37
  3. Distinctive:

    Karpathy Principle: When NOT to Use: - Don't use TypeScript for throwaway scripts (<50 lines).

    line 97
  4. Very specific:

    Karpathy Principle: Goal-Driven Execution: - After writing generics, verify inference works: hover over variables, check inferred types.

    line 140
  5. Very specific:

    Karpathy Principle: Composition Over Configuration: - Use utility types (Partial<T>, Pick<T>, Omit<T>) over manual interface duplication.

    line 518
  6. Distinctive:

    Before deploying TypeScript code:

    line 522

“Common” is measured: kept by at least 6 in 10 of the 33 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

TypeScript Pro - Advanced Type System Mastery

When to Use

Use this skill when:

  • Building type-safe full-stack applications
  • Creating reusable library types with complex generics
  • Implementing discriminated unions for state machines
  • Designing type-safe APIs with zero runtime errors
  • Optimizing TypeScript compilation and type checking
  • Creating branded types for domain modeling
  • Setting up monorepo with project references
  • Implementing advanced conditional and mapped types

Don't use when you need:

  • Basic TypeScript syntax (use TypeScript basics skill)
  • JavaScript without types (just use JavaScript)
  • Runtime type validation (use Zod/io-ts alongside TypeScript)
  • Dynamically typed code (TypeScript fights you on this)

Karpathy Principle: Think Before Coding - TypeScript types are compile-time only. They disappear at runtime. If you need runtime validation, use Zod. If you need type inference from Zod, use z.infer<typeof schema>. Don't fight the system.

Karpathy Principle: Trade-offs Everywhere - Strict mode catches bugs but slows development. Start strict in new projects. Migrate legacy gradually (skipLibCheck: true for incremental adoption). Perfect types today > working code never.

Prerequisites

Required Knowledge

  • TypeScript fundamentals (types, interfaces, classes)
  • JavaScript ES6+ features
  • Generics basics
  • Union and intersection types
  • Type narrowing concepts

Required Tools

# Install TypeScript
npm install -D typescript @types/node

# Initialize tsconfig.json
npx tsc --init

# Recommended additional tools
npm install -D ts-node tsx @types/node
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

Strict Mode Configuration

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022"],
    "strict": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Karpathy Principle: Simplicity First - Enable all strict flags from the start. Retrofitting strict mode to an existing codebase is painful. Pay the type-safety cost upfront.

Karpathy Principle: When NOT to Use - Don't use TypeScript for throwaway scripts (<50 lines). Don't use it when team lacks JS fundamentals. Types add cognitive load - worth it for maintained code, not experiments.

Core Workflows

1. Advanced Generics

Generic Constraints:

// Bad: Unconstrained generic
function getProperty<T>(obj: T, key: string) {
  return obj[key]  // Error: no index signature
}

// Good: Constrained generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]  // Type-safe!
}

const user = { name: 'Alice', age: 30 }
const name = getProperty(user, 'name')  // string
const age = getProperty(user, 'age')    // number

Generic Type Inference:

// Tuple with rest parameters
function tuple<T extends any[]>(...args: T): T {
  return args
}

const result = tuple(1, 'hello', true)  // [number, string, boolean]

// Type-safe object builder
function createObject<T extends Record<string, any>>(obj: T): T {
  return obj
}

const user = createObject({
  name: 'Alice',
  age: 30,
})  // { name: string; age: number }

Karpathy Principle: Goal-Driven Execution - After writing generics, verify inference works: hover over variables, check inferred types. If you see any or overly broad types, your constraints are wrong.

Karpathy Principle: Fail Fast, Fail Loud - Enable noUncheckedIndexedAccess. array[0] should be T | undefined, not T. This single flag prevents 80% of production runtime errors.

2. Conditional Types

Basic Conditional Types:

type IsString<T> = T extends string ? true : false

type A = IsString<string>  // true
type B = IsString<number>  // false

// Extract function return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

type Func = () => string
type Result = ReturnType<Func>  // string

Distributive Conditional Types:

type ToArray<T> = T extends any ? T[] : never

type Arr = ToArray<string | number>  // string[] | number[]

// Extract non-nullable types
type NonNullable<T> = T extends null | undefined ? never : T

type Clean = NonNullable<string | null | undefined>  // string

3. Mapped Types

Basic Mapped Types:

type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}

type Partial<T> = {
  [K in keyof T]?: T[K]
}

type Required<T> = {
  [K in keyof T]-?: T[K]  // Remove optional modifier
}

// Usage
interface User {
  name: string
  age?: number
}

type ReadonlyUser = Readonly<User>
// { readonly name: string; readonly age?: number }

type PartialUser = Partial<User>
// { name?: string; age?: number }

Advanced Mapped Types:

// Deep readonly
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}

// Pick by value type
type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K]
}

interface Person {
  name: string
  age: number
  isActive: boolean
}

type StringFields = PickByType<Person, string>  // { name: string }
type NumberFields = PickByType<Person, number>  // { age: number }

4. Discriminated Unions

State Machine Pattern:

type LoadingState = {
  status: 'loading'
}

type SuccessState<T> = {
  status: 'success'
  data: T
}

type ErrorState = {
  status: 'error'
  error: string
}

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState

function handleState(state: AsyncState<User>) {
  switch (state.status) {
    case 'loading':
      // state is LoadingState
      return 'Loading...'
    
    case 'success':
      // state is SuccessState<User>
      return `Hello ${state.data.name}`
    
    case 'error':
      // state is ErrorState
      return `Error: ${state.error}`
  }
}

Exhaustive Checks:

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`)
}

function handleState(state: AsyncState<User>) {
  switch (state.status) {
    case 'loading':
      return 'Loading...'
    case 'success':
      return state.data.name
    case 'error':
      return state.error
    default:
      return assertNever(state)  // Compile error if we miss a case
  }
}

5. Type Guards

User-Defined Type Guards:

interface Dog {
  type: 'dog'
  bark(): void
}

interface Cat {
  type: 'cat'
  meow(): void
}

type Animal = Dog | Cat

// Type predicate
function isDog(animal: Animal): animal is Dog {
  return animal.type === 'dog'
}

function makeSound(animal: Animal) {
  if (isDog(animal)) {
    animal.bark()  // Type narrowed to Dog
  } else {
    animal.meow()  // Type narrowed to Cat
  }
}

Assertion Functions:

function assertIsDefined<T>(value: T | null | undefined): asserts value is T {
  if (value === null || value === undefined) {
    throw new Error('Value is null or undefined')
  }
}

function processUser(user: User | null) {
  assertIsDefined(user)
  // user is now User (not User | null)
  console.log(user.name)
}

Common Patterns

1. Branded Types

type Brand<K, T> = K & { __brand: T }

type UserId = Brand<string, 'UserId'>
type OrderId = Brand<string, 'OrderId'>

function createUserId(id: string): UserId {
  return id as UserId
}

function createOrderId(id: string): OrderId {
  return id as OrderId
}

function getUser(userId: UserId) {
  // ...
}

const userId = createUserId('123')
const orderId = createOrderId('456')

getUser(userId)   // OK
getUser(orderId)  // Error: OrderId not assignable to UserId

2. Builder Pattern

class QueryBuilder<T> {
  private conditions: Array<(item: T) => boolean> = []
  
  where<K extends keyof T>(key: K, value: T[K]): this {
    this.conditions.push(item => item[key] === value)
    return this
  }
  
  execute(items: T[]): T[] {
    return items.filter(item =>
      this.conditions.every(condition => condition(item))
    )
  }
}

// Usage
interface User {
  name: string
  age: number
  city: string
}

const users: User[] = [...]

const result = new QueryBuilder<User>()
  .where('city', 'NYC')
  .where('age', 30)
  .execute(users)

3. Type-Safe Event Emitter

type EventMap = {
  'user:created': { id: string; name: string }
  'user:deleted': { id: string }
  'order:placed': { orderId: string; amount: number }
}

class TypedEventEmitter<T extends Record<string, any>> {
  private listeners: Partial<{
    [K in keyof T]: Array<(data: T[K]) => void>
  }> = {}
  
  on<K extends keyof T>(event: K, listener: (data: T[K]) => void) {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event]!.push(listener)
  }
  
  emit<K extends keyof T>(event: K, data: T[K]) {
    this.listeners[event]?.forEach(listener => listener(data))
  }
}

// Usage
const emitter = new TypedEventEmitter<EventMap>()

emitter.on('user:created', (data) => {
  // data is { id: string; name: string }
  console.log(`User created: ${data.name}`)
})

emitter.emit('user:created', { id: '1', name: 'Alice' })  // OK
emitter.emit('user:created', { id: '1' })  // Error: missing name

Common Pitfalls

1. Type Assertions vs Type Guards

❌ Bad (unsafe assertion):

const value: unknown = JSON.parse(input)
const user = value as User  // Unsafe!

✅ Good (type guard or runtime validation):

import { z } from 'zod'

const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
})

type User = z.infer<typeof UserSchema>

const value = JSON.parse(input)
const user = UserSchema.parse(value)  // Runtime validation

2. Enum vs const Object

❌ Bad (enum generates runtime code):

enum Status {
  Active = 'active',
  Inactive = 'inactive',
}

✅ Good (const object, zero runtime cost):

const Status = {
  Active: 'active',
  Inactive: 'inactive',
} as const

type Status = typeof Status[keyof typeof Status]  // 'active' | 'inactive'

3. Missing Generic Constraints

❌ Bad (unconstrained):

function merge<T>(obj1: T, obj2: T): T {
  return { ...obj1, ...obj2 }  // Error: spread requires object type
}

✅ Good (constrained):

function merge<T extends object>(obj1: T, obj2: T): T {
  return { ...obj1, ...obj2 }
}

4. Type-Only Imports

❌ Bad (mixed import):

import { User } from './types'  // Might include runtime code

✅ Good (type-only import):

import type { User } from './types'  // Guaranteed erased at runtime

5. Overly Complex Types

❌ Bad (unreadable):

type ComplexType<T> = T extends (infer U)[]
  ? U extends object
    ? { [K in keyof U]: U[K] extends Function ? never : U[K] }[]
    : T
  : T

✅ Good (split into smaller types):

type ArrayElement<T> = T extends (infer U)[] ? U : T
type OmitFunctions<T> = { [K in keyof T]: T[K] extends Function ? never : T[K] }
type SimplifiedType<T> = T extends any[] ? OmitFunctions<ArrayElement<T>>[] : T

Karpathy Principle: Surgical Changes - Complex types are hard to debug. Build them incrementally. Test each layer. Use type utilities to break down complexity.

Karpathy Principle: Composition Over Configuration - Use utility types (Partial<T>, Pick<T>, Omit<T>) over manual interface duplication. DRY applies to types too.

Verification Checklist

Before deploying TypeScript code:

Type Safety

  • All strict flags enabled
  • No explicit any without justification
  • Type coverage > 95% (use type-coverage package)
  • No unsafe type assertions
  • Runtime validation for external data (Zod)

Performance

  • Incremental compilation enabled
  • Project references used (monorepos)
  • skipLibCheck: true for faster builds
  • No circular dependencies

Code Quality

  • Type-only imports where applicable
  • Discriminated unions for state
  • Type guards instead of assertions
  • Generic constraints where needed

Integration with Other Skills

With Next.js

  • Type-safe API routes with tRPC
  • Server/client component types
  • Props type inference

With React

  • Generic component props
  • Event handler types
  • Context type safety

With tRPC

  • End-to-end type safety
  • Router type inference
  • Input/output validation

With Zod

  • Runtime validation + type inference
  • Schema-first API design
  • Type-safe forms

References

Official Docs

Tools

Meta: Skill Quality

Completeness: ✅ Comprehensive (9/9 sections)
Karpathy Principles: ✅ 5 mentions
Code Examples: ✅ 25+ working examples
Production Tested: ✅ Used across all Alteriom TypeScript projects
Last Updated: 2026-04-13

Coverage:

  • ✅ Advanced generics with constraints
  • ✅ Conditional types
  • ✅ Mapped types
  • ✅ Discriminated unions
  • ✅ Type guards and assertion functions
  • ✅ Branded types
  • ✅ Builder pattern
  • ✅ Type-safe event emitters
  • ✅ Common pitfalls and solutions

Skill Level: Advanced
Time to Learn: 6-8 hours
Prerequisites Met: TypeScript fundamentals, JavaScript ES6+

Known Gaps: None - production-ready

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.