UNPKG

lib-result

Version:

A Rust-inspired `Result` type for type-safe error handling in TypeScript and JavaScript

339 lines (335 loc) 16.4 kB
/** biome-ignore-all lint/complexity/noBannedTypes: No alternative */ /** * Defines methods for a `Result` type, providing type-safe operations for checking and extracting values. * @template T - The type of the success value. * @template E - The error type, must extend `Error`. * @template U - The return type of `unwrap`, typically `T` for `OkState` or `never` for `ErrorState`. */ interface ResultMethods<T, E extends Error> { /** * Checks if the result is in the `Ok` state (contains a value and no error). * @returns `true` if the result is `Ok`, narrowing the type to `OkState<T, E>`. */ isOk(): this is OkState<T, E>; /** * Checks if the result is in the `Err` state (contains an error and no value). * @returns `true` if the result is `Err`, narrowing the type to `ErrorState<E, T>`. */ isError(): this is ErrorState<E, T>; /** * Extracts the value from an `OkState<T>` result or throws the error from an `ErrorState<E>` result. * @returns The success value (`T`) if `OkState<T>`, or throws the error (`E`) if `ErrorState<E>`. * @throws {E} The error if the result is in the Error state. */ unwrap(): T; /** * Extracts the success value from an `OkState<T>` result. * If the result is an `ErrorState<E>`, it throws a `CustomError` with the provided message, * and the original error from `ErrorState<E>` is passed as the `cause` property. * @returns {T} The success value if `OkState<T>`. * @throws {CustomError} Throws a `CustomError` containing the provided message and the original error as its cause, if the result is in the Error state. */ expect(message: string): T; /** * Transforms the success value of a `Result` using the provided function, preserving the error if in the `Err` state. * @template U - The type of the transformed success value. * @param fn - A function that takes the `Ok` value of type `T` and returns a new value of type `U`. * @returns A new `Result` containing the transformed value (`Ok<U>`) if the original `Result` is `Ok`, or the same error (`Err<E>`) if the original `Result` is `Err`. * @example * const result = Ok(5); * const mapped = result.map(x => x.toString()); * // mapped: Result<string, Error> = Ok("5") * * const error: Result<number, Error> = Err(new Error("Failed")); * const mappedError = error.map(x => x.toString()); * // mappedError: Result<string, Error> = Err(Error("Failed")) */ map<U>(fn: (value: T) => U): Result<U, E>; /** * Chains a transformation by passing the `Ok` value to a function that returns a new `Result`, preserving the `Err` state if present. * @template U - The type of the success value in the returned `Result`. * @param {(value: T) => Result<U, E>} fn - A function that takes the `Ok` value of type `T` and returns a new `Result<U, E>`. * @returns {Result<U, E>} A new `Result<U, E>`: the result of `fn` if the original `Result` is `Ok`, or the same `Err` state if the original `Result` is `Err`. * @example * // Chaining transformations * const result: Result<number, Error> = Ok(5); * const chained = result * .pipe(x => Ok(x * 2)) // Result<number, Error> = Ok(10) * .pipe(x => Ok(x.toString())); // Result<string, Error> = Ok("10") * if (chained.isOk()) { * console.log(chained.ok); // "10" * } * * // Preserving Err state * const error: Result<number, Error> = Err(new Error("Failed")); * const chainedError = error.pipe(x => Ok(x * 2)); // Result<number, Error> = Err(Error("Failed")) * if (chainedError.isErr()) { * console.log(chainedError.error.message); // "Failed" * } */ pipe<U>(fn: (value: T) => Result<U, E>): Result<U, E>; /** * Pattern matches on the Result state, transforming both `Ok` and `Err` cases into a common type. * @template U - The type that both transformations will produce. * @param {(value: T) => U} okFn - Function to transform the success value if Result is `Ok`. * @param {(error: E) => U} errFn - Function to transform the error if Result is `Err`. * @returns {U} The result of either `okFn` or `errFn` depending on the Result state. * @example * const result: Result<number, Error> = Ok(42); * const message = result.match( * value => `Success: ${value}`, * error => `Error: ${error.message}` * ); // "Success: 42" * * const error: Result<number, Error> = Err(new Error("Failed")); * const errorMessage = error.match( * value => `Success: ${value}`, * error => `Error: ${error.message}` * ); // "Error: Failed" */ match<U>(okFn: (value: T) => U, errFn: (value: E) => U): U; /** * Returns the success value if the Result is `Ok`, or the result of the provided function if it's `Err`. * @template U - The type that the error handler function returns. * @param {(error: E) => U} fn - Function to handle the error case and provide an alternative value. * @returns {T | U} Either the success value or the result of the error handler function. * @example * const result: Result<number, Error> = Ok(42); * const value = result.orElse(() => 0); // 42 * * const error: Result<number, Error> = Err(new Error("Failed")); * const fallback = error.orElse(() => 0); // 0 */ orElse<U>(fn: (error: E) => U): T | U; /** * Returns the success value if the Result is `Ok`, or the fallback value if it's `Err`. * @template T - The type of the ok and the fallback value. * @param {T} fallback - Fallback value to return if the result is `ErrorState`. * @returns {T} Either the success value or the fallback value. * @example * const result: Result<number, Error> = Ok(42); * const value = result.unwrapOr(0); // 42 * * const error: Result<number, Error> = Err(new Error("Failed")); * const fallback = error.unwrapOr(0); // 0 */ unwrapOr(fallback: T): T; } /** * Represents a successful result state with an `ok` value and no error (undefined). * @template T - The type of the success value. * @template E - The error type, must extend `Error` (defaults to `Error`). */ interface OkState<T, E extends Error = Error> extends ResultMethods<T, E> { /** The success value of type `T`. */ readonly ok: T; /** Always `undefined` in the `Ok` state, indicating no error. */ readonly error: undefined; } /** * Represents an error result state with an error of type `E` and no value (undefined). * @template E - The error type, must extend `Error`. * @template T - The type of the success value (used for type compatibility with `Result`). */ interface ErrorState<E extends Error = Error, T = undefined> extends ResultMethods<T, E> { /** Always `undefined` in the `Error` state, indicating no value. */ readonly ok: undefined; /** The error of type `E`. */ readonly error: E; } /** * Simulates Rust's `Result` enum, representing either a success (`Ok`) or failure (`Err`). * @template T - The type of the success value. * @template E - The error type, must extend `Error` (defaults to `Error`). * @remarks A `Result` is either an `OkState` with a value (`ok: T`, `error: undefined`) * or an `ErrorState` with an error (`ok: undefined`, `error: E`). * Use `isOk()` and `isError()` for type-safe checking, and `unwrap()` to extract the value or throw the error. */ type Result<T, E extends Error = Error> = OkState<T, E> | ErrorState<E, T>; /** * Represents a custom error type that extends the built-in `Error` object * with additional properties defined in `CustomErrorProps`. * * @example * const err: CustomError<{ code: number; info: string }> = * Object.assign(new Error("Oops"), { code: 404, info: "Not Found" }); * console.log(err.info); // "Not Found" * console.log(err.code); // 404 */ type CustomError<T extends OptionalKeyValue = undefined> = T extends undefined ? Error : Error & CustomErrorProps<T>; /** * Defines the shape of properties that can be added to a custom error. * When `T` is `undefined`, resolves to `never`. Otherwise, it combines `T` with optional * standard error properties. * @template T - The type of additional properties to include in the error. * @example * type ApiErrorProps = CustomErrorProps<{ code: number; status: string }>; * // Equivalent to: { code: number; status: string; message?: string; cause?: unknown } */ type CustomErrorProps<T extends OptionalKeyValue> = T extends undefined ? never : T & (T extends { message: string; } ? {} : { message?: string; }) & (T extends { cause: any; } ? {} : { cause?: unknown; }); /** * A function type that takes a tuple of arguments and returns a value. * @template Args - A tuple type representing the arguments the function accepts. * @template T - The return type of the function. * @example * // A function that takes two numbers and returns a number * type Add = Callback<[number, number], number>; * * // A function that takes no arguments and returns a string * type GetText = Callback<[], string>; */ type Callback<Args extends unknown[], T> = (...args: Args) => T; /** * Represents an object that can have any string or symbol keys with unknown value types. * Useful for type-checking objects with dynamic property access. * @example * const config: KeyValue = { * timeout: 1000, * retry: true, * onError: (err: Error) => {} * }; */ type KeyValue = Record<string | symbol, unknown>; /** * A variant of KeyValue that can also be undefined. * Useful for optional configuration objects or function parameters. * @example * function configure(options?: OptionalKeyValue) { * // options might be undefined * const timeout = options?.timeout; // unknown * } */ type OptionalKeyValue = KeyValue | undefined; /** * Wraps a synchronous function, capturing its return value as an `Ok` result or any thrown error as an `Error` result. * @template T - The type of the success value returned by the callback. * @param callback - A synchronous function that may return a value or throw an error. * @returns {Result<T, CustomError>} A `Result` containing the function's return value (`Ok`) or the caught error (`Error`). * @example * function divide(a: number, b: number): number { * if (b === 0) throw new Error("Division by zero"); * return a / b; * } * const result = wrap(() => divide(10, 2)); // { ok: 5, error: undefined } * const errorResult = wrap(() => divide(10, 0)); // { ok: undefined, error: Error("Division by zero") } */ declare function wrap<T>(callback: () => T): Result<T, CustomError>; /** * Wraps an asynchronous function, capturing its resolved value as an `Ok` result or any rejected error as an `Error` result. * @template T - The type of the success value resolved by the callback's promise. * @param callback - An asynchronous function that returns a `Promise` which may resolve to a value or reject with an error. * @returns {Promise<Result<T, CustomError>>} A `Promise` resolving to a `Result` containing the resolved value (`Ok`) or the caught error (`Error`). * @example * async function divideAsync(a: number, b: number): Promise<number> { * if (b === 0) throw new Error("Division by zero"); * return a / b; * } * const result = await wrapAsync(async () => await divideAsync(10, 2)); // { ok: 5, error: undefined } * const errRes = await wrapAsync(async () => await divideAsync(10, 0)); // { ok: undefined, error: Error("Division by zero") } */ declare function wrapAsync<T>(callback: () => Promise<T>): Promise<Result<T, CustomError>>; /** * Wraps a function that might throw errors into a function that returns a Result type. * @template Args - The tuple type of arguments the function accepts * @template T - The type of value the function returns * @param callback - The function to wrap, which may throw errors * @returns A new function that takes the same arguments but returns a Result type * @example * const divide = (a: number, b: number): number => { * if (b === 0) throw new Error("Division by zero"); * return a / b; * }; * const safeDivide = wrapThrowable(divide); * const result = safeDivide(10, 2); // { ok: 5, error: undefined } * const errorResult = safeDivide(10, 0); // { ok: undefined, error: Error("Division by zero") } */ declare function wrapThrowable<T, Args extends unknown[] = []>(callback: Callback<Args, T>): Callback<Args, Result<T, CustomError>>; /** * Wraps an async function that might throw errors into a function that returns a Promise of Result type. * @template Args - The tuple type of arguments the function accepts * @template T - The type of value the function's Promise resolves to * @param callback - The async function to wrap, which may throw errors or reject its Promise * @returns A new async function that takes the same arguments but returns a Promise of Result type * @example * const fetchJson = wrapAsyncThrowable(async (url: string) => { * const res = await fetch(url); * if (!res.ok) throw new Error("Failed to fetch"); * return res.json(); * }); * * const result = await fetchJson("https://example.com/data.json"); * if (result.isOk()) { * console.log(result.ok); * } else { * console.error(result.error.message); * } */ declare function wrapAsyncThrowable<T, Args extends unknown[] = []>(callback: Callback<Args, Promise<T>>): Callback<Args, Promise<Result<T, CustomError>>>; /** * Creates a successful `Result` in the `Ok` state. * @template T - The type of the success value. * @template E - The error type, must extend `Error` (defaults to `Error`). * @param {T} ok - The success value * @returns {OkState<T, E>} A `Result` in the `Ok` state. * @example * const result = Ok(42); * // result: { ok: 42, error: undefined } // and some helper methods */ declare function Ok<T, E extends Error = Error>(ok: T): OkState<T, E>; /** * Creates a failure `Result` in the `Error` state from an `Error` instance. * @template E - The error type, must extend `Error`. * @template T - The type of the success value (used for type compatibility with `Result`). * @param {E} error - The error instance, must be an instance of `Error` or a subclass. * @returns {ErrorState<E, T>} A `Result` in the `Error` state with the provided error and no value. * @throws {TypeError} If the provided `error` is not an instance of `Error`. * @example * const result = Err(new Error("Something went wrong")); * // result: { ok: undefined, error: Error("Something went wrong") } // and some helper methods */ declare function Err<T = undefined, E extends Error = Error>(error: E): ErrorState<E, T>; declare function ErrFromText<T = undefined>(message: string): ErrorState<Error, T>; /** * Creates a failure `Result` in the `Error` state from custom error properties. * This is particularly useful for creating type-safe error objects with additional metadata. * * @template T - The success type parameter (unused in the error case but maintains Result type compatibility) * @template P - The type of additional properties for the custom error * @param {CustomErrorProps<P>} props - An object containing error properties including an optional message and cause * @returns {ErrorState<CustomError<P>, T>} A `Result` in the `Error` state with the provided error properties * * @example * // Basic usage with custom properties * const result = ErrFromObject<number, { code: number; status: string }>({ * message: 'Resource not found', * code: 404, * status: 'Not Found' * }); * * if (result.isError()) { * console.log(result.error.message); // 'Resource not found' * console.log(result.error.code); // 404 * console.log(result.error.status); // 'Not Found' * } * * // With error cause * try { * // Some operation that might throw * } catch (cause) { * const result = ErrFromObject({ * message: 'Operation failed', * cause, * timestamp: new Date().toISOString() * }); * } */ declare function ErrFromObject<P extends KeyValue = KeyValue, T = undefined>(props: CustomErrorProps<P>): ErrorState<CustomError<P>, T>; export { type CustomError, Err, ErrFromObject, ErrFromText, Ok, type Result, wrap, wrapAsync, wrapAsyncThrowable, wrapThrowable };