home / skills / gentleman-programming / gentleman.dots / typescript

This skill helps you apply TypeScript strict patterns and best practices by promoting const-based enums, flat interfaces, and safe typing.

npx playbooks add skill gentleman-programming/gentleman.dots --skill typescript

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
2.4 KB
---
name: typescript
description: >
  TypeScript strict patterns and best practices.
  Trigger: When writing TypeScript code - types, interfaces, generics.
license: Apache-2.0
metadata:
  author: gentleman-programming
  version: "1.0"
---

## Const Types Pattern (REQUIRED)

```typescript
// ✅ ALWAYS: Create const object first, then extract type
const STATUS = {
  ACTIVE: "active",
  INACTIVE: "inactive",
  PENDING: "pending",
} as const;

type Status = (typeof STATUS)[keyof typeof STATUS];

// ❌ NEVER: Direct union types
type Status = "active" | "inactive" | "pending";
```

**Why?** Single source of truth, runtime values, autocomplete, easier refactoring.

## Flat Interfaces (REQUIRED)

```typescript
// ✅ ALWAYS: One level depth, nested objects → dedicated interface
interface UserAddress {
  street: string;
  city: string;
}

interface User {
  id: string;
  name: string;
  address: UserAddress;  // Reference, not inline
}

interface Admin extends User {
  permissions: string[];
}

// ❌ NEVER: Inline nested objects
interface User {
  address: { street: string; city: string };  // NO!
}
```

## Never Use `any`

```typescript
// ✅ Use unknown for truly unknown types
function parse(input: unknown): User {
  if (isUser(input)) return input;
  throw new Error("Invalid input");
}

// ✅ Use generics for flexible types
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

// ❌ NEVER
function parse(input: any): any { }
```

## Utility Types

```typescript
Pick<User, "id" | "name">     // Select fields
Omit<User, "id">              // Exclude fields
Partial<User>                 // All optional
Required<User>                // All required
Readonly<User>                // All readonly
Record<string, User>          // Object type
Extract<Union, "a" | "b">     // Extract from union
Exclude<Union, "a">           // Exclude from union
NonNullable<T | null>         // Remove null/undefined
ReturnType<typeof fn>         // Function return type
Parameters<typeof fn>         // Function params tuple
```

## Type Guards

```typescript
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value
  );
}
```

## Import Types

```typescript
import type { User } from "./types";
import { createUser, type Config } from "./utils";
```

## Keywords
typescript, ts, types, interfaces, generics, strict mode, utility types

Overview

This skill documents strict TypeScript patterns and best practices for defining types, interfaces, and generics. It focuses on maintainable, type-safe code: const-backed unions, flat interfaces, avoiding any, utility types, type guards, and import-time type syntax. Use it as a checklist when writing or reviewing TypeScript in strict mode.

How this skill works

Define runtime constant objects first and derive union types from them to keep a single source of truth and enable autocomplete. Keep interfaces flat by extracting nested shapes into their own interfaces. Replace any with unknown, type guards, or generics. Leverage built-in utility types and import types explicitly to keep type-only imports clear.

When to use it

  • When creating enumerated string unions that must match runtime values
  • When designing domain models with nested objects or inheritance
  • When you need safe parsing or validation of external data
  • When writing reusable functions that require flexible typing via generics
  • When importing types across modules to avoid runtime overhead

Best practices

  • Create const objects and derive union types: const X = { ... } as const; type T = (typeof X)[keyof typeof X];
  • Keep interfaces one level deep; extract nested objects into named interfaces for reuse and clarity
  • Never use any; prefer unknown + type guards or generics for safe flexibility
  • Use built-in utility types (Pick, Omit, Partial, Required, Readonly, Record, Extract, Exclude, NonNullable, ReturnType, Parameters) to express intent
  • Write type guards (value is Type) for runtime checks and safe narrowing
  • Use import type and inline type imports to separate type-only dependencies from runtime code

Example use cases

  • Defining status or role enums where values must exist at runtime and be exhaustively referenced
  • Modeling domain objects (User, Address, Admin) with reusable sub-interfaces and derived types
  • Implementing safe parsers for JSON input using unknown and type guards before casting
  • Creating generic utilities like first<T>(arr: T[]): T | undefined to preserve type information
  • Refactoring code safely: change a const value and have derived types and autocomplete update automatically

FAQ

Why derive unions from const objects instead of writing string unions?

Deriving unions from const objects provides a single source of truth, preserves runtime values, enables autocomplete, and makes refactoring safer.

When should I use unknown vs generics?

Use unknown when input can be any external value and needs explicit runtime validation. Use generics when the type should be preserved and propagated across functions.