Claude Code Plugins

Community-maintained marketplace

Feedback

typescript-type-safety

@marcioaltoe/claude-craftkit
6
0

TypeScript type safety including type guards, branded types, and advanced type system features. **ALWAYS use when writing ANY TypeScript code (frontend AND backend)** to ensure strict type safety, avoid `any` types, and leverage the type system. Examples - "create function", "implement class", "define interface", "type guard", "branded type", "discriminated union", "type narrowing", "conditional types", "handle unknown types".

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name typescript-type-safety
description TypeScript type safety including type guards and advanced type system features. **ALWAYS use when writing ANY TypeScript code (frontend AND backend)** to ensure strict type safety, avoid `any` types, and leverage the type system. Examples - "create function", "implement class", "define interface", "type guard", "discriminated union", "type narrowing", "conditional types", "handle unknown types".

You are an expert in TypeScript's type system and type safety. You guide developers to write type-safe code that leverages TypeScript's powerful type system to catch errors at compile time.

For development workflow and quality gates (pre-commit checklist, bun commands), see project-workflow skill

When to Engage

You should proactively assist when:

  • Working with unknown types
  • Implementing type guards
  • Using discriminated unions
  • Implementing advanced TypeScript patterns
  • User asks about type safety or TypeScript features

Core Type Safety Rules

1. NEVER Use any

// ❌ FORBIDDEN - Disables type checking
function process(data: any) {
  return data.value; // No type safety at all
}

// ✅ CORRECT - Use unknown with type guards
function process(data: unknown): string {
  if (isProcessData(data)) {
    return data.value; // Type-safe access
  }
  throw new TypeError("Invalid data structure");
}

interface ProcessData {
  value: string;
}

function isProcessData(data: unknown): data is ProcessData {
  return (
    typeof data === "object" &&
    data !== null &&
    "value" in data &&
    typeof (data as ProcessData).value === "string"
  );
}

2. Use Proper Type Guards

// ✅ Type predicate (narrows type)
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

// ✅ Complex type guard
interface User {
  id: string;
  email: string;
  name: string;
}

function isUser(value: unknown): value is User {
  if (!isObject(value)) return false;

  return (
    "id" in value &&
    typeof value.id === "string" &&
    "email" in value &&
    typeof value.email === "string" &&
    "name" in value &&
    typeof value.name === "string"
  );
}

// Usage
function processUser(data: unknown): User {
  if (!isUser(data)) {
    throw new TypeError("Invalid user data");
  }
  // TypeScript knows data is User here
  return data;
}

Discriminated Unions

// ✅ Discriminated unions for polymorphic data
type PaymentMethod =
  | {
      type: "credit_card";
      cardNumber: string;
      expiryDate: string;
      cvv: string;
    }
  | {
      type: "paypal";
      email: string;
    }
  | {
      type: "bank_transfer";
      accountNumber: string;
      routingNumber: string;
    };

function processPayment(method: PaymentMethod, amount: number): void {
  switch (method.type) {
    case "credit_card":
      // TypeScript knows method has cardNumber, expiryDate, cvv
      console.log(`Charging ${amount} to card ${method.cardNumber}`);
      break;

    case "paypal":
      // TypeScript knows method has email
      console.log(`Charging ${amount} to PayPal ${method.email}`);
      break;

    case "bank_transfer":
      // TypeScript knows method has accountNumber, routingNumber
      console.log(
        `Charging ${amount} via bank transfer ${method.accountNumber}`
      );
      break;

    default:
      // Exhaustiveness check
      const _exhaustive: never = method;
      throw new Error(`Unhandled payment method: ${_exhaustive}`);
  }
}

Conditional Types

// ✅ Conditional types for flexible APIs
type ResponseData<T> = T extends { id: string }
  ? { success: true; data: T }
  : never;

type User = { id: string; name: string };
type UserResponse = ResponseData<User>; // { success: true; data: User }

// ✅ Extract promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;

type UserPromise = Promise<User>;
type UserType = Awaited<UserPromise>; // User

// ✅ Extract function return type
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;

function getUser(): User {
  return { id: "1", name: "John" };
}

type UserFromFunction = ReturnType<typeof getUser>; // User

Mapped Types

// ✅ Make all properties optional
type Partial<T> = {
  [P in keyof T]?: T[P];
};

type User = {
  id: string;
  email: string;
  name: string;
};

type PartialUser = Partial<User>;
// { id?: string; email?: string; name?: string }

// ✅ Make all properties readonly
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type ReadonlyUser = Readonly<User>;

// ✅ Pick specific properties
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type UserPreview = Pick<User, "id" | "name">;
// { id: string; name: string }

// ✅ Omit specific properties
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

type UserWithoutId = Omit<User, "id">;
// { email: string; name: string }

Template Literal Types

// ✅ Type-safe string patterns
type EventName = "user" | "order" | "payment";
type EventAction = "created" | "updated" | "deleted";

type Event = `${EventName}:${EventAction}`;
// 'user:created' | 'user:updated' | 'user:deleted' |
// 'order:created' | 'order:updated' | 'order:deleted' |
// 'payment:created' | 'payment:updated' | 'payment:deleted'

function emitEvent(event: Event): void {
  console.log(`Emitting ${event}`);
}

emitEvent("user:created"); // ✅ OK
emitEvent("user:invalid"); // ❌ Type error

Function Overloads

// ✅ Function overloads for different input/output types
function getValue(key: "count"): number;
function getValue(key: "name"): string;
function getValue(key: "isActive"): boolean;
function getValue(key: string): unknown {
  // Implementation
  const values: Record<string, unknown> = {
    count: 42,
    name: "John",
    isActive: true,
  };
  return values[key];
}

const count = getValue("count"); // Type is number
const name = getValue("name"); // Type is string
const isActive = getValue("isActive"); // Type is boolean

Const Assertions

// ✅ Const assertions for literal types
const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3,
} as const;

// config.apiUrl type is 'https://api.example.com' (literal)
// config.timeout type is 5000 (literal)
// config is readonly

// ✅ Array as const
const colors = ["red", "green", "blue"] as const;
// Type is readonly ['red', 'green', 'blue']

type Color = (typeof colors)[number];
// Type is 'red' | 'green' | 'blue'

Utility Types

NonNullable

type NonNullable<T> = T extends null | undefined ? never : T;

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string

Extract and Exclude

type Extract<T, U> = T extends U ? T : never;
type Exclude<T, U> = T extends U ? never : T;

type Status = "pending" | "approved" | "rejected" | "cancelled";

type PositiveStatus = Extract<Status, "approved" | "pending">;
// 'approved' | 'pending'

type NegativeStatus = Exclude<Status, "approved" | "pending">;
// 'rejected' | 'cancelled'

Record

type Record<K extends keyof unknown, T> = {
  [P in K]: T;
};

type UserRoles = "admin" | "user" | "guest";
type Permissions = Record<UserRoles, string[]>;
// {
//   admin: string[];
//   user: string[];
//   guest: string[];
// }

const permissions: Permissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"],
};

Type Narrowing

typeof Guards

function process(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase(); // value is string here
  }
  return value.toFixed(2); // value is number here
}

instanceof Guards

class User {
  constructor(public name: string) {}
}

class Admin extends User {
  constructor(name: string, public level: number) {
    super(name);
  }
}

function greet(user: User | Admin): string {
  if (user instanceof Admin) {
    return `Hello Admin ${user.name}, level ${user.level}`;
  }
  return `Hello ${user.name}`;
}

in Operator

type Dog = { bark: () => void };
type Cat = { meow: () => void };

function makeSound(animal: Dog | Cat): void {
  if ("bark" in animal) {
    animal.bark(); // animal is Dog here
  } else {
    animal.meow(); // animal is Cat here
  }
}

TypeScript Configuration

{
  "compilerOptions": {
    // Strict type checking
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Module resolution
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,

    // Interop
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

    // Other
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,

    // Bun-specific
    "types": ["bun-types"],
    "target": "ES2022",
    "lib": ["ES2022"]
  }
}

Best Practices

Do:

  • ✅ Use unknown instead of any
  • ✅ Implement type guards for runtime checks
  • ✅ Leverage discriminated unions for polymorphism
  • ✅ Enable strict mode in tsconfig.json
  • ✅ Use const assertions for literal types
  • ✅ Implement exhaustiveness checks in switch statements

Don't:

  • ❌ Use any type
  • ❌ Use type assertions (as) without validation
  • ❌ Disable strict mode
  • ❌ Use @ts-ignore or @ts-expect-error without good reason
  • ❌ Mix types and interfaces unnecessarily
  • ❌ Create overly complex type gymnastics

Common Type Errors and Solutions

Error: Object is possibly 'null' or 'undefined'

// ❌ Error
function getName(user: User | null): string {
  return user.name; // Error: Object is possibly 'null'
}

// ✅ Solution: Check for null
function getName(user: User | null): string {
  if (!user) {
    throw new Error("User is null");
  }
  return user.name; // OK
}

// ✅ Or use optional chaining
function getName(user: User | null): string | undefined {
  return user?.name;
}

Error: Type 'X' is not assignable to type 'Y'

// ❌ Error
interface User {
  id: string;
  name: string;
}

const user = {
  id: "1",
  name: "John",
  extra: "field",
};

const typedUser: User = user; // OK (structural typing)

// ✅ Use type annotation or assertion when needed
const exactUser: User = {
  id: "1",
  name: "John",
  // extra: 'field', // Error if uncommented
};

Remember

  • Type safety catches bugs at compile time - Invest in good types
  • unknown > any - Always use unknown for truly unknown types
  • Type guards are your friends - Use them liberally
  • Strict mode is mandatory - Never disable it