better-result-adopt

Adopt better-result in an existing TypeScript codebase. Use when replacing try/catch, Promise rejection handling, null sentinels, or thrown domain exceptions…

INSTALLATION
npx skills add https://github.com/dmmulroy/better-result --skill better-result-adopt
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

better-result Adopt

Adopt better-result incrementally in existing codebases without rewriting everything at once.

When to Use

Use this skill when the user wants to:

  • migrate from try/catch to Result.try or Result.tryPromise
  • replace nullable return values with typed Result<T, E>
  • define domain-specific TaggedError types
  • refactor nested error handling into andThen chains or Result.gen
  • standardize error handling across a service or module

Reading Order

Task

Files to Read

Adopt better-result in a module

This file

Define or review error types

references/tagged-errors.md

Inspect library implementation details

opensrc/ if present

Prerequisites

Before editing code:

  • Confirm better-result is already installed in the target project.
  • Check for an opensrc/ directory. If present, read the package source there for current patterns.
  • Identify the migration scope first: one file, one module, or one boundary layer.

Migration Strategy

1. Start at boundaries

Begin with I/O boundaries and exception-heavy code:

  • HTTP clients
  • database access
  • file system operations
  • parsing and validation
  • framework adapters

Do not convert the whole codebase at once.

2. Classify existing failures

Category

Examples

Target shape

Domain errors

not found, validation, auth

TaggedError + Result.err

Infrastructure errors

network, DB, file I/O

Result.tryPromise + mapped error

Programmer defects

bad assumptions, null deref

leave throwing; defects become Panic inside Result callbacks

3. Migrate in this order

  • Define error types.
  • Wrap throwing boundaries with Result.try / Result.tryPromise.
  • Replace null or boolean sentinel returns with Result.
  • Refactor call sites to propagate Result values.
  • Collapse nested branching into andThen, mapError, or Result.gen.

Core Transformations

Try/catch → Result.try

function parseConfig(json: string): Result<Config, ParseError> {

  return Result.try({

    try: () => JSON.parse(json) as Config,

    catch: (cause) => new ParseError({ cause, message: `Parse failed: ${cause}` }),

  });

}

Async throws → Result.tryPromise

async function fetchUser(id: string): Promise<Result<User, ApiError | UnhandledException>> {

  return Result.tryPromise({

    try: async () => {

      const res = await fetch(`/api/users/${id}`);

      if (!res.ok) throw new ApiError({ status: res.status, message: `API ${res.status}` });

      return res.json() as Promise<User>;

    },

    catch: (cause) => (cause instanceof ApiError ? cause : new UnhandledException({ cause })),

  });

}

Null sentinel → Result

function findUser(id: string): Result<User, NotFoundError> {

  const user = users.find((candidate) => candidate.id === id);

  return user

    ? Result.ok(user)

    : Result.err(new NotFoundError({ id, message: `User ${id} not found` }));

}

Nested flow → Result.gen

async function processOrder(orderId: string): Promise<Result<OrderResult, OrderError>> {

  return Result.gen(async function* () {

    const order = yield* Result.await(fetchOrder(orderId));

    const validated = yield* validateOrder(order);

    const result = yield* Result.await(submitOrder(validated));

    return Result.ok(result);

  });

}

Execution Workflow

  • Audit the target module for try, catch, .catch(...), throw, null, undefined, and status-flag error handling.
  • Define or update TaggedError classes before changing control flow.
  • Convert boundary functions first and change their signatures to Result<T, E> or Promise<Result<T, E>>.
  • Update immediate callers so they handle or propagate the new Result.
  • Where multiple Result-returning steps compose, use Result.gen or andThen.
  • Preserve error context by keeping cause, IDs, messages, and other structured fields.
  • Run tests and add coverage for both success and error paths.

Completion Criteria

A migration is complete when:

  • target functions no longer rely on try/catch for expected domain failures
  • nullable or sentinel error returns are replaced with explicit Result values
  • domain failures use typed TaggedError classes
  • callers either propagate Result or explicitly unwrap/match it
  • tests cover at least one success path and one representative error path

Common Pitfalls

  • Over-wrapping everything instead of starting at boundaries
  • Losing original failure context when mapping errors
  • Mixing throw-based and Result-based APIs deep in the same flow
  • Catching Panic instead of fixing the underlying defect

In This Reference

File

Purpose

references/tagged-errors.md

TaggedError patterns, matching, type guards, and examples

If opensrc/ exists, treat it as the source of truth for implementation details and current API behavior.

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card