agentleFS
michaelsvanbeekmichaelsvanbeek/personal-agent-skills/skills/typescript/SKILL.md

typescript

Skill1 starsChanged 7 months agoMITView on GitHub

TypeScript coding standards and type safety conventions. Use when: creating TypeScript files, defining interfaces and types, writing type-safe code, reviewing TypeScript for type correctness, auditing a codebase for type safety gaps, eliminating any or ts-ignore usage, or improving strict-mode compliance. Covers strict typing, avoiding any and ts-ignore, discriminated unions, Zod runtime validation, immutability patterns, and proper type definitions.

66 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use msw, rust and 9 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • thongdn-itSkill

    msw

    MSW (Mock Service Worker) best practices for API mocking in tests (formerly test-msw). This skill should be used when setting up MSW, writing request handlers, or mocking HTTP APIs. This skill does NOT cover general testing patterns (use test-vitest or test-tdd skills) or test methodology.

    Also in 3 projects that use this

  • MicrosoftSkill

    rust

    Use this guide when working on Rust changes in the FAST monorepo.

    Also in 9 projects that use this

  • SupabaseSkill

    react-hook-form

    Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions,

    Also in 3 projects that use this

  • VercelSkill

    react

    React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.

    Also in 10 projects that use this

  • leonaaardobSkill

    drizzle

    Complete Drizzle ORM documentation in markdown format. Use when working with Drizzle ORM - covers schema definition, queries, migrations, database connections (PostgreSQL, MySQL, SQLite), integrations (Neon, Supabase, PlanetScale, Cloudflare D1, Turso), column types, relations, transactions, and framework usage (Next.js, SvelteKit, Astro).

    Also in 3 projects that use this

  • thongdn-itSkill

    tailwind

    Tailwind CSS v4 performance optimization and best practices guidelines (formerly tailwindcss-v4-style). This skill should be used when writing, reviewing, or refactoring Tailwind CSS v4 code to ensure optimal build performance, minimal CSS output, and correct usage of v4 features. Triggers on tasks involving Tailwind configuration, @theme directive, utility classes, responsive design, dark mode, container queries, or CSS generation optimization.

    Also in 5 projects that use this

  • +1 more from thongdn-it

The file

Show its rules and text

We split this skill into the rules it gives an agent

17 rules. 0 are common, 4 are distinctive, and 13 are very specific to this project.

  • 0

    Common

    Most of the 52 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 13

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Create valid, well-defined types for all data structures.

    line 18
  2. Very specific:

    Never use any type: - use unknown if the type is truly unknown, then narrow with type guards.

    line 20
  3. Very specific:

    Prefer interface for object shapes that may be extended.

    line 21
  4. Very specific:

    Use discriminated unions for any type that can be one of several variants.

    line 48
  5. Distinctive:

    Every variant must include the discriminator field.

    line 94
  6. Very specific:

    Use switch with exhaustive checks — add a default: never assertion to catch unhandled variants:

    line 95
  7. Distinctive:

    Use Zod to validate data at system boundaries — API responses, config files, user input, environment variables.

    line 114
  8. Very specific:

    Use readonly on interface properties that should not be mutated after creation.

    line 206
  9. Very specific:

    Use ReadonlyArray<T> or readonly T[] for arrays that should not be modified.

    line 207
  10. Very specific:

    Use Readonly<T> for shallow immutability, DeepImmutable<T> for state shared across boundaries.

    line 208
  11. Very specific:

    Update immutable state with spread: { ...prev, field: newValue }, never with assignment.

    line 209
  12. Very specific:

    Check signal.aborted before expensive operations in long-running loops.

    line 240
  13. Very specific:

    Prefer readonly for properties that should not be mutated.

    line 246
  14. Very specific:

    Use as const for literal type inference on constants.

    line 247
  15. Very specific:

    Use satisfies operator to validate types while preserving inference:

    line 249
  16. Very specific:

    Use barrel exports (index.ts) sparingly and only at module boundaries.

    line 284
  17. Distinctive:

    Prefer named exports over default exports for better refactoring support.

    line 285

“Common” is measured: kept by at least 6 in 10 of the 52 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

TypeScript Standards

When to Use

  • Creating or modifying TypeScript files
  • Defining types, interfaces, or type utilities
  • Reviewing TypeScript code for type safety

Type Safety

  • Create valid, well-defined types for all data structures.
  • Never use @ts-ignore - fix the underlying type issue instead.
  • Never use any type - use unknown if the type is truly unknown, then narrow with type guards.
  • Prefer interface for object shapes that may be extended; use type for unions, intersections, and computed types.

Type Definitions

// Prefer interfaces for object shapes
interface User {
  id: string;
  name: string;
  email: string;
  role: UserRole;
}

// Use type for unions and computed types
type UserRole = "admin" | "editor" | "viewer";
type UserMap = Record<string, User>;

// Use generics for reusable patterns
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

Discriminated Unions

Use discriminated unions for any type that can be one of several variants. Add a literal type field as the discriminator:

// Define variants with a shared discriminator field
interface LoadingState {
  type: "loading";
}

interface SuccessState<T> {
  type: "success";
  data: T;
}

interface ErrorState {
  type: "error";
  error: string;
  retryable: boolean;
}

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

// TypeScript narrows automatically in switch/if blocks
function render(state: AsyncState<User[]>) {
  switch (state.type) {
    case "loading":
      return <Spinner />;
    case "success":
      return <UserList users={state.data} />; // data is typed as User[]
    case "error":
      return <ErrorMessage message={state.error} />;
  }
}

When to Use Discriminated Unions

Use caseExample
State machinesidle → loading → success | error
Command/event types{ type: "create" } | { type: "update" } | { type: "delete" }
API responsesDifferent shapes per endpoint or error type
Configuration variants{ transport: "stdio" } | { transport: "http", url: string }

Rules

  • The discriminator field must be a string literal type, not a broad string.
  • Every variant must include the discriminator field.
  • Use switch with exhaustive checks — add a default: never assertion to catch unhandled variants:
function assertNever(x: never): never {
  throw new Error(`Unexpected variant: ${JSON.stringify(x)}`);
}

function handleCommand(cmd: Command) {
  switch (cmd.type) {
    case "create": return handleCreate(cmd);
    case "update": return handleUpdate(cmd);
    case "delete": return handleDelete(cmd);
    default: return assertNever(cmd); // compile error if a variant is missing
  }
}

Runtime Validation with Zod

Use Zod to validate data at system boundaries — API responses, config files, user input, environment variables. Zod bridges the gap between TypeScript's compile-time types and runtime reality.

import { z } from "zod";

// Define schema — this is the single source of truth
const UserSchema = z.object({
  id: z.string(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(["admin", "editor", "viewer"]),
});

// Derive the TypeScript type from the schema
type User = z.infer<typeof UserSchema>;

// Validate at runtime
function parseUser(data: unknown): User {
  return UserSchema.parse(data); // throws ZodError on invalid input
}

// Safe parse (returns result instead of throwing)
const result = UserSchema.safeParse(data);
if (result.success) {
  console.warn(result.data.name); // typed as User
} else {
  console.error(result.error.issues);
}

When to Use Zod

BoundaryExample
External API responsesParse JSON before trusting it
Configuration filesValidate settings, frontmatter, manifests
Environment variablesValidate and type env vars at startup
User inputForm data, URL params, CLI arguments
Webhook payloadsVerify structure before processing

Zod Patterns

// Discriminated union schemas
const EventSchema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
  z.object({ type: z.literal("keypress"), key: z.string() }),
]);

// Coercion for env vars (strings → typed values)
const EnvSchema = z.object({
  PORT: z.coerce.number().default(3000),
  DEBUG: z.coerce.boolean().default(false),
  DATABASE_URL: z.string().url(),
});

// Reusable schema composition
const PaginationSchema = z.object({
  limit: z.number().int().min(1).max(100).default(25),
  cursor: z.string().optional(),
});

Rules

  • Schema is the source of truth — derive TypeScript types with z.infer<>, don't duplicate.
  • Validate at boundaries, trust internally — once data passes Zod, pass the typed result through your code without re-validating.
  • Use safeParse for user-facing errors — returns structured error details. Use parse for internal assertions where failure is a bug.

Immutability Patterns

Enforce immutability at the type level to prevent accidental mutations in shared state:

// Deep immutable wrapper — prevents mutations at any depth
type DeepImmutable<T> = T extends Map<infer K, infer V>
  ? ReadonlyMap<DeepImmutable<K>, DeepImmutable<V>>
  : T extends Set<infer S>
    ? ReadonlySet<DeepImmutable<S>>
    : T extends object
      ? { readonly [K in keyof T]: DeepImmutable<T[K]> }
      : T;

// Use for state that must not be mutated
interface AppState {
  readonly settings: DeepImmutable<Settings>;
  readonly permissions: DeepImmutable<PermissionRules>;
}

Rules

  • Use readonly on interface properties that should not be mutated after creation.
  • Use ReadonlyArray<T> or readonly T[] for arrays that should not be modified.
  • Use Readonly<T> for shallow immutability, DeepImmutable<T> for state shared across boundaries.
  • Update immutable state with spread: { ...prev, field: newValue }, never with assignment.

Abort Signal / Cancellation

Propagate AbortSignal through async operations for proper cancellation:

async function fetchData(url: string, signal: AbortSignal): Promise<Data> {
  const response = await fetch(url, { signal });
  return response.json() as Promise<Data>;
}

// Type guard for abort errors (multiple sources)
function isAbortError(error: unknown): boolean {
  return (
    error instanceof DOMException && error.name === "AbortError" ||
    error instanceof Error && error.name === "AbortError"
  );
}

// Usage with cleanup
const controller = new AbortController();
try {
  const data = await fetchData("/api/users", controller.signal);
} catch (error) {
  if (isAbortError(error)) return; // expected cancellation, not an error
  throw error;
}
  • Pass AbortSignal to all async functions that support cancellation.
  • Check signal.aborted before expensive operations in long-running loops.
  • Distinguish abort errors from real failures — don't log aborts as errors.

Best Practices

  • Enable strict mode in tsconfig.json.
  • Prefer readonly for properties that should not be mutated.
  • Use as const for literal type inference on constants.
  • Leverage type narrowing with typeof, instanceof, and custom type guards over type assertions.
  • Use satisfies operator to validate types while preserving inference:
// satisfies checks the type without widening — preserves literal types
const ROUTES = {
  home: "/",
  about: "/about",
  settings: "/settings",
} as const satisfies Record<string, string>;
// ROUTES.home is typed as "/" (literal), not string

tsconfig.json

Enable strict mode and recommended compiler options:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Module Organization

  • One primary export per file. Co-locate related types in the same file.
  • Use barrel exports (index.ts) sparingly and only at module boundaries.
  • Prefer named exports over default exports for better refactoring support.

For React-specific patterns with TypeScript, TailwindCSS, and Vite, see the web-ui skill. For runtime validation at API boundaries using Zod, see the api-design skill.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.