smithery.ai

type-patterns

Type-safe patterns using Result types, discriminated unions, and exhaustive matching. Reference for implementing immutable, type-safe core logic without exceptions.

First seen Apr 19, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,874 B
  • docs SUMMARY.md 186 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Type Safety Patterns

Core Rules

Avoid Use Instead
any Proper type definitions
as type assertions Discriminated unions
throw exceptions Result type
Type guards ts-pattern .exhaustive()

Result Type

export type Result<T, E> =
  | { readonly type: "success"; readonly data: T }
  | { readonly type: "error"; readonly error: E }

// Domain-specific error types
export type AppError =
  | { type: "notFound"; resource: string }
  | { type: "validation"; field: string; message: string }
  | { type: "permission"; action: string }
  | { type: "network"; status: number }
  | { type: "unknown"; cause: unknown }

ts-pattern Exhaustive Matching

import { match } from "ts-pattern"

const handleResult = <T>(result: Result<T, AppError>) =>
  match(result)
    .with({ type: "success" }, ({ data }) => renderData(data))
    .with({ type: "error" }, ({ error }) =>
      match(error)
        .with({ type: "notFound" }, () => showNotFound())
        .with({ type: "validation" }, ({ message }) => showValidation(message))
        .with({ type: "permission" }, () => showPermissionError())
        .with({ type: "network" }, ({ status }) => showNetworkError(status))
        .with({ type: "unknown" }, () => showUnknown())
        .exhaustive()
    )
    .exhaustive()

Discriminated Union Pattern

// Base interface with discriminant
interface BaseNode {
  readonly id: string
  readonly name: string
}

// Discriminated variants
interface FileNode extends BaseNode {
  readonly type: "file"
  readonly size: number
  readonly mimeType: string
}

interface FolderNode extends BaseNode {
  readonly type: "folder"
  readonly children: ReadonlyArray<TreeNode>
}

export type TreeNode = FileNode | FolderNode

// Exhaustive handling
const getIcon = (node: TreeNode) =>
  match(node)
    .with({ type: "file" }, () => FileIcon)
    .with({ type: "folder" }, () => FolderIcon)
    .exhaustive()

Immutable Class Pattern

export class SelectionManager<T extends { id: string }> {
  private constructor(
    private readonly selectedIds: ReadonlySet<string>,
    private readonly anchorId: string | null
  ) {}

  static empty<T extends { id: string }>(): SelectionManager<T> {
    return new SelectionManager(new Set(), null)
  }

  select(itemId: string): SelectionManager<T> {
    return new SelectionManager(new Set([itemId]), itemId)
  }

  toggle(itemId: string): SelectionManager<T> {
    const next = new Set(this.selectedIds)
    if (next.has(itemId)) {
      next.delete(itemId)
    } else {
      next.add(itemId)
    }
    return new SelectionManager(next, itemId)
  }

  isSelected(itemId: string): boolean {
    return this.selectedIds.has(itemId)
  }
}

Readonly Collections

// Prefer readonly variants
type Items = ReadonlyArray<Item>
type Lookup = ReadonlyMap<string, Item>
type UniqueIds = ReadonlySet<string>

// Immutable updates
const addItem = (items: ReadonlyArray<Item>, item: Item): ReadonlyArray<Item> =>
  [...items, item]

const removeItem = (items: ReadonlyArray<Item>, id: string): ReadonlyArray<Item> =>
  items.filter(item => item.id !== id)

Branded Types

// Prevent type confusion
declare const brand: unique symbol

type UserId = string & { readonly [brand]: "UserId" }
type PostId = string & { readonly [brand]: "PostId" }

const createUserId = (id: string): UserId => id as UserId
const createPostId = (id: string): PostId => id as PostId

// Compile-time safety
declare function getUser(id: UserId): User
declare function getPost(id: PostId): Post

const userId = createUserId("123")
const postId = createPostId("456")

getUser(userId)  // ✅ OK
getUser(postId)  // ❌ Type error

Option Type (for nullable values)

export type Option<T> =
  | { readonly type: "some"; readonly value: T }
  | { readonly type: "none" }

export const some = <T>(value: T): Option<T> => ({ type: "some", value })
export const none: Option<never> = { type: "none" }

// Usage
const findUser = (id: string): Option<User> =>
  users.has(id) ? some(users.get(id)!) : none

const userName = match(findUser("123"))
  .with({ type: "some" }, ({ value }) => value.name)
  .with({ type: "none" }, () => "Unknown")
  .exhaustive()

References