byethrow

repository·main·Indexed 43 days ago

https://github.com/praha-inc/byethrow

A lightweight, tree-shakable Result type library for JavaScript and TypeScript designed to handle errors and fallible operations explicitly. It provides a consistent API to replace try/catch blocks with success and failure paths using utilities like Result.fail, Result.succeed, Result.pipe, and combinators such as andThen and andThrough. The ecosystem includes @praha/byethrow-docs for documentation and @praha/byethrow-oxlint for linting.

Tokens
45.3K
Snippets
146
Records
176
Agent score
53%

What's inside byethrow

  1. Overview of @praha/byethrow features

    main

    The @praha/byethrow library provides a lightweight, tree-shakable Result type for handling errors explicitly.

    Core characteristics:

    • Tree-shakable: Only the utilities you import are included in your bundle.
    • Object-based: Uses plain objects instead of classes to avoid overhead and magic.
    • Unified sync/async: Designed to work seamlessly with both Result<T, E> and Promise<Result<T, E>>.
    • Focused API: Provides a consistent and predictable API for handling both synchronous and asynchronous fallible operations without unnecessary complexity.
  2. Overview of @praha/byethrow

    main
    @praha/byethrow is a lightweight, tree-shakable Result type package for JavaScript and TypeScript. It provides a simple, consistent API for handling fallible operations (errors and results) without the overhead of larger libraries. For detailed usage guides and API references, refer to the official documentation.
  3. Introduction to byethrow

    main

    byethrow is a lightweight, tree-shakable Result type library designed for type-safe error handling in TypeScript. It aims to replace the use of throw with a more predictable Result<T, E> pattern.

    Key features include:

    • Tree-shakable: Optimized for modern bundlers to ensure only used code is included.
    • Object-based Design: Uses plain objects instead of complex class hierarchies, making it easy to debug.
    • Unified Sync/Async: Seamlessly handles both synchronous Result<T, E> and asynchronous Promise<Result<T, E>> types.
    • Composable Pipeline: Provides utilities like pipe, andThen, and andThrough for clean, chainable data flows.
    • Fully Type-Safe: All functions are rigorously type-tested to prevent type errors in your application logic.
  4. What is `Result.pipe` and how does it work?

    main

    The Result.pipe function is the core mechanism in @praha/byethrow for chaining operations in a readable, left-to-right flow. It takes an initial value (or a Result) and passes it through a series of functions, where each subsequent function receives the output of the previous one. This pattern replaces deeply nested function calls and the need for multiple intermediate variables.

    import { Result } from '@praha/byethrow';
    
    const result = Result.pipe(
      5,                    // Start with 5
      (x) => x + 1,         // 5 + 1 = 6
      (x) => x * 2,         // 6 * 2 = 12
      (x) => `Result: ${x}` // 'Result: 12'
    );
  5. What is the Result type?

    main

    The core abstraction in @praha/byethrow is the Result type. It represents the outcome of an operation that might fail, making error handling explicit instead of relying on exceptions. A Result can be one of two states:

    • Success: Contains a value of type T.
    • Failure: Contains an error of type E.

    You can check the state of a result using Result.isSuccess(result) or Result.isFailure(result).

    import { Result } from '@praha/byethrow';
    
    const success = Result.succeed('Hello, World!');
    const failure = Result.fail(new Error('Something went wrong'));
    
    if (Result.isSuccess(success)) {
      console.log(success.value); // "Hello, World!"
    }
    
    if (Result.isFailure(failure)) {
      console.log(failure.error.message); // "Something went wrong"
    }
  6. When to use or avoid `unwrap`

    main

    Deciding when to exit the Result type system is critical for maintaining type-safe error handling.

    Good Use Cases: Application Boundaries

    Use unwrap at the edges of your application (e.g., HTTP handlers, CLI entry points) where you must return plain values to an external environment. This allows unexpected errors to bubble up to infrastructure-level monitoring tools (like Sentry or CloudWatch).

    Avoid: Middle-of-Logic Extraction

    Do not call unwrap in the middle of your business logic. Doing so prematurely exits the Result world and loses the benefits of type-safe error handling. Instead, use combinators like map, flatMap, or orElse to transform values while they are still wrapped.

    Bad Pattern:

    const bad = Result.unwrap(Result.succeed(42)) * 2;

    Good Pattern:

    const good = Result.pipe(
      Result.succeed(42),
      Result.map((x) => x * 2),
    );
  7. When to use @praha/byethrow

    main

    Use this library when you want to handle errors explicitly as part of your application logic rather than using exceptions. Ideal use cases include:

    • API calls: Handling network failures or non-200 responses.
    • Data validation: Managing multiple validation errors gracefully.
    • File operations: Dealing with I/O or permission errors.
    • Parser implementations: Handling invalid input without crashing.
    • Business logic: Treating expected error states as first-class citizens in your domain logic.
  8. Comparing @praha/byethrow to other error handling approaches

    main

    When choosing an error handling strategy, @praha/byethrow positions itself relative to these common patterns:

    ApproachComparison to @praha/byethrow
    try/catchtry/catch mixes error management with business logic and can lead to unclear control flow. Byethrow makes errors explicit in the return type.
    neverthrowByethrow aims to be more complete for real-world application needs while maintaining simplicity.
    effect-ts / fp-tsThese libraries are highly powerful but introduce significant complexity and a steep learning curve. Byethrow provides a simpler, more predictable API for most use cases.
  9. Chain synchronous and asynchronous operations with Result.pipe

    main

    The Result.pipe utility allows you to compose multiple operations using Result.andThen.

    Key Behavior:

    • If all operations are synchronous (Result.Result), the pipeline returns a Result.Result.
    • If any operation in the chain is asynchronous (Result.ResultAsync), the entire pipeline automatically upgrades to a Result.ResultAsync.
    • ResultAsync<T, E> is a type alias for Promise<Result<T, E>> and can be resolved using await.
    import { Result } from '@praha/byethrow';
    
    // Sync function
    const validate = (input: string): Result.Result<string, Error> => {
      if (input.length === 0) {
        return Result.fail(new Error('Input is empty'));
      }
      return Result.succeed(input);
    };
    
    // Async function
    const fetchData = async (input: string): Result.ResultAsync<number, Error> => {
      return Result.succeed(input.length);
    };
    
    // Sync and async can be chained together seamlessly
    const result = Result.pipe(
      Result.succeed('hello'),
      Result.andThen(validate),  // sync
      Result.andThen(fetchData), // async - from here, the pipeline becomes async
      Result.andThen((n) => Result.succeed(n * 2)), // sync, but still in async context
    );
    // Type: Result.ResultAsync<number, Error>