Skip to content
Development
Skill

/typescript

Apply when writing TypeScript code. Strict types, discriminated unions, async patterns, and runtime safety.

From plugin
skill-everything
2123 skills

Claude Code

$ npx -y skills add sordi-ai/skill-everything --skill typescript --agent claude-code

SKILL.md

typescript.SKILL.md
name: typescript
description: Apply when writing TypeScript code. Strict types, discriminated unions, async patterns, and runtime safety.
license: MIT
version: 1.0.0
tokens_target: 2500
triggers:
  - typescript code
  - strict types
  - async typescript
loads_after:
  - code-quality
supersedes: []

Sub-Skill: TypeScript Best Practices

<!-- target: ~2500 tokens (real tiktoken count) | 17 rules with severity classification -->

**Purpose:** Prevents the TypeScript-specific mistakes LLMs make repeatedly — weak types, unsafe assertions, and patterns that compile but fail at runtime.

Rule classification

  • **MUST** — load-bearing. Violating leaks runtime errors past the type checker. Never break.
  • **SHOULD** — default behavior. Deviation needs a documented reason in the code or PR.
  • **AVOID** — usually wrong; documented exception inline where needed.

**Where these rules don't strictly apply:** test fixtures, generated types (e.g. from GraphQL codegen, OpenAPI generators, Prisma), declaration files (`*.d.ts`) for untyped third-party libraries, and migration scripts may legitimately differ. The rules below apply to **production application code**.

---

Type Safety

1. **MUST: Never use `any`. Use `unknown` and narrow it.** `any` disables the type checker entirely. `unknown` forces you to prove the type before use. *Exception: third-party libraries without types and explicit dynamic-data boundaries (e.g. JSON parse at the API edge), with a comment explaining why.*

   // Wrong
   function parse(data: any) { return data.name; }

   // Correct
   function parse(data: unknown): string {
     if (typeof data === 'object' && data !== null && 'name' in data) {
       return String((data as { name: unknown }).name);
     }
     throw new Error('Invalid data shape');
   }

2. **AVOID: `Object` or `{}` as a type.** Both accept nearly everything. Use `Record<string, unknown>` for arbitrary objects or define an explicit interface.

   // Wrong
   function merge(a: {}, b: Object): {} { ... }

   // Correct
   function merge<T extends Record<string, unknown>>(a: T, b: Partial<T>): T { ... }

3. **SHOULD: Use `as` only when you know more than the compiler — and document why.** Prefer type guards or `satisfies` instead.

   // Wrong — silences the error, hides the bug
   const user = response.data as User;

   // Correct — validate first
   function isUser(v: unknown): v is User {
     return typeof v === 'object' && v !== null && 'id' in v && 'email' in v;
   }
   const user = isUser(response.data) ? response.data : null;

4. **SHOULD: Mark immutable data `readonly`.** Prevents accidental mutation and communicates intent.

   // Avoid
   function process(ids: string[]) { ids.push('extra'); }

   // Prefer
   function process(ids: readonly string[]) { /* ids.push() is a compile error */ }

5. **MUST: Enable `strictNullChecks` and handle every `T | undefined`.** Optional chaining `?.` returns `undefined` — always handle that branch.

   // Wrong
   const name = user?.profile.name.toUpperCase(); // crashes if name is undefined

   // Correct
   const name = user?.profile.name?.toUpperCase() ?? 'Anonymous';

6. **SHOULD: Use branded types for IDs.** Prevents passing a `UserId` where an `OrderId` is expected — both are `string` at runtime.

   type UserId = string & { readonly _brand: 'UserId' };
   type OrderId = string & { readonly _brand: 'OrderId' };

   function createUserId(raw: string): UserId { return raw as UserId; }

   function getUser(id: UserId): User { ... }
   // getUser(orderId) → compile error

---

Patterns

7. **SHOULD: Use discriminated unions for state, not optional fields.** Optional fields force you to reason about all combinations. A discriminated union makes illegal states unrepresentable.

   // Avoid — 8 possible combinations, most invalid
   type Request = { loading?: boolean; data?: User; error?: Error };

   // Prefer — exactly 3 valid states
   type Request =
     | { status: 'idle' }
     | { status: 'loading' }
     | { status: 'success'; data: User }
     | { status: 'error'; error: Error };

8. **SHOULD: Use `satisfies` to validate shape without widening the type.** `as const` preserves literals; `satisfies` validates against an interface without losing them.

   const config = {
     host: 'localhost',
     port: 5432,
   } satisfies DatabaseConfig;
   // config.port is still typed as 5432, not number

9. **SHOULD: Use `const` objects instead of `enum`.** Enums emit runtime code, have surprising reverse-mapping behavior, and are not idiomatic TypeScript.

   // Avoid
   enum Direction { Up, Down, Left, Right }

   // Prefer
   const Direction = { Up: 'Up', Down: 'Down', Left: 'Left', Right: 'Right' } as const;
   type Direction = typeof Direction[keyof typeof Direction];

10. **AVOID: Barrel `index.ts` re-exports in large modules.** They cause circular dependency chains that are hard to debug. Export directly from source files or use explicit named re-exports only.

    // Wrong — index.ts re-exports everything, A imports B through index, B imports A through index
    export * from './userService';
    export * from './orderService';

    // Correct — import directly
    import { getUser } from './services/userService';

---

Error Handling

11. **MUST: Type your thrown errors explicitly.** `catch (e)` gives you `unknown` in strict mode. Narrow before accessing properties.

    try {
      await fetchUser(id);
    } catch (e) {
      // Wrong: e.message — e is unknown
      // Correct:
      const message = e instanceof Error ? e.message : String(e);
      logger.error('fetchUser failed', { message, id });
    }

12. **SHOULD: Use `Result` types for expected failures instead of throwing.** Throwing for control flow forces callers to know which functions throw and what.

    typ
Read more
@sordi-ai
Ships withskill-everything

Git-versioned agent memory: agents that never make the same mistake twice. Anthropic-Skill folder standard, multi-runtime (Claude Code, Cursor, Gemini CLI, OpenCode).

Stars on GitHub

Momentum

stars in total
21
Star history of sordi-ai/skill-everything on GitHub, a chart by star-history.com

Other skills on skill-everything.

code-quality

code-quality

Apply when writing or refactoring code. Generic rules to prevent the most common review comments — function length, naming, error…

db-schema

db-schema

Apply when designing database schemas, writing migrations, or reviewing table structure. Covers naming, keys, indexes,…

debugging

debugging

Apply when diagnosing a bug, reproducing a failure, or performing root cause analysis. Covers systematic isolation, binary…

docker

docker

Apply when writing or reviewing Dockerfiles, docker compose files, or container build pipelines. Covers layer caching,…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about typescript.

Which coding agents can use typescript?
typescript installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.