# Typescript

> TypeScript rules - types, generics, tsconfig, decorators, and Result pattern

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

---


# TypeScript — Rules and Conventions

---

## 1. Philosophy

1. **Strict by default** — `strict: true` catches entire classes of bugs at compile time.
2. **Types as documentation** — Prefer inference, annotate only boundaries (API, props, exports).
3. **No `any`** — Use `unknown` + narrowing. `any` disables type checking.
4. **Discriminated unions over optional chains** — Model state explicitly.
5. **Result pattern for errors** — Never throw in async code. Return `Result<T, E>`.

---

## 2. Minimum Versions

| Technology | Minimum Version |
| ---------- | --------------- |
| TypeScript | 5.4+            |
| Node.js    | 22+             |
| pnpm       | 11+             |

---

## 3. tsconfig.json — Configurations

### Base config (extends all)

```json
// tsconfig.base.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
```

### App config (Vite/Astro/Next)

```json
// tsconfig.app.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "noEmit": true,
    "jsx": "react-jsx",
    "types": ["vite/client", "astro/client"]
  },
  "include": ["src/**/*", "vite.config.ts", "astro.config.mjs"],
  "exclude": ["node_modules", "dist"]
}
```

### Library config (publishing)

```json
// tsconfig.lib.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "dist/types",
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"],
  "exclude": ["**/*.test.ts", "**/*.spec.ts", "node_modules", "dist"]
}
```

### Strictness ladder (add incrementally)

```json
// tsconfig.strict.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "alwaysStrict": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "useUnknownInCatchVariables": true
  }
}
```

### Rules

- **`moduleResolution: "bundler"`** — matches Vite/esbuild/tsc behavior
- **`verbatimModuleSyntax`** — preserves `import type` / `export type`
- **`isolatedModules`** — required for esbuild/Vite transpilation
- **`noUncheckedIndexedAccess`** — `obj[key]` returns `T | undefined`
- **`exactOptionalPropertyTypes`** — distinguishes `?` from `| undefined`

---

## 4. Type System Essentials

### Inference first, annotate boundaries

```ts
// ✅ Good: inference
const users = await fetchUsers()
const active = users.filter(u => u.active)

// ✅ Annotate: function boundaries
async function fetchUsers(): Promise<User[]> { ... }
function processUser(user: User): ProcessedUser { ... }

// ❌ Bad: redundant annotations
const count: number = 5
const name: string = "Alice"
```

### `interface` vs `type`

| Use `interface`                | Use `type`                    |
| ------------------------------ | ----------------------------- |
| Object shapes, class contracts | Unions, intersections, tuples |
| Declaration merging needed     | Mapped/conditional types      |
| Public API surfaces            | Internal aliases              |

```ts
// interface: extensible, mergeable
interface User {
  id: string;
  name: string;
}
interface User {
  email: string;
} // merged

// type: algebraic, precise
type User = { id: string; name: string } & { email: string };
type Status = "loading" | "success" | "error";
type Tuple = [string, number];
```

### Generics — constraints and defaults

```ts
// Constraint
function pick<T extends { id: string }>(items: T[], id: string): T | undefined {
  return items.find((i) => i.id === id);
}

// Default
interface Repository<T = User> {
  find(id: string): Promise<T | null>;
  save(entity: T): Promise<void>;
}

// Variance (advanced)
type Producer<out T> = () => T; // covariant (return only)
type Consumer<in T> = (value: T) => void; // contravariant (arg only)
```

### Utility types (essential only)

| Utility          | Use Case              |
| ---------------- | --------------------- |
| `Partial<T>`     | All props optional    |
| `Required<T>`    | All props required    |
| `Pick<T, K>`     | Subset of keys        |
| `Omit<T, K>`     | Exclude keys          |
| `Record<K, T>`   | Object map            |
| `ReturnType<Fn>` | Function return type  |
| `Parameters<Fn>` | Function param tuple  |
| `Awaited<P>`     | Unwrap Promise        |
| `NonNullable<T>` | Remove null/undefined |

---

## 5. Type Narrowing

### Type guards

```ts
// User-defined guard
function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}

// Usage
const data: unknown = await fetchData();
if (isUser(data)) {
  data.id; // narrowed to User
}
```

### Discriminated unions (preferred)

```ts
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

function handle<T, E>(result: Result<T, E>): void {
  if (result.ok) {
    console.log(result.value); // T
  } else {
    console.error(result.error); // E
  }
}
```

### `never` and `unknown`

```ts
// Exhaustiveness check
function assertNever(value: never): never {
  throw new Error(`Unexpected: ${value}`);
}

function process(status: Status): void {
  switch (status) {
    case "loading":
      return;
    case "success":
      return;
    case "error":
      return;
    default:
      assertNever(status); // compile error if new status added
  }
}
```

---

## 6. Advanced Types

### Mapped types

```ts
// Make all properties optional and nullable
type PartialNullable<T> = {
  [K in keyof T]?: T[K] | null;
};

// Require specific keys
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;

// Readonly recursively
type DeepReadonly<T> = {
  readonly [K in keyof T]: DeepReadonly<T[K]>;
};
```

### Conditional types

```ts
// Non-nullable
type NonNullable<T> = T extends null | undefined ? never : T;

// Flatten promises
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`;
type Handler = EventName<"click" | "hover">; // "onClick" | "onHover"
```

---

## 7. Standard Decorators (TC39 Stage 3)

```ts
// Class decorator
@logged
class UserService {
  @debounce(300)
  save(user: User): void { ... }
}

// Implementation
function logged(target: Function, context: ClassDecoratorContext) {
  return class extends target {
    constructor(...args: any[]) {
      console.log("Creating", target.name)
      super(...args)
    }
  }
}

function debounce(ms: number) {
  return function (target: Function, context: ClassMethodDecoratorContext) {
    let timeout: ReturnType<typeof setTimeout>
    return function (this: any, ...args: any[]) {
      clearTimeout(timeout)
      timeout = setTimeout(() => target.apply(this, args), ms)
    }
  }
}
```

> **Experimental decorators** (`experimentalDecorators: true`) — legacy, not recommended. Use standard only.

---

## 8. Result Pattern for Errors

> Full pattern in `design-patterns` skill. Compact version here:

```ts
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

function ok<T>(value: T): Result<T, never> {
  return { ok: true, value };
}

function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

// Async helper
async function tryCatch<T, E = Error>(
  fn: () => Promise<T>,
): Promise<Result<T, E>> {
  try {
    return ok(await fn());
  } catch (e) {
    return err(e as E);
  }
}

// Usage
const result = await tryCatch(() => fetchUser(id));
if (result.ok) {
  result.value; // User
} else {
  result.error; // Error
}
```

### Rules Result Patterns

- **Never `throw` in async** — returns `Promise<never>` breaks callers
- **Return `Result`** — forces caller to handle both cases
- **Error as value** — serializable, loggable, testable

---

## 9. Declaration Files (`.d.ts`)

### Module augmentation

```ts
// types/express.d.ts
import "express";

declare module "express" {
  interface Request {
    user?: User;
  }
}
```

### Global types

```ts
// types/global.d.ts
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      DATABASE_URL: string;
      JWT_SECRET: string;
    }
  }
}

export {}; // makes this a module
```

### External package types

```ts
// types/some-untyped-pkg.d.ts
declare module "some-untyped-pkg" {
  export function doSomething(input: string): number;
  export interface Config {
    debug: boolean;
  }
}
```

### Rules Declarations File

- **`types/` folder** at project root, included in `tsconfig.json`
- **`declare module`** for augmentation — never modify `node_modules`
- **`export {}`** in globals — prevents global pollution

---

## 10. Methodology

Before using ANY TypeScript config/pattern not documented in
this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` +
   `context7_query-docs` for TypeScript.
2. **Official docs**: typescriptlang.org — verify current compiler
   options + features.
3. **Project config**: `tsconfig.json`, `tsconfig.*.json`
   — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against
   2 authoritative sources → DO NOT USE IT. Document as assumption or risk in
   report to orchestrator.

---

## 11. Prohibitions

- ❌ Do not use `any` — use `unknown` + narrowing
- ❌ Do not use `!!` (double negation) — use explicit checks
- ❌ Do not use `as Type` assertions — prefer guards/narrowing
- ❌ Do not use `interface` for unions — use `type`
- ❌ Do not use `enum` — use `const` objects + `as const`
- ❌ Do not use experimental decorators — standard only
- ❌ Do not skip `noUncheckedIndexedAccess` — catches `undefined` access
- ❌ Do not use `namespace` — use modules (`export`)
- ❌ Do not put runtime logic in types — types are erased

---

## 12. References

> **Note:** For JavaScript conventions, see [JavaScript](../javascript/SKILL.md)
> **Note:** For design patterns (Result, etc.), see
> [Design Patterns](../design-patterns/SKILL.md)
> **Note:** For package manager conventions, see
> [Package Manager](../package-manager/SKILL.md)

---

Last updated: 2026-08

