From aai-stack-typescript
Provides TypeScript patterns and best practices for interfaces vs types, immutability with readonly, optional/required properties, discriminated unions, and result types for type-safe code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/aai-stack-typescript:typescript-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Best practices and patterns for writing type-safe TypeScript code.
Best practices and patterns for writing type-safe TypeScript code.
// Use interfaces for object shapes (can be extended)
interface User {
id: string
name: string
email: string
}
interface AdminUser extends User {
permissions: string[]
}
// Use types for unions, intersections, and complex types
type Status = 'pending' | 'active' | 'inactive'
type Nullable<T> = T | null
type UserOrAdmin = User | AdminUser
// Readonly properties
interface Config {
readonly apiUrl: string
readonly timeout: number
}
// Readonly arrays
const items: readonly string[] = ['a', 'b', 'c']
// items.push('d') // Error!
// Deep readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
const config: DeepReadonly<Config> = {
apiUrl: 'https://api.example.com',
timeout: 5000,
}
interface UserInput {
name: string // Required
email: string // Required
phone?: string // Optional
age?: number // Optional
}
// Make all properties required
type RequiredUser = Required<UserInput>
// Make all properties optional
type PartialUser = Partial<UserInput>
// Pick specific properties
type UserContact = Pick<UserInput, 'email' | 'phone'>
// Omit specific properties
type UserWithoutAge = Omit<UserInput, 'age'>
interface LoadingState {
status: 'loading'
}
interface SuccessState {
status: 'success'
data: unknown
}
interface ErrorState {
status: 'error'
error: Error
}
type AsyncState = LoadingState | SuccessState | ErrorState
function handleState(state: AsyncState) {
switch (state.status) {
case 'loading':
return 'Loading...'
case 'success':
return `Data: ${JSON.stringify(state.data)}`
case 'error':
return `Error: ${state.error.message}`
}
}
// Exhaustiveness checking
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`)
}
function handleStateExhaustive(state: AsyncState) {
switch (state.status) {
case 'loading':
return 'Loading...'
case 'success':
return `Data: ${JSON.stringify(state.data)}`
case 'error':
return `Error: ${state.error.message}`
default:
return assertNever(state) // Compile error if case is missing
}
}
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E }
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return { ok: false, error: 'Division by zero' }
}
return { ok: true, value: a / b }
}
const result = divide(10, 2)
if (result.ok) {
console.log(result.value) // TypeScript knows value exists
} else {
console.log(result.error) // TypeScript knows error exists
}
interface Dog {
kind: 'dog'
bark(): void
}
interface Cat {
kind: 'cat'
meow(): void
}
type Animal = Dog | Cat
// Type guard function
function isDog(animal: Animal): animal is Dog {
return animal.kind === 'dog'
}
function handleAnimal(animal: Animal) {
if (isDog(animal)) {
animal.bark() // TypeScript knows it's a Dog
} else {
animal.meow() // TypeScript knows it's a Cat
}
}
// Type assertion (use sparingly)
const value = someFunction() as string
// Non-null assertion (use sparingly)
const element = document.getElementById('root')!
// Const assertion (preferred for literals)
const config = {
endpoint: '/api',
method: 'GET',
} as const
// config.method is type 'GET', not string
interface Response {
data?: {
user?: {
name?: string
}
}
}
function getUserName(response: Response): string {
// Optional chaining
return response.data?.user?.name ?? 'Unknown'
}
// ?? only uses fallback for null/undefined
const value = someValue ?? 'default'
// Different from || which uses fallback for all falsy
const count = 0
const result1 = count || 10 // 10 (0 is falsy)
const result2 = count ?? 10 // 0 (0 is not null/undefined)
type Nullable<T> = T | null | undefined
function process<T>(value: Nullable<T>): NonNullable<T> {
if (value == null) {
throw new Error('Value is null or undefined')
}
return value
}
// Function type
type Comparator<T> = (a: T, b: T) => number
// Function with overloads
function format(value: string): string
function format(value: number): string
function format(value: string | number): string {
return String(value)
}
// Arrow function with type
const add: (a: number, b: number) => number = (a, b) => a + b
// Generic function
function identity<T>(value: T): T {
return value
}
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0)
}
// Typed rest parameters with tuple
function log(level: string, ...messages: string[]): void {
console.log(`[${level}]`, ...messages)
}
abstract class BaseService {
abstract fetchData(): Promise<unknown>
protected log(message: string): void {
console.log(`[${this.constructor.name}] ${message}`)
}
}
class UserService extends BaseService {
async fetchData(): Promise<User[]> {
this.log('Fetching users')
return fetch('/api/users').then(r => r.json())
}
}
class User {
constructor(
public readonly id: string,
public name: string,
private password: string,
protected email: string
) {}
}
// Equivalent to:
class UserExplicit {
public readonly id: string
public name: string
private password: string
protected email: string
constructor(id: string, name: string, password: string, email: string) {
this.id = id
this.name = name
this.password = password
this.email = email
}
}
// components/index.ts
export { Button } from './Button'
export { Input } from './Input'
export { Modal } from './Modal'
// types/index.ts
export type { User, UserInput } from './user'
export type { Config } from './config'
// Import only types (erased at runtime)
import type { User, Config } from './types'
// Mixed import
import { createUser, type User } from './user'
any// Bad
function process(data: any): any {
return data.value
}
// Good
function process<T extends { value: unknown }>(data: T): T['value'] {
return data.value
}
// If you truly need escape hatch, use `unknown`
function processUnknown(data: unknown): string {
if (typeof data === 'string') {
return data
}
throw new Error('Expected string')
}
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
Used by:
frontend-developer agentbackend-developer agentfullstack-developer agentnpx claudepluginhub bradtaylorsf/alphaagent-teamProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.