# Typescript

> Enforces strict TypeScript guardrails whenever writing, reviewing, refactoring, or generating .ts/.tsx/.mts/.cts code. Applies automatically to any TypeScript task — creating functions, components, types, configs, utility libraries, or converting JS to TS. Catches violations like enum (use as const), any (use unknown), default exports, barrel files, empty catch blocks, var, @ts-ignore, missing return types, and non-readonly properties. Also triggers for tsconfig changes, React/JSX with TypeScript props, API clients, CLI tools, and code reviews mentioning type safety. If the file extension or code context is TypeScript, use this skill — even if the user doesn't say 'TypeScript' explicitly.

- Skill: `crustacean-dev/typescript` (Agent Skill)
- Install (CLI): `npx skillmds@latest add crustacean-dev/typescript`
- Raw SKILL.md: https://api.skillmd.com/api/skills/crustacean-dev/typescript/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: crustacean-dev (https://skillmd.com/u/crustacean-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/crustacean-dev/typescript

---


# TypeScript Guardrails

These are hard rules. Check every piece of TypeScript you write, review, or modify against this list.

---

## Types

- **DO NOT** use `enum`. Use `as const` objects + inferred type instead.
  ```ts
  // wrong
  enum Status { Active, Inactive }

  // correct
  const STATUS = { Active: 'active', Inactive: 'inactive' } as const;
  type Status = typeof STATUS[keyof typeof STATUS];
  ```

- **DO NOT** use `any`. Use `unknown` with type guards to narrow.

- **Prefer `type` over `interface`** unless you need declaration merging, `extends`, or `implements`.

- **DO NOT** use type assertions (`as X`) unless no alternative exists. Narrow with control flow instead. If unavoidable, add a comment explaining why.
  - **Only exception**: `as Record<string, unknown>` inside type guards, to access properties on a narrowed object. No other `as` casts — validate with `typeof`, `in`, or equality checks. When checking membership in a const object, widen the array type to `ReadonlyArray<string>` instead of casting the value:
    ```ts
    function isUser(value: unknown): value is User {
      if (typeof value !== 'object' || value === null) return false;
      const obj = value as Record<string, unknown>; // only acceptable assertion
      const validRoles: ReadonlyArray<string> = Object.values(USER_ROLE);
      return typeof obj.name === 'string' && typeof obj.role === 'string' && validRoles.includes(obj.role);
      // ↑ widen to ReadonlyArray<string> — do NOT cast obj.role as SomeType
    }
    ```

- **Use `satisfies`** for type-safe object literals where you want inference + validation.
  ```ts
  const config = { port: 3000, host: 'localhost' } satisfies ServerConfig;
  ```

- **Explicit return types** on all exported functions.

---

## Imports / Exports

- **No barrel files** (`index.ts` re-exports) except designated package entry points.

- **Explicit import paths** — `import { bar } from './utils/bar'`, not `from './utils'`.

- **No default exports** except config files (`vite.config.ts`, `eslint.config.ts`, etc.). Use named exports everywhere else.

- **ESM only** — `import`/`export`. Never `require()` or `module.exports`.

---

## Strict Mode

- **`"strict": true`** in tsconfig — always. Do not loosen individual strict flags.

- **`"noUncheckedIndexedAccess": true`** when the project supports it.

- **DO NOT** use `@ts-ignore`. Use `@ts-expect-error` with an explanation comment if suppression is truly needed.

- **DO NOT** use non-null assertions (`!`) unless provably safe. If used, add a comment explaining the proof.

---

## Patterns

- **`const` by default.** `let` only when reassignment is needed. Never `var`.

- **Exhaustive switches** — always include a `default` case with a `never` check:
  ```ts
  default: {
    const _exhaustive: never = value;
    throw new Error(`Unhandled case: ${_exhaustive}`);
  }
  ```

- **Discriminated unions over type predicates** when modeling variants.

- **Readonly by default** — use `readonly` properties, `ReadonlyArray<T>`, `Readonly<T>`.

- **No classes** unless required by a framework (e.g., Angular, NestJS). Prefer functions + closures.

---

## Naming

| Thing                | Convention         | Example              |
|----------------------|--------------------|----------------------|
| Files                | kebab-case         | `user-service.ts`    |
| Types / Interfaces   | PascalCase         | `UserProfile`        |
| Functions / variables | camelCase         | `getUserById`        |
| Constants            | UPPER_SNAKE_CASE   | `MAX_RETRY_COUNT`    |
| Schema interfaces    | `<Name>Schema`     | `UserSchema`         |

---

## Error Handling

- **Never swallow errors** — no empty `catch` blocks. Log, rethrow, or handle explicitly.

- **Typed errors** — use custom error classes or discriminated union result types.

- **Avoid `throw` in library code** when possible. Prefer a Result pattern or explicit error returns:
  ```ts
  type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
  ```

