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.
Scanned 8/31/2026
Install to Claude Code
npx -y skills add instrument-org/instrument --skill typescript-result --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Typescript Result?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/instrument-org-typescript-result)More formats (shields.io, HTML) on the badges page.
---
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).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!