> For AI agents: the complete documentation index is available at /byethrow/llms.txt, the full documentation bundle is available at /byethrow/llms-full.txt.

# byethrow

A lightweight Result type package

> Say goodbye to throw, embrace type-safe results

[Introduction](./guide/start/introduction) | [Quick Start](./guide/start/quick)


```sh [npm]
npm install @praha/byethrow
```

```sh [pnpm]
pnpm add @praha/byethrow
```

```sh [yarn]
yarn add @praha/byethrow
```

```sh [bun]
bun add @praha/byethrow
```

## Error handling that the compiler understands

Every failure becomes a value. No hidden throws, no forgotten catches, no surprises in production.

- [**Errors are values**](./guide/tutorial/basics/result-type): Functions return Result<T, E> instead of throwing. Success carries the value, Failure carries the error, and the type checker makes sure you handle both.
- [**Just plain objects**](./guide/best-practices/result-vs-throw): No classes, no instanceof, no hidden state. Log it, serialize it, send it across the wire as it is.
- [**Sync and async, one API**](./api/modules/Result): Every function accepts both Result and Promise<Result>. There is no second API to learn.
- [**Composable by design**](./guide/tutorial/chaining/pipe-basics): Chain steps with pipe. Each function only runs on success, and a failure flows straight through to the end.
- [**Ship only what you use**](./guide/best-practices/importing-result): Each function is an independent export, so bundlers drop everything you never call.
- [**Batteries included**](./guide/ecosystem/linter): A linter plugin, test matchers and documentation for AI assistants keep the whole team on the same patterns.

## Every failure path, visible in the types

### Wrap code that throws

Result.fn turns a throwing or async function into one that returns a Result.


```ts title=fetch-user.ts
import { Result } from '@praha/byethrow';

type User = { id: string; name: string };

const fetchUser = Result.fn({
  try: async (id: string): Promise<User> => {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  },
  catch: (error) => new Error('Failed to fetch user', { cause: error }),
});

const result = await fetchUser('u_123');
```

[`Result.fn`](./api/functions/Result.fn) | [Read the guide](./guide/tutorial/basics/wrapping-functions)

### Validate unknown input

Result.parse accepts any Standard Schema, such as Zod, Valibot or ArkType.


```ts title=sign-up.ts
import { Result } from '@praha/byethrow';
import { z } from 'zod';

const SignUp = z.object({
  name: z.string().min(1),
  email: z.email(),
});

const result = Result.parse(SignUp, await request.json());
```

[`Result.parse`](./api/functions/Result.parse) | [Read the guide](./api/functions/Result.parse)

### Compose steps with pipe

Each step runs only on success, and every possible error adds up in the type.


```ts title=welcome.ts
import { Result } from '@praha/byethrow';

const result = await Result.pipe(
  Result.succeed('u_123'),
  Result.andThen(validateId),
  Result.andThen(findUser),
  Result.andThrough(sendWelcomeMail),
  Result.map((user) => `Welcome, ${user.name}!`),
);

if (Result.isFailure(result)) {
  const { error } = result;
}
```

[`Result.pipe`](./api/functions/Result.pipe) | [`Result.andThen`](./api/functions/Result.andThen) | [`Result.andThrough`](./api/functions/Result.andThrough) | [`Result.map`](./api/functions/Result.map) | [Read the guide](./guide/tutorial/chaining/pipe-basics)

### Combine independent results

Result.collect waits for all of them and reports every failure, not just the first.


```ts title=dashboard.ts
import { Result } from '@praha/byethrow';

// Waits for every request and keeps all failures, not just the first
const result = await Result.collect({
  user: fetchUser('u_123'),
  posts: fetchPosts('u_123'),
  settings: fetchSettings('u_123'),
});

if (Result.isFailure(result)) {
  const errors = result.error;
}
```

[`Result.collect`](./api/functions/Result.collect) | [`Result.isFailure`](./api/functions/Result.isFailure) | [Read the guide](./guide/tutorial/combining/aggregating-results)

### Handle every error case

Pair it with ts-pattern for exhaustive matching, so a new error type fails to compile.


```ts title=delete-post.ts
import { Result } from '@praha/byethrow';
import { match } from 'ts-pattern';

await Result.pipe(
  deletePost('post_1'),
  Result.inspect(() => toast.success('Post deleted')),
  Result.inspectError((error) =>
    match(error)
      .with({ name: 'PostNotFoundError' }, () => toast.info('Already deleted'))
      .with({ name: 'PostPermissionError' }, () => toast.error('Not allowed'))
      // Adding a new error type turns this line into a compile error
      .exhaustive(),
  ),
);
```

[`Result.inspect`](./api/functions/Result.inspect) | [`Result.inspectError`](./api/functions/Result.inspectError) | [Read the guide](./guide/best-practices/pattern-matching)

## Say goodbye to throw.

Install the package and turn your first function into one that returns a Result. It takes about a minute.

[Read the guide](./guide/start/introduction) | [Browse the API](./api/)

