All skills
jwynia avatar

/typescript-best-practices

@99a8797
by J Wyniajwynia/agent-skills160 stars
20

Guide AI agents through TypeScript coding best practices including type safety, error handling, code organization, and architecture patterns. This skill should be used when generating TypeScript code, reviewing TypeScript files, creating new TypeScript modules, refactoring JavaScript to TypeScript, or when the user asks about TypeScript patterns, types, or coding standards. Keywords: typescript, types, coding standards, best practices, type safety, generics, architecture, refactoring.

Use this Skill: https://skilld.dev/gh/jwynia/agent-skills/typescript-best-practices

This session only. Nothing lands on disk.

referencestype-systemadvanced-types.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Advanced Types

Deep dive into TypeScript's advanced type features: generics, conditional types, mapped types, and template literal types.

Generics

Basic Generic Functions

// Generic function with constraint
function first<T>(arr: ReadonlyArray<T>): T | undefined {
  return arr[0];
}

// Generic with default
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

// Multiple type parameters
function zip<T, U>(a: ReadonlyArray<T>, b: ReadonlyArray<U>): Array<[T, U]> {
  return a.map((item, i) => [item, b[i]]);
}

Generic Constraints

// Constrain to object with specific property
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// Constrain to objects with certain shape
interface HasLength {
  readonly length: number;
}

function logLength<T extends HasLength>(value: T): void {
  console.log(value.length);
}

// Works with string, array, or any object with length
logLength("hello");      // 5
logLength([1, 2, 3]);    // 3
logLength({ length: 10 }); // 10

Generic Classes

class Container<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  get(index: number): T | undefined {
    return this.items[index];
  }

  getAll(): ReadonlyArray<T> {
    return this.items;
  }
}

// With constraint
class Repository<T extends { id: string }> {
  private store = new Map<string, T>();

  save(item: T): void {
    this.store.set(item.id, item);
  }

  findById(id: string): T | undefined {
    return this.store.get(id);
  }
}

Conditional Types

Basic Conditional Types

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;  // true
type B = IsString<123>;      // false

// Extract array element type
type ElementType<T> = T extends ReadonlyArray<infer E> ? E : never;

type C = ElementType<string[]>;  // string
type D = ElementType<number>;    // never

Distributive Conditional Types

// Conditional types distribute over unions
type ToArray<T> = T extends unknown ? T[] : never;

type E = ToArray<string | number>;  // string[] | number[]

// Prevent distribution with tuple
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;

type F = ToArrayNonDist<string | number>;  // (string | number)[]

The infer Keyword

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

type G = ReturnOf<() => string>;  // string
type H = ReturnOf<(x: number) => boolean>;  // boolean

// Extract promise value
type Awaited<T> = T extends Promise<infer V> ? V : T;

type I = Awaited<Promise<string>>;  // string
type J = Awaited<string>;           // string

// Extract function parameters
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never;

type K = Parameters<(a: string, b: number) => void>;  // [string, number]

Practical Conditional Types

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

// Exclude types from union
type Exclude<T, U> = T extends U ? never : T;

type L = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"

// Extract types from union
type Extract<T, U> = T extends U ? T : never;

type M = Extract<string | number | boolean, number>;  // number

Mapped Types

Basic Mapped Types

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

// Make all properties required
type Required<T> = {
  [P in keyof T]-?: T[P];
};

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

// Remove readonly
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Key Remapping

// Rename keys with template literals
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface Person {
  name: string;
  age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

// Filter keys
type OnlyStrings<T> = {
  [P in keyof T as T[P] extends string ? P : never]: T[P];
};

type N = OnlyStrings<{ name: string; age: number; email: string }>;
// { name: string; email: string }

Mapped Type Modifiers

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

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

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

type O = Record<"a" | "b", number>;  // { a: number; b: number }

Template Literal Types

Basic Template Literals

// String manipulation
type Greeting = `Hello, ${string}`;

const g1: Greeting = "Hello, World";  // OK
const g2: Greeting = "Hi, World";     // Error

// Union expansion
type Color = "red" | "blue";
type Size = "small" | "large";

type ColorSize = `${Color}-${Size}`;
// "red-small" | "red-large" | "blue-small" | "blue-large"

Built-in String Manipulation Types

type Upper = Uppercase<"hello">;      // "HELLO"
type Lower = Lowercase<"HELLO">;      // "hello"
type Cap = Capitalize<"hello">;       // "Hello"
type Uncap = Uncapitalize<"Hello">;   // "hello"

Event Handler Pattern

type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

type EventHandlers = {
  [E in EventName as `on${Capitalize<E>}`]: (event: Event) => void;
};
// { onClick: ..., onFocus: ..., onBlur: ... }

Path Types

// Nested property paths
type PropPath<T, Prefix extends string = ""> = {
  [K in keyof T]: T[K] extends object
    ? PropPath<T[K], `${Prefix}${K & string}.`>
    : `${Prefix}${K & string}`;
}[keyof T];

interface Config {
  database: {
    host: string;
    port: number;
  };
  cache: {
    enabled: boolean;
  };
}

type ConfigPath = PropPath<Config>;
// "database.host" | "database.port" | "cache.enabled"

Combining Advanced Types

Builder Pattern with Types

type Builder<T, Built = {}> = {
  set<K extends keyof T>(
    key: K,
    value: T[K]
  ): Builder<Omit<T, K>, Built & Pick<T, K>>;
  build(): Built extends T ? T : never;
};

interface User {
  id: string;
  name: string;
  email: string;
}

declare function createBuilder<T>(): Builder<T>;

const user = createBuilder<User>()
  .set("id", "123")
  .set("name", "John")
  .set("email", "john@example.com")
  .build();

Deep Readonly

type DeepReadonly<T> = T extends object
  ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
  : T;

interface NestedConfig {
  server: {
    port: number;
    host: string;
  };
  features: string[];
}

type ImmutableConfig = DeepReadonly<NestedConfig>;
// All nested properties are readonly

Type-Safe Object.keys

// Standard Object.keys returns string[]
// Create type-safe version
function typedKeys<T extends object>(obj: T): Array<keyof T> {
  return Object.keys(obj) as Array<keyof T>;
}

const config = { port: 3000, host: "localhost" };
const keys = typedKeys(config);  // ("port" | "host")[]

Best Practices

  1. Start Simple: Use basic generics before reaching for conditional types
  2. Name Descriptively: TItem, TResult better than T, U
  3. Constrain Generics: Use extends to limit acceptable types
  4. Prefer Utility Types: Use built-in Partial, Required, Pick, Omit
  5. Document Complex Types: Add JSDoc for non-obvious type logic
  6. Test Types: Use @ts-expect-error to verify type behavior

Source: SKILL.md on GitHub

1 warning14d5 checks · Risk SAFE
  • Gen Agent Trust Hub14d

    This skill provides educational documentation and Deno-based utility scripts for TypeScript development, including tools for code analysis, module scaffolding, and type generation. It operates entirely on local files and contains no malicious code or network operations.

  • Socket14d

    No alerts

  • Snyk14d

    Risk: LOW · No issues

  • Runlayer7mo

    18/18 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 99a8797. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 8 months ago
compatibility
Requires Deno for analysis scripts. Applicable to any TypeScript codebase.
Other metadata
metadata
{
  "author": "agent-skills",
  "version": "1.0",
  "type": "utility",
  "mode": "assistive",
  "domain": "development"
}

README badge

README badge for jwynia/agent-skills/typescript-best-practices