Claude-code-setup standards-typescript
This skill provides TypeScript coding standards and is automatically loaded for TypeScript projects. It includes naming conventions, best practices, and recommended tooling.
install
source · Clone the upstream repo
git clone https://github.com/b33eep/claude-code-setup
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/b33eep/claude-code-setup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/standards-typescript" ~/.claude/skills/b33eep-claude-code-setup-standards-typescript && rm -rf "$T"
manifest:
skills/standards-typescript/SKILL.mdsource content
TypeScript Coding Standards
Core Principles
- Simplicity: Simple, understandable code
- Readability: Readability over cleverness
- Maintainability: Code that's easy to maintain
- Testability: Code that's easy to test
- DRY: Don't Repeat Yourself - but don't overdo it
General Rules
- Early Returns: Use early returns to avoid nesting
- Descriptive Names: Meaningful names for variables and functions
- Minimal Changes: Only change relevant code parts
- No Over-Engineering: No unnecessary complexity
- Minimal Comments: Code should be self-explanatory. No redundant comments!
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Variables/Functions | camelCase | , |
| Classes/Interfaces/Types | PascalCase | , |
| Constants | UPPER_SNAKE_CASE | |
| Private | Prefix with or | , |
| Files | kebab-case or camelCase | , |
| Interfaces | No prefix | not |
| Type aliases | PascalCase | , |
| Event Handlers | Prefix with | , |
Project Structure
myproject/ ├── src/ │ ├── index.ts # Entry point │ ├── config.ts # Settings, env vars │ ├── types/ │ │ └── index.ts # Shared types │ ├── models/ │ │ └── user.ts # Domain models │ ├── services/ │ │ └── user-service.ts # Business logic │ ├── repositories/ │ │ └── user-repo.ts # Data access │ └── utils/ │ └── helpers.ts # Utility functions ├── tests/ │ ├── services/ │ │ └── user-service.test.ts │ └── setup.ts ├── package.json ├── tsconfig.json └── README.md
Code Style
// Use explicit types for function parameters and return values function getUserById(userId: string): User | undefined { if (!userId) { throw new Error("userId cannot be empty"); } // implementation... } // Prefer interfaces for object shapes interface User { id: string; name: string; email: string; age?: number; } // Use type aliases for unions, intersections, or primitives type UserId = string; type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"; type Result<T> = { success: true; data: T } | { success: false; error: string };
Best Practices
// Prefer const over let const users: User[] = []; // Use nullish coalescing and optional chaining const name = user?.profile?.name ?? "Anonymous"; // Prefer template literals const message = `Hello, ${user.name}!`; // Use destructuring const { id, name, email } = user; function processUser({ id, name }: User): void { } // Prefer array methods over loops const activeUsers = users.filter(u => u.isActive); const userNames = users.map(u => u.name); const totalAge = users.reduce((sum, u) => sum + u.age, 0); // Use readonly for immutable data interface Config { readonly apiUrl: string; readonly maxRetries: number; } // Use as const for literal types const DIRECTIONS = ["north", "south", "east", "west"] as const; type Direction = typeof DIRECTIONS[number]; // Prefer unknown over any function parseJson(input: string): unknown { return JSON.parse(input); } // Type guards for type narrowing function isUser(value: unknown): value is User { return typeof value === "object" && value !== null && "id" in value; }
Utility Types
// Partial<T> - Make all properties optional type UserUpdate = Partial<User>; // { id?: string; name?: string; email?: string; age?: number } // Pick<T, K> - Select specific properties type UserPreview = Pick<User, "id" | "name">; // { id: string; name: string } // Omit<T, K> - Exclude specific properties type UserWithoutEmail = Omit<User, "email">; // { id: string; name: string; age?: number } // Record<K, T> - Object with specific keys and value type type RolePermissions = Record<"admin" | "user" | "guest", string[]>; // { admin: string[]; user: string[]; guest: string[] } // ReturnType<F> - Extract return type of function type FetchResult = ReturnType<typeof fetchUser>; // Promise<User | undefined> // Parameters<F> - Extract parameter types type FetchParams = Parameters<typeof fetchUser>; // [userId: string] // Awaited<T> - Unwrap Promise type type ResolvedUser = Awaited<ReturnType<typeof fetchUser>>; // User | undefined
Discriminated Unions
// Use a common "type" or "status" field as discriminator type ApiResponse<T> = | { status: "success"; data: T } | { status: "error"; error: string } | { status: "loading" }; function handleResponse(response: ApiResponse<User>) { switch (response.status) { case "success": console.log(response.data.name); // TypeScript knows data exists break; case "error": console.error(response.error); // TypeScript knows error exists break; case "loading": console.log("Loading..."); break; } } // State machines with discriminated unions type AuthState = | { state: "idle" } | { state: "loading" } | { state: "authenticated"; user: User } | { state: "error"; message: string }; // Action types for reducers type UserAction = | { type: "SET_USER"; payload: User } | { type: "CLEAR_USER" } | { type: "UPDATE_NAME"; payload: string };
Runtime Validation with Zod
import { z } from "zod"; // Define schema const UserSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(100), email: z.string().email(), age: z.number().int().min(0).max(150).optional(), }); // Infer TypeScript type from schema type User = z.infer<typeof UserSchema>; // Validate data (throws on error) const user = UserSchema.parse(untrustedData); // Safe validation (returns result object) const result = UserSchema.safeParse(untrustedData); if (result.success) { console.log(result.data); // User } else { console.error(result.error.issues); } // Common patterns const ConfigSchema = z.object({ apiUrl: z.string().url(), timeout: z.number().default(5000), retries: z.number().min(0).max(10).default(3), }); // Transform and validate const EmailSchema = z.string().email().transform((val) => val.toLowerCase());
Async/Await
// Async function with proper typing async function fetchUser(userId: string): Promise<User | undefined> { const response = await fetch(`/api/users/${userId}`); if (!response.ok) return undefined; return response.json() as Promise<User>; } // Use Promise.all for concurrent operations async function fetchAllUsers(userIds: string[]): Promise<User[]> { const users = await Promise.all(userIds.map(fetchUser)); return users.filter((user): user is User => user !== undefined); } // Handle errors with try/catch async function safeFetch<T>(url: string): Promise<Result<T>> { try { const response = await fetch(url); const data = await response.json(); return { success: true, data }; } catch (error) { return { success: false, error: String(error) }; } }
Error Handling
// Custom error classes for domain errors class UserNotFoundError extends Error { constructor(public readonly userId: string) { super(`User not found: ${userId}`); this.name = "UserNotFoundError"; } } // Strict vs optional returns function getUserStrict(userId: string): User { const user = repository.get(userId); if (!user) throw new UserNotFoundError(userId); return user; } function getUserOptional(userId: string): User | undefined { return repository.get(userId); } // Result type for explicit error handling type Result<T, E = Error> = | { ok: true; value: T } | { ok: false; error: E };
Comments - Less is More
// BAD - redundant comment // Get the user from database const user = repository.getUser(userId); // GOOD - self-explanatory code, no comment needed const user = repository.getUser(userId); // GOOD - comment explains WHY (not obvious) // Rate limit: API allows max 1000 requests/min await rateLimiter.acquire();
Recommended Tooling
| Tool | Purpose |
|---|---|
or | Package manager (faster than npm) |
| Linting with TypeScript rules |
| Code formatting |
or | Testing framework |
or | TypeScript execution |
| Runtime validation with type inference |
tsconfig.json Recommendations
Note: These are strict settings for new projects. For existing codebases, enable incrementally.
{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "exactOptionalPropertyTypes": true, "noPropertyAccessFromIndexSignature": true, "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true } }
Production Best Practices
- Strict mode - Enable
in tsconfig.jsonstrict: true - Explicit return types - Always declare return types for public functions
- Avoid any - Use
and type guards insteadunknown - Readonly by default - Use
andreadonly
for immutable dataas const - Discriminated unions - For state management and result types
- Dependency injection - Pass dependencies explicitly
- Custom errors - Domain-specific error classes
- Environment variables - Type-safe config with validation (zod, env-var)
- Barrel exports - Use index.ts for clean imports
- Path aliases - Configure
paths in tsconfig for cleaner imports@/
References
- Utility Types, Discriminated Unions, and Zod sections inspired by moai-lang-typescript by AJBcoding