Claude Agent Skill · by Wshobson

Typescript Advanced Types

This gets deep into TypeScript's type system machinery with practical patterns for building bulletproof APIs and components. You'll learn conditional types for

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill typescript-advanced-types
Works with Paperclip

How Typescript Advanced Types fits into a Paperclip company.

Typescript Advanced Types drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md717 lines
Expand
---name: typescript-advanced-typesdescription: Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.--- # TypeScript Advanced Types Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications. ## When to Use This Skill - Building type-safe libraries or frameworks- Creating reusable generic components- Implementing complex type inference logic- Designing type-safe API clients- Building form validation systems- Creating strongly-typed configuration objects- Implementing type-safe state management- Migrating JavaScript codebases to TypeScript ## Core Concepts ### 1. Generics **Purpose:** Create reusable, type-flexible components while maintaining type safety. **Basic Generic Function:** ```typescriptfunction identity<T>(value: T): T {  return value;} const num = identity<number>(42); // Type: numberconst str = identity<string>("hello"); // Type: stringconst auto = identity(true); // Type inferred: boolean``` **Generic Constraints:** ```typescriptinterface HasLength {  length: number;} function logLength<T extends HasLength>(item: T): T {  console.log(item.length);  return item;} logLength("hello"); // OK: string has lengthlogLength([1, 2, 3]); // OK: array has lengthlogLength({ length: 10 }); // OK: object has length// logLength(42);             // Error: number has no length``` **Multiple Type Parameters:** ```typescriptfunction merge<T, U>(obj1: T, obj2: U): T & U {  return { ...obj1, ...obj2 };} const merged = merge({ name: "John" }, { age: 30 });// Type: { name: string } & { age: number }``` ### 2. Conditional Types **Purpose:** Create types that depend on conditions, enabling sophisticated type logic. **Basic Conditional Type:** ```typescripttype IsString<T> = T extends string ? true : false; type A = IsString<string>; // truetype B = IsString<number>; // false``` **Extracting Return Types:** ```typescripttype ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; function getUser() {  return { id: 1, name: "John" };} type User = ReturnType<typeof getUser>;// Type: { id: number; name: string; }``` **Distributive Conditional Types:** ```typescripttype ToArray<T> = T extends any ? T[] : never; type StrOrNumArray = ToArray<string | number>;// Type: string[] | number[]``` **Nested Conditions:** ```typescripttype TypeName<T> = T extends string  ? "string"  : T extends number    ? "number"    : T extends boolean      ? "boolean"      : T extends undefined        ? "undefined"        : T extends Function          ? "function"          : "object"; type T1 = TypeName<string>; // "string"type T2 = TypeName<() => void>; // "function"``` ### 3. Mapped Types **Purpose:** Transform existing types by iterating over their properties. **Basic Mapped Type:** ```typescripttype Readonly<T> = {  readonly [P in keyof T]: T[P];}; interface User {  id: number;  name: string;} type ReadonlyUser = Readonly<User>;// Type: { readonly id: number; readonly name: string; }``` **Optional Properties:** ```typescripttype Partial<T> = {  [P in keyof T]?: T[P];}; type PartialUser = Partial<User>;// Type: { id?: number; name?: string; }``` **Key Remapping:** ```typescripttype Getters<T> = {  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];}; interface Person {  name: string;  age: number;} type PersonGetters = Getters<Person>;// Type: { getName: () => string; getAge: () => number; }``` **Filtering Properties:** ```typescripttype PickByType<T, U> = {  [K in keyof T as T[K] extends U ? K : never]: T[K];}; interface Mixed {  id: number;  name: string;  age: number;  active: boolean;} type OnlyNumbers = PickByType<Mixed, number>;// Type: { id: number; age: number; }``` ### 4. Template Literal Types **Purpose:** Create string-based types with pattern matching and transformation. **Basic Template Literal:** ```typescripttype EventName = "click" | "focus" | "blur";type EventHandler = `on${Capitalize<EventName>}`;// Type: "onClick" | "onFocus" | "onBlur"``` **String Manipulation:** ```typescripttype UppercaseGreeting = Uppercase<"hello">; // "HELLO"type LowercaseGreeting = Lowercase<"HELLO">; // "hello"type CapitalizedName = Capitalize<"john">; // "John"type UncapitalizedName = Uncapitalize<"John">; // "john"``` **Path Building:** ```typescripttype Path<T> = T extends object  ? {      [K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never;    }[keyof T]  : never; interface Config {  server: {    host: string;    port: number;  };  database: {    url: string;  };} type ConfigPath = Path<Config>;// Type: "server" | "database" | "server.host" | "server.port" | "database.url"``` ### 5. Utility Types **Built-in Utility Types:** ```typescript// Partial<T> - Make all properties optionaltype PartialUser = Partial<User>; // Required<T> - Make all properties requiredtype RequiredUser = Required<PartialUser>; // Readonly<T> - Make all properties readonlytype ReadonlyUser = Readonly<User>; // Pick<T, K> - Select specific propertiestype UserName = Pick<User, "name" | "email">; // Omit<T, K> - Remove specific propertiestype UserWithoutPassword = Omit<User, "password">; // Exclude<T, U> - Exclude types from uniontype T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" // Extract<T, U> - Extract types from uniontype T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b" // NonNullable<T> - Exclude null and undefinedtype T3 = NonNullable<string | null | undefined>; // string // Record<K, T> - Create object type with keys K and values Ttype PageInfo = Record<"home" | "about", { title: string }>;``` ## Advanced Patterns ### Pattern 1: Type-Safe Event Emitter ```typescripttype EventMap = {  "user:created": { id: string; name: string };  "user:updated": { id: string };  "user:deleted": { id: string };}; class TypedEventEmitter<T extends Record<string, any>> {  private listeners: {    [K in keyof T]?: Array<(data: T[K]) => void>;  } = {};   on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {    if (!this.listeners[event]) {      this.listeners[event] = [];    }    this.listeners[event]!.push(callback);  }   emit<K extends keyof T>(event: K, data: T[K]): void {    const callbacks = this.listeners[event];    if (callbacks) {      callbacks.forEach((callback) => callback(data));    }  }} const emitter = new TypedEventEmitter<EventMap>(); emitter.on("user:created", (data) => {  console.log(data.id, data.name); // Type-safe!}); emitter.emit("user:created", { id: "1", name: "John" });// emitter.emit("user:created", { id: "1" });  // Error: missing 'name'``` ### Pattern 2: Type-Safe API Client ```typescripttype HTTPMethod = "GET" | "POST" | "PUT" | "DELETE"; type EndpointConfig = {  "/users": {    GET: { response: User[] };    POST: { body: { name: string; email: string }; response: User };  };  "/users/:id": {    GET: { params: { id: string }; response: User };    PUT: { params: { id: string }; body: Partial<User>; response: User };    DELETE: { params: { id: string }; response: void };  };}; type ExtractParams<T> = T extends { params: infer P } ? P : never;type ExtractBody<T> = T extends { body: infer B } ? B : never;type ExtractResponse<T> = T extends { response: infer R } ? R : never; class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {  async request<Path extends keyof Config, Method extends keyof Config[Path]>(    path: Path,    method: Method,    ...[options]: ExtractParams<Config[Path][Method]> extends never      ? ExtractBody<Config[Path][Method]> extends never        ? []        : [{ body: ExtractBody<Config[Path][Method]> }]      : [          {            params: ExtractParams<Config[Path][Method]>;            body?: ExtractBody<Config[Path][Method]>;          },        ]  ): Promise<ExtractResponse<Config[Path][Method]>> {    // Implementation here    return {} as any;  }} const api = new APIClient<EndpointConfig>(); // Type-safe API callsconst users = await api.request("/users", "GET");// Type: User[] const newUser = await api.request("/users", "POST", {  body: { name: "John", email: "john@example.com" },});// Type: User const user = await api.request("/users/:id", "GET", {  params: { id: "123" },});// Type: User``` ### Pattern 3: Builder Pattern with Type Safety ```typescripttype BuilderState<T> = {  [K in keyof T]: T[K] | undefined;}; type RequiredKeys<T> = {  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;}[keyof T]; type OptionalKeys<T> = {  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;}[keyof T]; type IsComplete<T, S> =  RequiredKeys<T> extends keyof S    ? S[RequiredKeys<T>] extends undefined      ? false      : true    : false; class Builder<T, S extends BuilderState<T> = {}> {  private state: S = {} as S;   set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {    this.state[key] = value;    return this as any;  }   build(this: IsComplete<T, S> extends true ? this : never): T {    return this.state as T;  }} interface User {  id: string;  name: string;  email: string;  age?: number;} const builder = new Builder<User>(); const user = builder  .set("id", "1")  .set("name", "John")  .set("email", "john@example.com")  .build(); // OK: all required fields set // const incomplete = builder//   .set("id", "1")//   .build();  // Error: missing required fields``` ### Pattern 4: Deep Readonly/Partial ```typescripttype DeepReadonly<T> = {  readonly [P in keyof T]: T[P] extends object    ? T[P] extends Function      ? T[P]      : DeepReadonly<T[P]>    : T[P];}; type DeepPartial<T> = {  [P in keyof T]?: T[P] extends object    ? T[P] extends Array<infer U>      ? Array<DeepPartial<U>>      : DeepPartial<T[P]>    : T[P];}; interface Config {  server: {    host: string;    port: number;    ssl: {      enabled: boolean;      cert: string;    };  };  database: {    url: string;    pool: {      min: number;      max: number;    };  };} type ReadonlyConfig = DeepReadonly<Config>;// All nested properties are readonly type PartialConfig = DeepPartial<Config>;// All nested properties are optional``` ### Pattern 5: Type-Safe Form Validation ```typescripttype ValidationRule<T> = {  validate: (value: T) => boolean;  message: string;}; type FieldValidation<T> = {  [K in keyof T]?: ValidationRule<T[K]>[];}; type ValidationErrors<T> = {  [K in keyof T]?: string[];}; class FormValidator<T extends Record<string, any>> {  constructor(private rules: FieldValidation<T>) {}   validate(data: T): ValidationErrors<T> | null {    const errors: ValidationErrors<T> = {};    let hasErrors = false;     for (const key in this.rules) {      const fieldRules = this.rules[key];      const value = data[key];       if (fieldRules) {        const fieldErrors: string[] = [];         for (const rule of fieldRules) {          if (!rule.validate(value)) {            fieldErrors.push(rule.message);          }        }         if (fieldErrors.length > 0) {          errors[key] = fieldErrors;          hasErrors = true;        }      }    }     return hasErrors ? errors : null;  }} interface LoginForm {  email: string;  password: string;} const validator = new FormValidator<LoginForm>({  email: [    {      validate: (v) => v.includes("@"),      message: "Email must contain @",    },    {      validate: (v) => v.length > 0,      message: "Email is required",    },  ],  password: [    {      validate: (v) => v.length >= 8,      message: "Password must be at least 8 characters",    },  ],}); const errors = validator.validate({  email: "invalid",  password: "short",});// Type: { email?: string[]; password?: string[]; } | null``` ### Pattern 6: Discriminated Unions ```typescripttype Success<T> = {  status: "success";  data: T;}; type Error = {  status: "error";  error: string;}; type Loading = {  status: "loading";}; type AsyncState<T> = Success<T> | Error | Loading; function handleState<T>(state: AsyncState<T>): void {  switch (state.status) {    case "success":      console.log(state.data); // Type: T      break;    case "error":      console.log(state.error); // Type: string      break;    case "loading":      console.log("Loading...");      break;  }} // Type-safe state machinetype State =  | { type: "idle" }  | { type: "fetching"; requestId: string }  | { type: "success"; data: any }  | { type: "error"; error: Error }; type Event =  | { type: "FETCH"; requestId: string }  | { type: "SUCCESS"; data: any }  | { type: "ERROR"; error: Error }  | { type: "RESET" }; function reducer(state: State, event: Event): State {  switch (state.type) {    case "idle":      return event.type === "FETCH"        ? { type: "fetching", requestId: event.requestId }        : state;    case "fetching":      if (event.type === "SUCCESS") {        return { type: "success", data: event.data };      }      if (event.type === "ERROR") {        return { type: "error", error: event.error };      }      return state;    case "success":    case "error":      return event.type === "RESET" ? { type: "idle" } : state;  }}``` ## Type Inference Techniques ### 1. Infer Keyword ```typescript// Extract array element typetype ElementType<T> = T extends (infer U)[] ? U : never; type NumArray = number[];type Num = ElementType<NumArray>; // number // Extract promise typetype PromiseType<T> = T extends Promise<infer U> ? U : never; type AsyncNum = PromiseType<Promise<number>>; // number // Extract function parameterstype Parameters<T> = T extends (...args: infer P) => any ? P : never; function foo(a: string, b: number) {}type FooParams = Parameters<typeof foo>; // [string, number]``` ### 2. Type Guards ```typescriptfunction isString(value: unknown): value is string {  return typeof value === "string";} function isArrayOf<T>(  value: unknown,  guard: (item: unknown) => item is T,): value is T[] {  return Array.isArray(value) && value.every(guard);} const data: unknown = ["a", "b", "c"]; if (isArrayOf(data, isString)) {  data.forEach((s) => s.toUpperCase()); // Type: string[]}``` ### 3. Assertion Functions ```typescriptfunction assertIsString(value: unknown): asserts value is string {  if (typeof value !== "string") {    throw new Error("Not a string");  }} function processValue(value: unknown) {  assertIsString(value);  // value is now typed as string  console.log(value.toUpperCase());}``` ## Best Practices 1. **Use `unknown` over `any`**: Enforce type checking2. **Prefer `interface` for object shapes**: Better error messages3. **Use `type` for unions and complex types**: More flexible4. **Leverage type inference**: Let TypeScript infer when possible5. **Create helper types**: Build reusable type utilities6. **Use const assertions**: Preserve literal types7. **Avoid type assertions**: Use type guards instead8. **Document complex types**: Add JSDoc comments9. **Use strict mode**: Enable all strict compiler options10. **Test your types**: Use type tests to verify type behavior ## Type Testing ```typescript// Type assertion teststype AssertEqual<T, U> = [T] extends [U]  ? [U] extends [T]    ? true    : false  : false; type Test1 = AssertEqual<string, string>; // truetype Test2 = AssertEqual<string, number>; // falsetype Test3 = AssertEqual<string | number, string>; // false // Expect error helpertype ExpectError<T extends never> = T; // Example usagetype ShouldError = ExpectError<AssertEqual<string, number>>;``` ## Common Pitfalls 1. **Over-using `any`**: Defeats the purpose of TypeScript2. **Ignoring strict null checks**: Can lead to runtime errors3. **Too complex types**: Can slow down compilation4. **Not using discriminated unions**: Misses type narrowing opportunities5. **Forgetting readonly modifiers**: Allows unintended mutations6. **Circular type references**: Can cause compiler errors7. **Not handling edge cases**: Like empty arrays or null values ## Performance Considerations - Avoid deeply nested conditional types- Use simple types when possible- Cache complex type computations- Limit recursion depth in recursive types- Use build tools to skip type checking in production