# TypeScript Style Guide

**Authority**: This guide is MANDATORY for all TypeScript code. No exceptions without explicit approval.

Based on Google TypeScript Style Guide with stricter enforcement.

---

## Core Principles

1. **Explicit over implicit** — Always declare types, never rely on inference for public APIs
2. **Specific over generic** — Use the narrowest type that works
3. **Safe over convenient** — Type safety is not negotiable
4. **Contract-first boundaries** — Cross-module and API payloads MUST use dedicated DTO files

---

## DTO Contract (MANDATORY)

DTO files are REQUIRED for TypeScript module boundaries to preserve shared context and consistency.

Hard requirements:

1. Input and output payloads crossing module boundaries MUST be defined in `*.dto.ts` files.
2. Controller/service boundary payloads MUST use DTO types; inline object literal types are NOT allowed.
3. Public API request/response contracts MUST use DTO files and remain stable across modules.
4. Shared DTOs used by multiple modules MUST live in a shared location (for example `src/shared/dto/` or `packages/shared/dto/`).
5. ORM/entity models MUST NOT be exposed directly across module boundaries; map them to DTOs.
6. DTO changes MUST be reflected in tests and documentation when contracts change.

```typescript
// ❌ WRONG: inline payload contract at boundary
export function createUser(payload: { email: string; role: string }): Promise<User> {}

// ✅ CORRECT: dedicated DTO file contract
// user-create.dto.ts
export interface UserCreateDto {
  email: string;
  role: UserRole;
}

// user-response.dto.ts
export interface UserResponseDto {
  id: string;
  email: string;
  role: UserRole;
}

// service.ts
export function createUser(payload: UserCreateDto): Promise<UserResponseDto> {}
```

---

## Forbidden Patterns (NEVER USE)

### `any` Type — FORBIDDEN

```typescript
// ❌ NEVER
function process(data: any) {}
const result: any = fetchData();
Record<string, any>;

// ✅ ALWAYS define explicit types
interface UserData {
  id: string;
  name: string;
  email: string;
}
function process(data: UserData) {}
```

### `unknown` as Lazy Typing — FORBIDDEN

`unknown` is only acceptable in these specific cases:

1. Error catch blocks (then immediately narrow)
2. JSON.parse results (then validate with Zod/schema)
3. External API responses before validation

```typescript
// ❌ NEVER - using unknown to avoid typing
function getData(): unknown {}
const config: Record<string, unknown> = {};

// ✅ ACCEPTABLE - error handling with immediate narrowing
try {
  riskyOperation();
} catch (error: unknown) {
  if (error instanceof Error) {
    logger.error(error.message);
  } else {
    logger.error('Unknown error', { error: String(error) });
  }
}

// ✅ ACCEPTABLE - external data with validation
const raw: unknown = JSON.parse(response);
const validated = UserSchema.parse(raw); // Zod validation
```

### Implicit `any` — FORBIDDEN

```typescript
// ❌ NEVER - implicit any from missing types
function process(data) {} // Parameter has implicit any
const handler = (e) => {}; // Parameter has implicit any

// ✅ ALWAYS - explicit types
function process(data: RequestPayload): ProcessedResult {}
const handler = (e: React.MouseEvent<HTMLButtonElement>): void => {};
```

### Type Assertions to Bypass Safety — FORBIDDEN

```typescript
// ❌ NEVER - lying to the compiler
const user = data as User;
const element = document.getElementById('app') as HTMLDivElement;

// ✅ USE - type guards and narrowing
function isUser(data: unknown): data is User {
  return typeof data === 'object' && data !== null && 'id' in data;
}
if (isUser(data)) {
  console.log(data.id); // Safe
}

// ✅ USE - null checks
const element = document.getElementById('app');
if (element instanceof HTMLDivElement) {
  element.style.display = 'none'; // Safe
}
```

### Non-null Assertion (`!`) — FORBIDDEN (except tests)

```typescript
// ❌ NEVER in production code
const name = user!.name;
const element = document.getElementById('app')!;

// ✅ USE - proper null handling
const name = user?.name ?? 'Anonymous';
const element = document.getElementById('app');
if (element) {
  // Safe to use element
}
```

---

## Required Patterns

### Explicit Return Types — REQUIRED for all public functions

```typescript
// ❌ WRONG - missing return type
export function calculateTotal(items: Item[]) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// ✅ CORRECT - explicit return type
export function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}
```

### Explicit Parameter Types — REQUIRED always

```typescript
// ❌ WRONG
const multiply = (a, b) => a * b;
users.map((user) => user.name); // If user type isn't inferred

// ✅ CORRECT
const multiply = (a: number, b: number): number => a * b;
users.map((user: User): string => user.name);
```

### Interface Over Type Alias — PREFERRED for objects

```typescript
// ✅ PREFERRED - interface (extendable, better error messages)
interface User {
  id: string;
  name: string;
  email: string;
}

// ✅ ACCEPTABLE - type alias for unions, intersections, primitives
type Status = 'active' | 'inactive' | 'pending';
type ID = string | number;
```

### Const Assertions for Literals — REQUIRED

```typescript
// ❌ WRONG - loses literal types
const config = {
  endpoint: '/api/users',
  method: 'GET',
};
// config.method is string, not 'GET'

// ✅ CORRECT - preserves literal types
const config = {
  endpoint: '/api/users',
  method: 'GET',
} as const;
// config.method is 'GET'
```

### Discriminated Unions — REQUIRED for variants

```typescript
// ❌ WRONG - optional properties for variants
interface ApiResponse {
  success: boolean;
  data?: User;
  error?: string;
}

// ✅ CORRECT - discriminated union
interface SuccessResponse {
  success: true;
  data: User;
}
interface ErrorResponse {
  success: false;
  error: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
```

---

## Generic Constraints

### Meaningful Constraints — REQUIRED

```typescript
// ❌ WRONG - unconstrained generic
function merge<T>(a: T, b: T): T {}

// ✅ CORRECT - constrained generic
function merge<T extends object>(a: T, b: Partial<T>): T {}
```

### Default Generic Parameters — USE SPECIFIC TYPES

```typescript
// ❌ WRONG
interface Repository<T = unknown> {}

// ✅ CORRECT - no default if type should be explicit
interface Repository<T extends Entity> {}

// ✅ ACCEPTABLE - meaningful default
interface Cache<T extends Serializable = JsonValue> {}
```

---

## React/JSX Specific

### Event Handlers — EXPLICIT TYPES REQUIRED

```typescript
// ❌ WRONG
const handleClick = (e) => {};
const handleChange = (e) => {};

// ✅ CORRECT
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {};
```

### Component Props — INTERFACE REQUIRED

```typescript
// ❌ WRONG - inline types
function Button({ label, onClick }: { label: string; onClick: () => void }) { }

// ✅ CORRECT - named interface
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

function Button({ label, onClick, disabled = false }: ButtonProps): JSX.Element {
  return <button onClick={onClick} disabled={disabled}>{label}</button>;
}
```

### Children Prop — USE React.ReactNode

```typescript
interface LayoutProps {
  children: React.ReactNode;
  sidebar?: React.ReactNode;
}
```

---

## API Response Typing

### Define Explicit Response Types

```typescript
// ❌ WRONG
const response = await fetch('/api/users');
const data = await response.json(); // data is any

// ✅ CORRECT
interface UsersResponse {
  users: User[];
  pagination: PaginationInfo;
}

const response = await fetch('/api/users');
const data: UsersResponse = await response.json();

// ✅ BEST - with runtime validation
const response = await fetch('/api/users');
const raw = await response.json();
const data = UsersResponseSchema.parse(raw); // Zod validates at runtime
```

---

## Error Handling

### Typed Error Classes — REQUIRED for domain errors

```typescript
class ValidationError extends Error {
  constructor(
    message: string,
    public readonly field: string,
    public readonly code: string,
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

class NotFoundError extends Error {
  constructor(
    public readonly resource: string,
    public readonly id: string,
  ) {
    super(`${resource} with id ${id} not found`);
    this.name = 'NotFoundError';
  }
}
```

### Error Narrowing — REQUIRED

```typescript
try {
  await saveUser(user);
} catch (error: unknown) {
  if (error instanceof ValidationError) {
    return { error: error.message, field: error.field };
  }
  if (error instanceof NotFoundError) {
    return { error: 'Not found', resource: error.resource };
  }
  if (error instanceof Error) {
    logger.error('Unexpected error', { message: error.message, stack: error.stack });
    return { error: 'Internal error' };
  }
  logger.error('Unknown error type', { error: String(error) });
  return { error: 'Internal error' };
}
```

---

## ESLint Rules — ENFORCE THESE

```javascript
{
  "@typescript-eslint/no-explicit-any": "error",
  "@typescript-eslint/explicit-function-return-type": ["error", {
    "allowExpressions": true,
    "allowTypedFunctionExpressions": true
  }],
  "@typescript-eslint/explicit-module-boundary-types": "error",
  "@typescript-eslint/no-inferrable-types": "off", // Allow explicit primitives
  "@typescript-eslint/no-non-null-assertion": "error",
  "@typescript-eslint/strict-boolean-expressions": "error",
  "@typescript-eslint/no-unsafe-assignment": "error",
  "@typescript-eslint/no-unsafe-member-access": "error",
  "@typescript-eslint/no-unsafe-call": "error",
  "@typescript-eslint/no-unsafe-return": "error"
}
```

---

## TSConfig Strict Mode — REQUIRED

```json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "useUnknownInCatchVariables": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true
  }
}
```

---

## Summary: The Type Safety Hierarchy

From best to worst:

1. **Explicit specific type** (interface/type) — REQUIRED
2. **Generic with constraints** — ACCEPTABLE
3. **`unknown` with immediate validation** — ONLY for external data
4. **`any`** — FORBIDDEN

**When in doubt, define an interface.**
