typescript-result
Version:
A Result type inspired by Rust and Kotlin that leverages TypeScript's powerful type system to simplify error handling and make your code more readable and maintainable.
971 lines (968 loc) • 46.6 kB
text/typescript
/**
* Utility function to assert that a case is unreachable
* @param value the value which to check for exhaustiveness
*
* @example
* ```ts
* declare const value: "a" | "b" | "c";
*
* switch (value) {
* case "a":
* // do something
* break;
* case "b":
* // do something
* break;
* default: assertUnreachable(value) // TS should complain here
* }
*
* ```
*/
declare function assertUnreachable(value: never): never;
type IsPromiseOrAsyncFunction<T> = T extends AnyAsyncFunction ? true : T extends Promise<any> ? true : false;
type IsFunction<T> = T extends AnyFunction ? true : false;
type IsPromise<T> = T extends AnyPromise ? true : false;
type UnionContainsPromise<Union> = AnyPromise extends Union ? true : false;
type ListContains<Items extends any[]> = Items[number] extends false ? false : true;
type ListContainsPromiseOrAsyncFunction<T extends any[]> = ListContains<{
[Index in keyof T]: IsPromiseOrAsyncFunction<T[Index]>;
}>;
type ListContainsFunction<T extends any[]> = ListContains<{
[Index in keyof T]: IsFunction<T[Index]>;
}>;
type ListContainsPromise<T extends any[]> = ListContains<{
[Index in keyof T]: IsPromise<T[Index]>;
}>;
type Union<T extends any[]> = T[number];
type Unwrap<T> = T extends (...args: any[]) => Promise<infer U> ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T;
type UnwrapList<Items extends any[]> = {
[Index in keyof Items]: Unwrap<Items[Index]>;
};
type InferPromise<T> = T extends Promise<infer U> ? U : never;
type AnyPromise = Promise<any>;
type AnyFunction<Returning = any> = (...args: any[]) => Returning;
type AnyAsyncFunction<Returning = any> = (...args: any[]) => Promise<Returning>;
type NativeError = globalThis.Error;
type AnyValue = {};
type InferError<T> = T extends Result<any, infer Error> ? Error : never;
type InferValue<T> = T extends Result<infer Value, any> ? Value : T;
type InferErrors<Items extends any[]> = {
[Index in keyof Items]: InferError<Items[Index]>;
};
type InferValues<Items extends any[]> = {
[Index in keyof Items]: InferValue<Items[Index]>;
};
type AnyResult = Result<any, any>;
type AnyAsyncResult = AsyncResult<any, any>;
type ValueOr<Value, Err, Or> = [Err] extends [never] ? [Value] extends [never] ? Or : Value : Value | Or;
type ErrorOr<Value, Err, Or> = [Value] extends [never] ? [Err] extends [never] ? Or : Err : Err | Or;
type AccountForFunctionThrowing<Items extends any[]> = ListContainsFunction<Items> extends true ? NativeError : ListContainsPromise<Items> extends true ? NativeError : never;
/**
* Represents the asynchronous outcome of an operation that can either succeed or fail.
*/
declare class AsyncResult<Value, Err> extends Promise<Result<Value, Err>> {
/**
* Utility getter to infer the value type of the result.
* Note: this getter does not hold any value, it's only used for type inference.
*/
$inferValue: Value;
/**
* Utility getter to infer the error type of the result.
* Note: this getter does not hold any value, it's only used for type inference.
*/
$inferError: Err;
/**
* Utility getter to check if the current instance is an `AsyncResult`.
*/
get isAsyncResult(): true;
/**
* @returns the result in a tuple format where the first element is the value and the second element is the error.
* If the result is successful, the error will be `null`. If the result is a failure, the value will be `null`.
*
* This method is especially useful when you want to destructure the result into a tuple and use TypeScript's narrowing capabilities.
*
* @example Narrowing down the result type using destructuring
* ```ts
* declare const result: AsyncResult<number, ErrorA>;
*
* const [value, error] = await result.toTuple();
*
* if (error) {
* // error is ErrorA
* return;
* }
*
* // value must be a number
* ```
*/
toTuple(): Promise<[
Err
] extends [never] ? [value: Value, error: never] : [Value] extends [never] ? [value: never, error: Err] : [value: Value, error: null] | [value: null, error: Err]>;
/**
* @returns the encapsulated error if the result is a failure, otherwise `null`.
*/
errorOrNull(): Promise<ErrorOr<Value, Err, null>>;
/**
* @returns the encapsulated value if the result is successful, otherwise `null`.
*/
getOrNull(): Promise<ValueOr<Value, Err, null>>;
/**
* Retrieves the encapsulated value of the result, or a default value if the result is a failure.
*
* @param defaultValue The value to return if the result is a failure.
*
* @returns The encapsulated value if the result is successful, otherwise the default value.
*
* @example
* obtaining the value of a result, or a default value
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* const value = await result.getOrDefault(0); // number
* ```
*
* @example
* using a different type for the default value
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* const value = await result.getOrDefault("default"); // number | string
* ```
*/
getOrDefault<Else>(defaultValue: Value | Else): Promise<Value | Else>;
/**
* Retrieves the value of the result, or transforms the error using the {@link onFailure} callback into a value.
*
* @param onFailure callback function which allows you to transform the error into a value. The callback can be async as well.
* @returns either the value if the result is successful, or the transformed error.
*
* @example
* transforming the error into a value
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* const value = await result.getOrElse((error) => 0); // number
* ```
*
* @example
* using an async callback
* ```ts
* const value = await result.getOrElse(async (error) => 0); // number
* ```
*/
getOrElse<Else>(onFailure: (error: Err) => Else): Promise<Value | Unwrap<Else>>;
/**
* Retrieves the encapsulated value of the result, or throws an error if the result is a failure.
*
* @returns The encapsulated value if the result is successful.
*
* @throws the encapsulated error if the result is a failure.
*
* @example
* obtaining the value of a result, or throwing an error
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* const value = await result.getOrThrow(); // number
* ```
*/
getOrThrow(): Promise<Value>;
/**
* Returns the result of the {@link onSuccess} callback when the result represents success or
* the result of the {@link onFailure} callback when the result represents a failure.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the callbacks are not caught, so it is your responsibility
* > to handle these exceptions
*
* @param onSuccess callback function to run when the result is successful. The callback can be async as well.
* @param onFailure callback function to run when the result is a failure. The callback can be async as well.
* @returns the result of the callback that was executed.
*
* @example
* folding a result to a response-like object
*
* ```ts
* declare const result: AsyncResult<User, NotFoundError | UserDeactivatedError>;
*
* const response = await result.fold(
* (user) => ({ status: 200, body: user }),
* (error) => {
* switch (error.type) {
* case "not-found":
* return { status: 404, body: "User not found" };
* case "user-deactivated":
* return { status: 403, body: "User is deactivated" };
* }
* }
* );
* ```
*/
fold<SuccessResult, FailureResult>(onSuccess: (value: Value) => SuccessResult, onFailure: (error: Err) => FailureResult): Promise<Unwrap<SuccessResult> | Unwrap<FailureResult>>;
/**
* Calls the {@link action} callback when the result represents a failure. It is meant to be used for
* side-effects and the operation does not modify the result itself.
*
* @param action callback function to run when the result is a failure. The callback can be async as well.
* @returns the original instance of the result.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link action} callback are not caught, so it is your responsibility
* > to handle these exceptions
*
* @example
* adding logging between operations
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* result
* .onFailure((error) => console.error("I'm failing!", error))
* .map((value) => value * 2); // proceed with other operations
* ```
*/
onFailure(action: (error: Err) => void | Promise<void>): AsyncResult<Value, Err>;
/**
* Calls the {@link action} callback when the result represents a success. It is meant to be used for
* side-effects and the operation does not modify the result itself.
*
* @param action callback function to run when the result is successful. The callback can be async as well.
* @returns the original instance of the result.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link action} callback are not caught, so it is your responsibility
* > to handle these exceptions
*
* @example
* adding logging between operations
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* result
* .onSuccess((value) => console.log("I'm a success!", value))
* .map((value) => value * 2); // proceed with other operations
* ```
*
* @example
* using an async callback
* ```ts
* declare const result: AsyncResultResult<number, Error>;
*
* const asyncResult = await result.onSuccess(async (value) => someAsyncOperation(value));
* ```
*/
onSuccess(action: (value: Value) => void | Promise<void>): AsyncResult<Value, Err>;
/**
* Transforms the value of a successful result using the {@link transform} callback.
* The {@link transform} callback can also return other {@link Result} or {@link AsyncResult} instances,
* which will be returned as-is (the `Error` types will be merged).
* The operation will be ignored if the result represents a failure.
*
* @param transform callback function to transform the value of the result. The callback can be async as well.
* @returns a new {@linkcode AsyncResult} instance with the transformed value
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link transform} callback are not caught, so it is your responsibility
* > to handle these exceptions. Please refer to {@linkcode AsyncResult.mapCatching} for a version that catches exceptions
* > and encapsulates them in a failed result.
*
* @example
* transforming the value of a result
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* const transformed = result.map((value) => value * 2); // AsyncResult<number, Error>
* ```
*
* @example
* returning a result instance
* ```ts
* declare const result: AsyncResult<number, Error>;
* declare function multiplyByTwo(value: number): Result<number, Error>;
*
* const transformed = result.map((value) => multiplyByTwo(value)); // AsyncResult<number, Error>
* ```
*
* @example
* doing an async transformation
* ```ts
* declare const result: AsyncResult<number, Error>;
*
* const transformed = result.map(async (value) => value * 2); // AsyncResult<number, Error>
* ```
*
* @example
* returning an async result instance
*
* ```ts
* declare const result: AsyncResult<number, Error>;
* declare function storeValue(value: number): AsyncResult<boolean, Error>;
*
* const transformed = result.map((value) => storeValue(value)); // AsyncResult<boolean, Error>
* ```
*/
map<ReturnType>(transform: (value: Value) => ReturnType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<ResultValue, Err | ResultError> : AsyncResult<PromiseValue, Err> : ReturnType extends Result<infer ResultValue, infer ResultError> ? AsyncResult<ResultValue, Err | ResultError> : AsyncResult<ReturnType, Err>;
/**
* Like {@linkcode AsyncResult.map} it transforms the value of a successful result using the {@link transformValue} callback.
* In addition, it catches any exceptions that might be thrown inside the {@link transformValue} callback and encapsulates them
* in a failed result.
*
* @param transformValue callback function to transform the value of the result. The callback can be async as well.
* @param transformError callback function to transform any potential caught error while transforming the value.
* @returns a new {@linkcode AsyncResult} instance with the transformed value
*/
mapCatching<ReturnType, ErrorType = NativeError>(transformValue: (value: Value) => ReturnType, transformError?: (error: unknown) => ErrorType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<ResultValue, Err | ResultError | ErrorType> : AsyncResult<PromiseValue, Err | ErrorType> : ReturnType extends Result<infer ResultValue, infer ResultError> ? AsyncResult<ResultValue, Err | ResultError | ErrorType> : AsyncResult<ReturnType, Err | ErrorType>;
/**
* Transforms the encapsulated error of a failed result using the {@link transform} callback into a new error.
* This can be useful for instance to capture similar or related errors and treat them as a single higher-level error type
* @param transform callback function to transform the error of the result.
* @returns new {@linkcode AsyncResult} instance with the transformed error.
*
* @example
* transforming the error of a result
* ```ts
* const result = Result.try(() => fetch("https://example.com"))
* .mapCatching((response) => response.json() as Promise<Data>)
* .mapError((error) => new FetchDataError("Failed to fetch data", { cause: error }));
* // AsyncResult<Data, FetchDataError>;
* ```
*/
mapError<NewError>(transform: (error: Err) => NewError): AsyncResult<Value, NewError>;
/**
* Transforms a failed result using the {@link onFailure} callback into a successful result. Useful for falling back to
* other scenarios when a previous operation fails.
* The {@link onFailure} callback can also return other {@link Result} or {@link AsyncResult} instances,
* which will be returned as-is.
* After a recovery, logically, the result can only be a success. Therefore, the error type is set to `never`, unless
* the {@link onFailure} callback returns a result-instance with another error type.
*
* @param onFailure callback function to transform the error of the result. The callback can be async as well.
* @returns a new successful {@linkcode AsyncResult} instance when the result represents a failure, or the original instance
* if it represents a success.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link onFailure} callback are not caught, so it is your responsibility
* > to handle these exceptions. Please refer to {@linkcode AsyncResult.recoverCatching} for a version that catches exceptions
* > and encapsulates them in a failed result.
*
* @example
* transforming the error into a value
* Note: Since we recover after trying to persist in the database, we can assume that the `DbError` has been taken care
* of and therefore it has been removed from the final result.
* ```ts
* declare function persistInDB(item: Item): AsyncResult<Item, DbError>;
* declare function persistLocally(item: Item): AsyncResult<Item, IOError>;
*
* persistInDB(item).recover(() => persistLocally(item)); // AsyncResult<Item, IOError>
* ```
*/
recover<ReturnType>(onFailure: (error: Err) => ReturnType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<Value | ResultValue, ResultError> : AsyncResult<PromiseValue | Value, never> : ReturnType extends Result<infer ResultValue, infer ResultError> ? AsyncResult<Value | ResultValue, ResultError> : AsyncResult<Value | ReturnType, never>;
/**
* Like {@linkcode AsyncResult.recover} it transforms a failed result using the {@link onFailure} callback into a successful result.
* In addition, it catches any exceptions that might be thrown inside the {@link onFailure} callback and encapsulates them
* in a failed result.
*
* @param onFailure callback function to transform the error of the result. The callback can be async as well.
* @returns a new successful {@linkcode AsyncResult} instance when the result represents a failure, or the original instance
* if it represents a success.
*/
recoverCatching<ReturnType>(onFailure: (error: Err) => ReturnType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<Value | ResultValue, ResultError | NativeError> : AsyncResult<PromiseValue | Value, NativeError> : ReturnType extends Result<infer ResultValue, infer ResultError> ? AsyncResult<Value | ResultValue, ResultError | NativeError> : AsyncResult<Value | ReturnType, NativeError>;
/**
* Print-friendly representation of the `AsyncResult` instance.
*/
toString(): string;
}
/**
* Represents the outcome of an operation that can either succeed or fail.
*/
declare class Result<Value, Err> {
private readonly _value;
private readonly _error;
private constructor();
/**
* Utility getter to infer the value type of the result.
* Note: this getter does not hold any value, it's only used for type inference.
*/
$inferValue: Value;
/**
* Utility getter to infer the error type of the result.
* Note: this getter does not hold any value, it's only used for type inference.
*/
$inferError: Err;
/**
* Utility getter that checks if the current instance is a `Result`.
*/
get isResult(): true;
/**
* Retrieves the encapsulated value of the result.
*
* @returns The value if the operation was successful, otherwise `undefined`.
*
* __Note:__ You can use {@linkcode Result.isOk} to narrow down the type to a successful result.
*
* @example
* obtaining the value of a result, without checking if it's successful
* ```ts
* declare const result: Result<number, Error>;
*
* result.value; // number | undefined
* ```
*
* @example
* obtaining the value of a result, after checking for success
* ```ts
* declare const result: Result<number, Error>;
*
* if (result.isOk()) {
* result.value; // number
* }
* ```
*/
get value(): ValueOr<Value, Err, undefined>;
/**
* Retrieves the encapsulated error of the result.
*
* @returns The error if the operation failed, otherwise `undefined`.
*
* > [!NOTE]
* > You can use {@linkcode Result.isError} to narrow down the type to a failed result.
*
* @example
* obtaining the value of a result, without checking if it's a failure
* ```ts
* declare const result: Result<number, Error>;
*
* result.error; // Error | undefined
* ```
*
* @example
* obtaining the error of a result, after checking for failure
* ```ts
* declare const result: Result<number, Error>;
*
* if (result.isError()) {
* result.error; // Error
* }
* ```
*/
get error(): ErrorOr<Value, Err, undefined>;
private get success();
private get failure();
/**
* Type guard that checks whether the result is successful.
*
* @returns `true` if the result is successful, otherwise `false`.
*
* @example
* checking if a result is successful
* ```ts
* declare const result: Result<number, Error>;
*
* if (result.isOk()) {
* result.value; // number
* }
* ```
*/
isOk(): this is Result<[Value] extends [never] ? AnyValue : Value, never>;
/**
* Type guard that checks whether the result is successful.
*
* @returns `true` if the result represents a failure, otherwise `false`.
*
* @example
* checking if a result represents a failure
* ```ts
* declare const result: Result<number, Error>;
*
* if (result.isError()) {
* result.error; // Error
* }
* ```
*/
isError(): this is Result<never, [Err] extends [never] ? AnyValue : Err>;
/**
* @returns the result in a tuple format where the first element is the value and the second element is the error.
* If the result is successful, the error will be `null`. If the result is a failure, the value will be `null`.
*
* This method is especially useful when you want to destructure the result into a tuple and use TypeScript's narrowing capabilities.
*
* @example Narrowing down the result type using destructuring
* ```ts
* declare const result: Result<number, ErrorA>;
*
* const [value, error] = result.toTuple();
*
* if (error) {
* // error is ErrorA
* return;
* }
*
* // value must be a number
* ```
*/
toTuple(): [Err] extends [never] ? [value: Value, error: never] : [Value] extends [never] ? [value: never, error: Err] : [value: Value, error: null] | [value: null, error: Err];
/**
* @returns the encapsulated error if the result is a failure, otherwise `null`.
*/
errorOrNull(): ErrorOr<Value, Err, null>;
/**
* @returns the encapsulated value if the result is successful, otherwise `null`.
*/
getOrNull(): ValueOr<Value, Err, null>;
/**
* Retrieves the value of the result, or a default value if the result is a failure.
*
* @param defaultValue The value to return if the result is a failure.
*
* @returns The encapsulated value if the result is successful, otherwise the default value.
*
* @example
* obtaining the value of a result, or a default value
* ```ts
* declare const result: Result<number, Error>;
*
* const value = result.getOrDefault(0); // number
* ```
*
* @example
* using a different type for the default value
* ```ts
* declare const result: Result<number, Error>;
*
* const value = result.getOrDefault("default"); // number | string
* ```
*/
getOrDefault<Else>(defaultValue: Else): Value | Else;
/**
* Retrieves the value of the result, or transforms the error using the {@link onFailure} callback into a value.
*
* @param onFailure callback function which allows you to transform the error into a value. The callback can be async as well.
* @returns either the value if the result is successful, or the transformed error.
*
* @example
* transforming the error into a value
* ```ts
* declare const result: Result<number, Error>;
*
* const value = result.getOrElse((error) => 0); // number
* ```
*
* @example
* using an async callback
* ```ts
* const value = await result.getOrElse(async (error) => 0); // Promise<number>
* ```
*/
getOrElse<Else>(onFailure: (error: Err) => Else): Else extends Promise<infer U> ? Promise<Value | U> : Value | Else;
/**
* Retrieves the value of the result, or throws an error if the result is a failure.
*
* @returns The value if the result is successful.
*
* @throws the encapsulated error if the result is a failure.
*
* @example
* obtaining the value of a result, or throwing an error
* ```ts
* declare const result: Result<number, Error>;
*
* const value = result.getOrThrow(); // number
* ```
*/
getOrThrow(): Value;
/**
* Returns the result of the {@link onSuccess} callback when the result represents success or
* the result of the {@link onFailure} callback when the result represents a failure.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the callbacks are not caught, so it is your responsibility
* > to handle these exceptions
*
* @param onSuccess callback function to run when the result is successful. The callback can be async as well.
* @param onFailure callback function to run when the result is a failure. The callback can be async as well.
* @returns the result of the callback that was executed.
*
* @example
* folding a result to a response-like object
*
* ```ts
* declare const result: Result<User, NotFoundError | UserDeactivatedError>;
*
* const response = result.fold(
* (user) => ({ status: 200, body: user }),
* (error) => {
* switch (error.type) {
* case "not-found":
* return { status: 404, body: "User not found" };
* case "user-deactivated":
* return { status: 403, body: "User is deactivated" };
* }
* }
* );
* ```
*/
fold<SuccessResult, FailureResult>(onSuccess: (value: Value) => SuccessResult, onFailure: (error: Err) => FailureResult): UnionContainsPromise<SuccessResult | FailureResult> extends true ? Promise<Unwrap<SuccessResult> | Unwrap<FailureResult>> : SuccessResult | FailureResult;
/**
* Calls the {@link action} callback when the result represents a failure. It is meant to be used for
* side-effects and the operation does not modify the result itself.
*
* @param action callback function to run when the result is a failure. The callback can be async as well.
* @returns the original instance of the result.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link action} callback are not caught, so it is your responsibility
* > to handle these exceptions
*
* @example
* adding logging between operations
* ```ts
* declare const result: Result<number, Error>;
*
* result
* .onFailure((error) => console.error("I'm failing!", error))
* .map((value) => value * 2); // proceed with other operations
* ```
*/
onFailure<ReturnValue>(action: (error: Err) => ReturnValue): ReturnValue extends AnyPromise ? AsyncResult<Value, Err> : this;
/**
* Calls the {@link action} callback when the result represents a success. It is meant to be used for
* side-effects and the operation does not modify the result itself.
*
* @param action callback function to run when the result is successful. The callback can be async as well.
* @returns the original instance of the result. If the callback is async, it returns a new {@link AsyncResult} instance.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link action} callback are not caught, so it is your responsibility
* > to handle these exceptions
*
* @example
* adding logging between operations
* ```ts
* declare const result: Result<number, Error>;
*
* result
* .onSuccess((value) => console.log("I'm a success!", value))
* .map((value) => value * 2); // proceed with other operations
* ```
*
* @example
* using an async callback
* ```ts
* declare const result: Result<number, Error>;
*
* const asyncResult = await result.onSuccess(async (value) => someAsyncOperation(value));
* ```
*/
onSuccess(action: (value: Value) => Promise<void>): AsyncResult<Value, Err>;
onSuccess(action: (value: Value) => void): this;
/**
* Transforms the value of a successful result using the {@link transform} callback.
* The {@link transform} callback can also return other {@link Result} or {@link AsyncResult} instances,
* which will be returned as-is (the `Error` types will be merged).
* The operation will be ignored if the result represents a failure.
*
* @param transform callback function to transform the value of the result. The callback can be async as well.
* @returns a new {@linkcode Result} instance with the transformed value, or a new {@linkcode AsyncResult} instance
* if the transform function is async.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link transform} callback are not caught, so it is your responsibility
* > to handle these exceptions. Please refer to {@linkcode Result.mapCatching} for a version that catches exceptions
* > and encapsulates them in a failed result.
*
* @example
* transforming the value of a result
* ```ts
* declare const result: Result<number, Error>;
*
* const transformed = result.map((value) => value * 2); // Result<number, Error>
* ```
*
* @example
* returning a result instance
* ```ts
* declare const result: Result<number, Error>;
* declare function multiplyByTwo(value: number): Result<number, Error>;
*
* const transformed = result.map((value) => multiplyByTwo(value)); // Result<number, Error>
* ```
*
* @example
* doing an async transformation
* ```ts
* declare const result: Result<number, Error>;
*
* const transformed = result.map(async (value) => value * 2); // AsyncResult<number, Error>
* ```
*
* @example
* returning an async result instance
*
* ```ts
* declare const result: Result<number, Error>;
* declare function storeValue(value: number): AsyncResult<boolean, Error>;
*
* const transformed = result.map((value) => storeValue(value)); // AsyncResult<boolean, Error>
* ```
*/
map<ReturnType>(transform: (value: Value) => ReturnType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<ResultValue, Err | ResultError> : AsyncResult<PromiseValue, Err> : ReturnType extends Result<infer ResultValue, infer ResultError> ? Result<ResultValue, Err | ResultError> : Result<ReturnType, Err>;
/**
* Like {@linkcode Result.map} it transforms the value of a successful result using the {@link transformValue} callback.
* In addition, it catches any exceptions that might be thrown inside the {@link transformValue} callback and encapsulates them
* in a failed result.
*
* @param transformValue callback function to transform the value of the result. The callback can be async as well.
* @param transformError callback function to transform any potential caught error while transforming the value.
* @returns a new {@linkcode Result} instance with the transformed value, or a new {@linkcode AsyncResult} instance
* if the transform function is async.
*/
mapCatching<ReturnType, ErrorType = NativeError>(transformValue: (value: Value) => ReturnType, transformError?: (err: unknown) => ErrorType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<ResultValue, Err | ResultError | ErrorType> : AsyncResult<PromiseValue, Err | ErrorType> : ReturnType extends Result<infer ResultValue, infer ResultError> ? Result<ResultValue, Err | ResultError | ErrorType> : Result<ReturnType, Err | ErrorType>;
/**
* Transforms the encapsulated error of a failed result using the {@link transform} callback into a new error.
* This can be useful for instance to capture similar or related errors and treat them as a single higher-level error type
* @param transform callback function to transform the error of the result.
* @returns new {@linkcode Result} instance with the transformed error.
*
* @example
* transforming the error of a result
* ```ts
* declare const result: Result<number, ErrorA>;
*
* result.mapError((error) => new ErrorB(error.message)); // Result<number, ErrorB>
* ```
*/
mapError<NewError>(transform: (error: Err) => NewError): Result<Value, NewError>;
/**
* Transforms a failed result using the {@link onFailure} callback into a successful result. Useful for falling back to
* other scenarios when a previous operation fails.
* The {@link onFailure} callback can also return other {@link Result} or {@link AsyncResult} instances,
* which will be returned as-is.
* After a recovery, logically, the result can only be a success. Therefore, the error type is set to `never`, unless
* the {@link onFailure} callback returns a result-instance with another error type.
*
* @param onFailure callback function to transform the error of the result. The callback can be async as well.
* @returns a new successful {@linkcode Result} instance or a new successful {@linkcode AsyncResult} instance
* when the result represents a failure, or the original instance if it represents a success.
*
* > [!NOTE]
* > Any exceptions that might be thrown inside the {@link onFailure} callback are not caught, so it is your responsibility
* > to handle these exceptions. Please refer to {@linkcode Result.recoverCatching} for a version that catches exceptions
* > and encapsulates them in a failed result.
*
* @example
* transforming the error into a value
* Note: Since we recover after trying to persist in the database, we can assume that the `DbError` has been taken care
* of and therefore it has been removed from the final result.
* ```ts
* declare function persistInDB(item: Item): Result<Item, DbError>;
* declare function persistLocally(item: Item): Result<Item, IOError>;
*
* persistInDB(item).recover(() => persistLocally(item)); // Result<Item, IOError>
* ```
*/
recover<ReturnType>(onFailure: (error: Err) => ReturnType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<Value | ResultValue, ResultError> : AsyncResult<PromiseValue | Value, never> : ReturnType extends Result<infer ResultValue, infer ResultError> ? Result<Value | ResultValue, ResultError> : Result<Value | ReturnType, never>;
/**
* Like {@linkcode Result.recover} it transforms a failed result using the {@link onFailure} callback into a successful result.
* In addition, it catches any exceptions that might be thrown inside the {@link onFailure} callback and encapsulates them
* in a failed result.
*
* @param onFailure callback function to transform the error of the result. The callback can be async as well.
* @returns a new successful {@linkcode Result} instance or a new successful {@linkcode AsyncResult} instance
* when the result represents a failure, or the original instance if it represents a success.
*/
recoverCatching<ReturnType>(onFailure: (error: Err) => ReturnType): ReturnType extends Promise<infer PromiseValue> ? PromiseValue extends Result<infer ResultValue, infer ResultError> ? AsyncResult<Value | ResultValue, ResultError | NativeError> : AsyncResult<PromiseValue | Value, NativeError> : ReturnType extends Result<infer ResultValue, infer ResultError> ? Result<Value | ResultValue, ResultError | NativeError> : Result<Value | ReturnType, NativeError>;
/**
* Returns a string representation of the result.
*/
toString(): string;
/**
* Creates a new result instance that represents a successful outcome.
*
* @param value The value to encapsulate in the result.
* @returns a new {@linkcode Result} instance.
*
* @example
* ```ts
* const result = Result.ok(42); // Result<number, never>
* ```
*/
static ok(): Result<void, never>;
static ok<Value>(value: Value): Result<Value, never>;
/**
* Creates a new result instance that represents a failed outcome.
*
* @param error The error to encapsulate in the result.
* @returns a new {@linkcode Result} instance.
*
* @example
* ```ts
* const result = Result.error(new NotFoundError()); // Result<never, NotFoundError>
* ```
*/
static error<Error>(error: Error): Result<never, Error>;
/**
* Type guard that checks whether the provided value is a {@linkcode Result} instance.
*
* @param possibleResult any value that might be a {@linkcode Result} instance.
* @returns true if the provided value is a {@linkcode Result} instance, otherwise false.
*/
static isResult(possibleResult: unknown): possibleResult is AnyResult;
/**
* Type guard that checks whether the provided value is a {@linkcode AsyncResult} instance.
*
* @param possibleAsyncResult any value that might be a {@linkcode AsyncResult} instance.
* @returns true if the provided value is a {@linkcode AsyncResult} instance, otherwise false.
*/
static isAsyncResult(possibleAsyncResult: unknown): possibleAsyncResult is AnyAsyncResult;
private static run;
private static allInternal;
/**
* Similar to {@linkcode Promise.all}, but for results.
* Useful when you want to run multiple independent operations and bundle the outcome into a single result.
* All possible values of the individual operations are collected into an array. `Result.all` will fail eagerly,
* meaning that as soon as any of the operations fail, the entire result will be a failure.
* Each argument can be a mixture of literal values, functions, {@linkcode Result} or {@linkcode AsyncResult} instances, or {@linkcode Promise}.
*
* @param items one or multiple literal value, function, {@linkcode Result} or {@linkcode AsyncResult} instance, or {@linkcode Promise}.
* @returns combined result of all the operations.
*
* > [!NOTE]
* > Any exceptions that might be thrown are not caught, so it is your responsibility
* > to handle these exceptions. Please refer to {@linkcode Result.allCatching} for a version that catches exceptions
* > and encapsulates them in a failed result.
*
* @example
* basic usage
* ```ts
* declare function createTask(name: string): Result<Task, IOError>;
*
* const tasks = ["task-a", "task-b", "task-c"];
* const result = Result.all(...tasks.map(createTask)); // Result<Task[], IOError>
* ```
*
* @example
* running multiple operations and combining the results
* ```ts
* const result = Result.all(
* "a",
* Promise.resolve("b"),
* Result.ok("c"),
* Result.try(async () => "d"),
* () => "e",
* () => Result.try(async () => "f"),
* () => Result.ok("g"),
* async () => "h",
* ); // AsyncResult<[string, string, string, string, string, string, string, string], Error>
* ```
*/
static all<Items extends any[], Unwrapped extends any[] = UnwrapList<Items>>(...items: Items): ListContainsPromiseOrAsyncFunction<Items> extends true ? AsyncResult<InferValues<Unwrapped>, Union<InferErrors<Unwrapped>>> : Result<InferValues<Unwrapped>, Union<InferErrors<Unwrapped>>>;
/**
* Similar to {@linkcode Result.all}, but catches any exceptions that might be thrown during the operations.
* @param items one or multiple literal value, function, {@linkcode Result} or {@linkcode AsyncResult} instance, or {@linkcode Promise}.
* @returns combined result of all the operations.
*/
static allCatching<Items extends any[], Unwrapped extends any[] = UnwrapList<Items>>(...items: Items): ListContainsPromiseOrAsyncFunction<Items> extends true ? AsyncResult<InferValues<Unwrapped>, Union<InferErrors<Unwrapped>> | AccountForFunctionThrowing<Items>> : Result<InferValues<Unwrapped>, Union<InferErrors<Unwrapped>> | AccountForFunctionThrowing<Items>>;
/**
* Wraps a function and returns a new function that returns a result. Especially useful when you want to work with
* external functions that might throw exceptions.
* The returned function will catch any exceptions that might be thrown and encapsulate them in a failed result.
*
* @param fn function to wrap. Can be synchronous or asynchronous.
* @returns a new function that returns a result.
*
* @example
* basic usage
* ```ts
* declare function divide(a: number, b: number): number;
*
* const safeDivide = Result.wrap(divide);
* const result = safeDivide(10, 0); // Result<number, Error>
* ```
*/
static wrap<Fn extends AnyAsyncFunction>(fn: Fn): (...args: Parameters<Fn>) => AsyncResult<InferPromise<ReturnType<Fn>>, NativeError>;
static wrap<Fn extends AnyFunction>(fn: Fn): (...args: Parameters<Fn>) => Result<ReturnType<Fn>, NativeError>;
/**
* Executes the given {@linkcode fn} function and encapsulates the returned value as a successful result, or the
* thrown exception as a failed result. In a way, you can view this method as a try-catch block that returns a result.
*
* @param fn function with code to execute. Can be synchronous or asynchronous.
* @param transform optional callback to transform the caught error into a more meaningful error.
* @returns a new {@linkcode Result} instance.
*
* @example
* basic usage
* ```ts
* declare function saveFileToDisk(filename: string): void; // might throw an error
*
* const result = Result.try(() => saveFileToDisk("file.txt")); // Result<void, Error>
* ```
*
* @example
* basic usage with error transformation
* ```ts
* declare function saveFileToDisk(filename: string): void; // might throw an error
*
* const result = Result.try(
* () => saveFileToDisk("file.txt"),
* (error) => new IOError("Failed to save file", { cause: error })
* ); // Result<void, IOError>
* ```
*/
static try<Fn extends AnyAsyncFunction<AnyResult>, R = InferPromise<ReturnType<Fn>>>(fn: Fn): AsyncResult<InferValue<R>, InferError<R> | NativeError>;
static try<Fn extends AnyFunction<AnyResult>, R = ReturnType<Fn>>(fn: Fn): Result<InferValue<R>, InferError<R> | NativeError>;
static try<ReturnType extends AnyPromise>(fn: () => ReturnType): AsyncResult<InferPromise<ReturnType>, NativeError>;
static try<ReturnType>(fn: () => ReturnType): Result<ReturnType, NativeError>;
static try<ReturnType extends AnyPromise, ErrorType extends AnyValue>(fn: () => ReturnType, transform: (error: unknown) => ErrorType): AsyncResult<InferPromise<ReturnType>, ErrorType>;
static try<ReturnType, ErrorType extends AnyValue>(fn: () => ReturnType, transform: (error: unknown) => ErrorType): Result<ReturnType, ErrorType>;
/**
* Utility method to transform a Promise, that holds a literal value or
* a {@linkcode Result} or {@linkcode AsyncResult} instance, into an {@linkcode AsyncResult} instance. Useful when you want to immediately chain operations
* after calling an async function.
*
* @param value a Promise that holds a literal value or a {@linkcode Result} or {@linkcode AsyncResult} instance.
*
* @returns a new {@linkcode AsyncResult} instance.
*
* > [!NOTE]
* > Any exceptions that might be thrown are not caught, so it is your responsibility
* > to handle these exceptions. Please refer to {@linkcode Result.fromAsyncCatching} for a version that catches exceptions
* > and encapsulates them in a failed result.
*
* @example
* basic usage
*
* ```ts
* declare function someAsyncOperation(): Promise<Result<number, Error>>;
*
* // without 'Result.fromAsync'
* const result = (await someAsyncOperation()).map((value) => value * 2); // Result<number, Error>
*
* // with 'Result.fromAsync'
* const asyncResult = Result.fromAsync(someAsyncOperation()).map((value) => value * 2); // AsyncResult<number, Error>
* ```
*/
static fromAsync<T extends Promise<AnyAsyncResult>>(value: T): T extends Promise<AsyncResult<infer V, infer E>> ? AsyncResult<V, E> : never;
static fromAsync<T extends Promise<AnyResult>>(value: T): T extends Promise<Result<infer V, infer E>> ? AsyncResult<V, E> : never;
static fromAsync<T extends AnyPromise>(value: T): T extends Promise<infer V> ? AsyncResult<V, never> : never;
/**
* Similar to {@linkcode Result.fromAsync} this method transforms a Promise into an {@linkcode AsyncResult} instance.
* In addition, it catches any exceptions that might be thrown during the operation and encapsulates them in a failed result.
*/
static fromAsyncCatching<T extends Promise<AnyAsyncResult>>(value: T): T extends Promise<AsyncResult<infer V, infer E>> ? AsyncResult<V, E | NativeError> : never;
static fromAsyncCatching<T extends Promise<AnyResult>>(value: T): T extends Promise<Result<infer V, infer E>> ? AsyncResult<V, E | NativeError> : never;
static fromAsyncCatching<T extends AnyPromise>(value: T): T extends Promise<infer V> ? AsyncResult<V, NativeError> : never;
/**
* Asserts that the provided result is successful. If the result is a failure, an error is thrown.
* Useful in unit tests.
*
* @param result the result instance to assert against.
*/
static assertOk<Value>(result: Result<Value, any>): asserts result is Result<Value, never>;
/**
* Asserts that the provided result is a failure. If the result is successful, an error is thrown.
* Useful in unit tests.
*
* @param result the result instance to assert against.
*/
static assertError<Err>(result: Result<any, Err>): asserts result is Result<never, Err>;
}
export { AsyncResult, Result, assertUnreachable };