lib-result
Version:
A Rust-inspired `Result` type for type-safe error handling in TypeScript and JavaScript
339 lines (335 loc) • 16.4 kB
TypeScript
/** 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 };