Transform hub-guide from a single-skill Hub monorepo guide into a comprehensive programming best-practice plugin covering all situations. Skills (26): - Core: engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance - Languages: typescript, python, rust, go - Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs - Infrastructure: docker, ci-cd, monitoring - Monorepo: monorepo, hub-guide (existing) Hooks: - SessionStart: auto-detect project type and activate relevant skills - PreToolUse (Write|Edit): inject language-specific rules per file type Reference files for deep dives: - clean-architecture/references/solid.md (SOLID + component principles) - design-patterns/references/catalog.md (full GoF catalog with examples) - testing/references/mocks.md (test double taxonomy) Restructure plugin to modern skills/ directory format.
5.7 KiB
5.7 KiB
name, description
| name | description |
|---|---|
| typescript | TypeScript best practices — strict mode, type patterns, generics, module system, async patterns, and project organization. Use when writing TypeScript code, configuring tsconfig, or whenever the user mentions "TypeScript," "TS," "ESM," "deno," "bun," "type annotation," "generics," "interface," "type," "strict mode," or "tsconfig." |
TypeScript Best Practices
Configuration
// tsconfig.json — strict mode is non-negotiable
{
"compilerOptions": {
"strict": true, // Enable all strict checks
"noUncheckedIndexedAccess": true, // Accessing arrays/objects is safe
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"esModuleInterop": true,
"moduleResolution": "bundler", // or "node16" for ESM
"target": "ESNext",
"isolatedModules": true,
"skipLibCheck": true // Fast, skip node_modules type check
}
}
Types vs Interfaces
// Prefer `interface` for public API contracts (extends, implements)
interface User {
id: string;
email: string;
}
// Prefer `type` for unions, intersections, complex types
type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
type Status = 'active' | 'inactive' | 'suspended';
type CreateUserInput = { name: string } & BaseInput;
Rule of thumb: interface for objects, type for everything else.
Generics — Use Intentionally
// ✅ Good — specific enough
function mapValues<K extends string, V, R>(
obj: Record<K, V>,
fn: (value: V, key: K) => R
): Record<K, R> { ... }
// ❌ Bad — over-generic, loses type info
function identity(value: any): any { ... }
// ✅ Good — preserves type
function identity<T>(value: T): T { return value; }
Async Patterns
- Top-level await — OK in ESM modules, but prefer
main()pattern. - Promise.all for parallel — don't
awaitin sequence if requests are independent. - Errors — always handle Promise rejections. No unhandled promises.
- Async iterators —
for await (const item of stream)for paginated data.
// ❌ Sequential
const a = await fetchA();
const b = await fetchB(); // waits for A
// ✅ Parallel
const [a, b] = await Promise.all([fetchA(), fetchB()]);
No any
// ❌ Never — disables all type checks
function parse(input: any): any { ... }
// ✅ Use `unknown` instead — forces type narrowing before use
function parse(input: unknown): Result<Data, ParseError> { ... }
// ✅ Use `never` for exhaustive checks
function assertNever(x: never): never { throw new Error('Unexpected: ' + x); }
Module System
- ESM only. No
require(). Useimport/export. - Named exports over default exports (better tree-shaking, rename safety).
- Barrel exports (
index.ts) — use sparingly. Can cause circular deps and slow builds. - Path aliases —
@/or~/for internal imports. Configure in tsconfig.
// Always use .js/.mjs extension for relative imports in ESM
import { User } from './user.js';
import { createOrder } from '@/use-cases/create-order.js';
Project Structure
src/
├── domain/ # Pure business logic, entities, value objects
│ ├── user.ts
│ └── order.ts
├── application/ # Use cases, ports
│ ├── create-order.ts
│ └── user-repository.ts # port (interface)
├── infrastructure/ # Adapters (DB, HTTP, queue)
│ └── postgres-user-repo.ts
├── api/ # HTTP handlers, middleware
│ ├── routes/
│ └── middleware/
├── lib/ # Shared utilities (pure, no framework deps)
└── index.ts # Composition root, entry point
Enum vs Union
// ❌ Enums — runtime overhead, not tree-shakeable, const enum has issues
enum Status { Active, Inactive }
// ✅ Union types — zero-cost, works everywhere
type Status = 'active' | 'inactive';
// ✅ Const objects with `as const` — when you need both type and runtime
const STATUS = { ACTIVE: 'active', INACTIVE: 'inactive' } as const;
type Status = (typeof STATUS)[keyof typeof STATUS];
Branded Types for IDs
// Type-safe IDs — prevents mixing up different entity IDs
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function getUser(id: UserId) { ... }
function getOrder(id: OrderId) { ... }
getUser('abc' as UserId); // OK
getUser(orderId); // ❌ Type error — OrderId not assignable to UserId
Error Handling
// Domain errors as discriminated unions
type CreateUserError = ValidationError | DuplicateEmailError | DatabaseError;
type ValidationError = { kind: 'validation'; field: string; message: string };
type DuplicateEmailError = { kind: 'duplicate_email'; email: string };
function createUser(input: CreateUserInput): Result<User, CreateUserError> { ... }
// Use branded Result in use cases, throw for unexpected infrastructure errors
class DatabaseError extends Error { constructor(cause: unknown) { super(); this.cause = cause; } }
Testing
- Vitest over Jest (faster, ESM-native, TypeScript-native).
vi.fn()for mocks. Prefer fakes (in-memory implementations).- Cover 'as const',
satisfies,z.inferpatterns — they are compile-time only.
Anti-patterns
- ❌
any— disables the type system entirely - ❌
astype assertions (value as Type) — use proper narrowing or Zod parsing - ❌
!non-null assertion (user!.name) — defeats strict null checks - ❌ Namespace — use ES modules instead
- ❌
Functiontype — use typed function signature(args: Args) => Result - ❌ Optional chaining chains —
a?.b?.c?.dis fragile. Narrow earlier.