Skip to content
Back to skills

Typescript Result

ASecurity

Use the `typescript-result` library for type-safe error handling in TypeScript. Replaces try/catch with Result<T, E> and AsyncResult<T, E> types, enabling compile-time error tracking, chaining, and generator-style composition. Use when writing functions that can fail, wrapping async operations, handling errors exhaustively, or when the user mentions Result types, error handling patterns, or typescript-result.

  • 5 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added August 31, 2026
developmenttypescriptrustapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned August 31, 2026

npx -y skills add instrument-org/instrument --skill typescript-result --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Typescript Result?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Typescript Result
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/instrument-org-typescript-result/badge)](https://www.skillsdirectory.com/skills/instrument-org-typescript-result)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: typescript-result
description: Use the `typescript-result` library for type-safe error handling in TypeScript. Replaces try/catch with Result<T, E> and AsyncResult<T, E> types, enabling compile-time error tracking, chaining, and generator-style composition. Use when writing functions that can fail, wrapping async operations, handling errors exhaustively, or when the user mentions Result types, error handling patterns, or typescript-result.
---

# typescript-result

Package: `typescript-result` | Import: `import { Result, AsyncResult, assertUnreachable } from "typescript-result"`

## Core Concepts

- `Result<T, E>` - synchronous result, either `{ ok: true, value: T }` or `{ ok: false, error: E }`
- `AsyncResult<T, E>` - a `Promise<Result<T, E>>` with the same chaining methods
- Errors should be typed class instances (ideally with a `readonly type` discriminant for narrowing)

```ts
class IOError extends Error {
  readonly type = "io-error";
}
```

## Creating Results

```ts
Result.ok(value); // Result<T, never>
Result.ok(); // Result<void, never>
Result.error(new Err()); // Result<never, Err>

// Wrap a throwing function (executes immediately)
Result.try(
  () => JSON.parse(str),
  (err) => new ParseError("bad json", { cause: err }),
);

// Wrap a function, returning a new function (deferred)
const safeRead = Result.wrap(
  (path: string) => fs.readFileSync(path, "utf-8"),
  (err) => new IOError("read failed", { cause: err }),
);

// Async - returns AsyncResult
Result.fromAsync(async () =>
  Math.random() > 0.5 ? Result.ok("yes") : Result.error(new Err()),
);
```

## Transforming: `map` / `mapCatching`

`map` runs only on success; errors pass through unchanged. Polymorphic: return a plain value, `Result`, `AsyncResult`, `Promise`, or generator.

```ts
const result = readFile(path)
  .map((contents) => parseJSON(contents)) // can return Result
  .map((json) => validate(json)) // chaining
  .mapCatching(
    (data) => riskyTransform(data),
    (err) => new TransformError("...", { cause: err }),
  );
```

## Generator Style

Use `yield*` inside `Result.gen` to unwrap results imperatively. Short-circuits on first error.

```ts
const result = await Result.gen(async function* () {
  const contents = yield* readFile(path); // unwraps or short-circuits
  const json = yield* Result.try(
    () => JSON.parse(contents),
    (err) => new ParseError("bad json", { cause: err }),
  );
  return validate(json);
});
```

**When to use generators vs chaining:**

- Generators: complex control flow, loops, conditionals, multiple references to earlier values
- Chaining: simple linear transformations

## Combining Multiple Results

```ts
// Returns first error if any fail; otherwise tuple of values
const result = Result.all(
  fetchUser(id), // AsyncResult<User, FetchError>
  fetchDetails(id), // AsyncResult<UserDetails, FetchError>
);

result.map(([user, details]) => ({ ...user, details }));
```

## Unwrapping

```ts
// Narrow with .ok
if (result.ok) {
  result.value; // T
} else {
  result.error; // E
}

// Tuple destructuring
const [value, error] = result.toTuple();
if (error) {
  /* handle */
}

// Fold (success + failure handlers)
result.fold(
  (value) => ({ status: 200, body: value }),
  (error) => ({ status: 500, body: error.message }),
);

// Getters
result.getOrNull(); // T | null
result.getOrDefault(fallback); // T
result.getOrElse(() => fb); // T (callback, can be async)
result.getOrThrow(); // T or throws — avoid in production
```

## Error Handling

```ts
if (!result.ok) {
  result
    .match()
    .when(IOError, (err) => handleIO(err))
    .when(ParseError, ValidationError, () => handleBoth()) // group errors
    .else((err) => handleUnknown(err)) // optional fallback
    .run(); // exhaustive: TS errors if a case is missing
}
```

Use `assertUnreachable` for exhaustive switch/if-else:

```ts
import { assertUnreachable } from "typescript-result";

switch (error.type) {
  case "io-error":
    return handleIO();
  case "parse-error":
    return handleParse();
  default:
    assertUnreachable(error); // compile error if cases missed
}
```

## Typical Patterns

**Wrap external APIs at the boundary:**

```ts
const fetchUser = Result.wrap(
  (id: string) => fetch(`/users/${id}`).then((r) => r.json()),
  (err) => new FetchError("fetch failed", { cause: err }),
);
```

**Async route handler:**

```ts
async function handler(id: string) {
  const result = await fetchUser(id).map(validate);
  return result.fold(
    (user) => ({ status: 200, body: user }) as const,
    (err) =>
      err
        .match()
        .when(FetchError, () => ({ status: 503, body: "unavailable" }) as const)
        .when(
          ValidationError,
          () => ({ status: 400, body: "invalid" }) as const,
        )
        .run(),
  );
}
```

**Prefer `Result.fromAsync` over `async` functions returning `Result`** to keep `AsyncResult` as the return type (plays better with generators and chaining):

```ts
// prefer this
function getUser(id: string) {
  return Result.fromAsync(async () => ...);
}

// over this
async function getUser(id: string): Promise<Result<...>> { ... }
```

## Catching variants

Every creation and composition API has a `*Catching` sibling that catches thrown exceptions and wraps them into a failed result. Use the catching variant at trust boundaries (external libraries, code you don't control, legacy code):

| Safe (no throw protection) | Catching (wraps throws)     |
| -------------------------- | --------------------------- |
| `Result.gen`               | `Result.genCatching`        |
| `Result.try`               | _(try is already catching)_ |
| `Result.fromAsync`         | `Result.fromAsyncCatching`  |
| `result.map`               | `result.mapCatching`        |

**`Result.gen` does NOT catch thrown exceptions.** If any called function can throw (rather than returning a `Result.error`), use `Result.genCatching` or wrap the whole block in `Result.fromAsyncCatching` instead.

```ts
// WRONG if fetchUser can throw internally
Result.gen(async function* () {
  const user = yield* fetchUser(id); // only catches Result errors, not thrown exceptions
});

// RIGHT
Result.genCatching(
  async function* () {
    const user = yield* fetchUser(id);
  },
  (err) => new FetchError("...", { cause: err }),
);
```

## `fromAsyncCatching` unwraps `AsyncResult` returns automatically

When the async callback passed to `Result.fromAsyncCatching` returns an `AsyncResult` (or `Result`), the library automatically unwraps it — you don't end up with `Result<Result<T, E>, E>`. This means you can safely `return` an `AsyncResult` from inside a `fromAsyncCatching` block and it resolves to the inner value:

```ts
// This is correct — the AsyncResult returned by fetchUser is unwrapped automatically
Result.fromAsyncCatching(
  async () => fetchUser(id), // returns AsyncResult<User, FetchError>
  (err) => new UnknownError("...", { cause: err }),
);
// yields AsyncResult<User, FetchError | UnknownError>
```

This is the pattern used in `fetchModelsForProvider`: each `case` branch returns an `AsyncResult`, which `fromAsyncCatching` flattens. **Don't try to convert this to `Result.gen` — that would drop the exception-catching safety net**, and any underlying function that throws (rather than returning `Result.error`) would bubble up uncaught.

## Full API Reference

For complete method signatures, edge cases, and additional examples, see [reference.md](reference.md).

Files in this skill

  • SKILL.md7.3 KB
  • reference.md7.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.