Skip to content

Latest commit

 

History

History
112 lines (82 loc) · 3.34 KB

File metadata and controls

112 lines (82 loc) · 3.34 KB

@mkvlrn/result

Dead simple Result pattern for TypeScript.

No .map(), no .flatMap(), no .andThen(), no .orElse(), no .unwrap(), no monadic gymnastics. Just a type, two functions, and TypeScript doing what TypeScript already does well.

npm

Why this one?

There are dozens of Result libraries for TypeScript. Nearly all of them bolt on method chaining, transformation pipelines, and functional programming utilities that turn a simple concept into an entire paradigm.

This package does one thing: gives you a type-safe Result<T, E> discriminated union with ok() and err() constructors. You use if/else to handle it. TypeScript narrows the type for you. That's the whole API.

The entire implementation is ~35 lines. Zero runtime dependencies. Four exports.

If you need .map().flatMap().andThen().orElse().unwrapOr() chains, use neverthrow or ts-results. They're good libraries. This isn't that.

Installation

pnpm add @mkvlrn/result

API

Export What it does
Result<T, E> Union type representing success or failure
AsyncResult<T, E> Promise<Result<T, E>> for async operations
ok(value) Creates a success result
err(error) Creates an error result

That's it. That's the whole thing.

Usage

import { type Result, type AsyncResult, ok, err } from "@mkvlrn/result";

Create results, check results

function divide(a: number, b: number): Result<number, Error> {
  if (b === 0) {
    return err(new Error("Division by zero"));
  }
  return ok(a / b);
}

const result = divide(10, 2);
if (result.isOk) {
  console.log(result.value); // number - TypeScript knows
}

if (result.isError) {
  console.log(result.error.message); // Error - TypeScript knows
}

No .unwrap(). No .expect(). Just an if statement and the compiler handles the rest.

Async Operations

async function fetchUser(id: number): AsyncResult<User, Error> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return err(new Error(`HTTP ${response.status}`));
    }
    const user = await response.json();
    return ok(user);
  } catch (error) {
    return err(error instanceof Error ? error : new Error("Unknown error"));
  }
}

AsyncResult<T, E> is just Promise<Result<T, E>>. It's a type alias, not a class, not a wrapper, not a monad.

Custom Error Types

class ValidationError extends Error {
  readonly code: number;

  constructor(code: number, message: string) {
    super(message);
    this.name = "ValidationError";
    this.code = code;
  }
}

function validateEmail(email: string): Result<string, ValidationError> {
  if (!email.includes("@")) {
    return err(new ValidationError(400, "bad-email"));
  }
  return ok(email);
}

const result = validateEmail("invalid-email");
if (result.isError) {
  // TypeScript knows this is a ValidationError, not just Error
  console.log(`${result.error.code}: ${result.error.message}`); // 400: bad-email
}

License

MIT