Skip to content
Development
Skill

/web-error-handling-result-types

TypeScript Result/Either types for type-safe error handling, railway-oriented programming patterns, error as values

From plugin
agents-inc-skills
24200 skills
Install
$ npx -y skills add agents-inc/skills --skill web-error-handling-result-types --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/web-error-handling-result-types

Context preview

The summary Claude sees to decide when to auto-load this skill.

TypeScript Result/Either types for type-safe error handling, railway-oriented programming patterns, error as values

SKILL.md

web-error-handling-result-types.SKILL.md
name: web-error-handling-result-types
description: TypeScript Result/Either types for type-safe error handling, railway-oriented programming patterns, error as values

TypeScript Result Type Patterns

> **Quick Guide:** A `Result<T, E>` is a discriminated union on `ok`, so TypeScript refuses to read `value` until the caller has checked. That moves a function's failure modes into its signature, where an exception hides them. Use it for expected failures — validation, parsing, requests — and keep exceptions for bugs and for conditions nothing downstream can act on. A custom implementation is about forty lines and the recommended default; the whole surface is in this skill.

**Detailed Resources:**

  • [examples/core.md](examples/core.md) — the Result module, typed error definitions, wrapping throwing code, pattern matching
  • [examples/async.md](examples/async.md) — `Promise<Result>`, async chaining, retry, converting a promise
  • [examples/combining.md](examples/combining.md) — fail-fast, collect-all, object and sequential combination
  • [reference.md](reference.md) — operation lookup, what Results do not catch, error-type templates

---

Which path applies

  • **Nothing exists yet** — write the module: the union, `ok`, `err`, `map`, `flatMap`, `match`,

`tryCatch`. [examples/core.md](examples/core.md) is the whole file.

  • **A library owns the type** — the operations are named differently but compose identically;

[reference.md](reference.md) maps the names.

  • **The failing operation is async** — the type is `Promise<Result<T, E>>` and the awaiting is the

caller's; see [examples/async.md](examples/async.md).

---

<critical_requirements>

Before writing Result code

**Check `result.ok` before reading `value` or `error`.** The union narrows only through that check, so TypeScript will refuse either access until it is made — and a runtime `undefined` is what a bypassed check produces.

**Wrap every throwing call inside a Result-returning function in `tryCatch`.** `JSON.parse` and its kin throw past the return type, so one unwrapped call makes the signature a lie and the caller's exhaustive handling incomplete.

**Give each error a discriminant field — `code` or `type` — rather than typing it as `Error` or `string`.** The discriminant is what lets the caller `switch` and lets TypeScript check the switch is exhaustive; a bare message can only be displayed.

**Chain with `flatMap` where each step returns a Result.** The error type unions itself and the first failure short-circuits the rest, which is what nested `if (result.ok)` blocks are reimplementing by hand.

**Do something with every Result you receive.** A discarded one is a failure that never happened as far as the rest of the program is concerned, and no type error marks it.

</critical_requirements>

---

**Auto-detection:** Result type, Either type, ok err, railway-oriented programming, error as value, flatMap andThen, tryCatch, unwrapOr, combineWithAllErrors, discriminated union error, typed errors

**Applies to:**

  • Expected, recoverable failures — validation, parsing, requests, business rules
  • Function signatures that have to name every way they can fail
  • Chaining fallible steps so the first failure skips the rest
  • Collecting every failure at once, as form validation needs

**Handled elsewhere:**

  • Render-phase failures — a component that throws is caught by whatever wraps it, and a Result never reaches that path.
  • Transport and caching — a Result describes the outcome of a request; issuing, retrying and caching it belong to whatever fetches.
  • Schema validation — a validator that reports issues has its own result shape; wrap it at the boundary and carry its report as your error payload.
  • Turning a failure into a response — the status code an error maps to is the API layer's rule, and this skill only guarantees the error arrives typed.

---

<philosophy>

Philosophy

An exception is invisible control flow: it leaves no trace in the type, so the only way to know a function throws is to read it or to be surprised in production. A Result puts the same information in the signature, where the compiler enforces it.

The cost is real — every caller handles or propagates, and the error union grows as a chain lengthens. That is why the boundary matters: convert throwing code to Results on the way in, and convert Results to whatever the outside world wants on the way out. In between, nothing throws.

**The railway:** success runs the main line, and the first error switches to the parallel one, where every later step is skipped until something explicitly handles it.

     parseNumber     validatePositive     double
OK   ─────────────────────────────────────────────> success
                  ↘                  ↘
ERR                 ────────────────────────────> failure

</philosophy>

---

<decision_framework>

Result, exception, or nullable

Can the caller do something about this failure?
├─ NO — it is a bug or a condition nothing can act on → throw
│   ├─ Index out of bounds, invalid internal state
│   └─ Missing startup configuration, unreachable database at boot
└─ YES → What does the failure need to carry?
    ├─ Nothing but its own absence → T | null
    ├─ A reason the caller branches on → Result<T, E>
    └─ Several distinct reasons → Result<T, E> with a discriminated E

A `Result<User, NotFoundError>` whose error carries only `code: "NOT_FOUND"` is a nullable wearing a costume. Reach for the Result when the caller's next action differs by reason.

**Fail fast or collect everything:** one invalid field in a form is not a reason to hide the other four, so form validation collects; a chain where step two consumes step one's output has nothing to collect and short-circuits.

Returning a value also costs far less than throwing one, because a thrown error captures a stack trace and unwinds; [reference.md](reference.md) carries the measured comparison. That is a tiebreaker on a hot path rather tha

Read more
Ships withagents-inc-skills

The official skills marketplace for Agents Inc. 150+ skills covering everything from React and Prisma to Redis, ElevenLabs, and infrastructure tooling. Pick the skills that match your stack and install them via Claude Code. Need more control?

Get the whole plugin

Other skills on agents-inc-skills.