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.
1,161 lines (1,158 loc) • 40.2 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
AsyncResult: () => AsyncResult,
Result: () => Result,
assertUnreachable: () => assertUnreachable
});
module.exports = __toCommonJS(src_exports);
// src/helpers.ts
function isPromise(value) {
if (value === null || value === void 0) {
return false;
}
if (typeof value !== "object") {
return false;
}
return value instanceof Promise || "then" in value;
}
function isFunction(value) {
return typeof value === "function";
}
function isAsyncFn(fn) {
return fn.constructor.name === "AsyncFunction";
}
function assertUnreachable(value) {
throw new Error(`Unreachable case: ${value}`);
}
// src/result.ts
var AsyncResult = class _AsyncResult extends Promise {
/**
* Utility getter to check if the current instance is an `AsyncResult`.
*/
get isAsyncResult() {
return 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
* ```
*/
async toTuple() {
const result = await this;
return result.toTuple();
}
/**
* @returns the encapsulated error if the result is a failure, otherwise `null`.
*/
async errorOrNull() {
const result = await this;
return result.errorOrNull();
}
/**
* @returns the encapsulated value if the result is successful, otherwise `null`.
*/
async getOrNull() {
const result = await this;
return result.getOrNull();
}
/**
* 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
* ```
*/
async getOrDefault(defaultValue) {
const result = await this;
return result.getOrDefault(defaultValue);
}
/**
* 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
* ```
*/
async getOrElse(onFailure) {
const result = await this;
return result.getOrElse(onFailure);
}
/**
* 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
* ```
*/
async getOrThrow() {
const result = await this;
return result.getOrThrow();
}
/**
* 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" };
* }
* }
* );
* ```
*/
async fold(onSuccess, onFailure) {
const result = await this;
return result.fold(onSuccess, onFailure);
}
/**
* 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) {
return new _AsyncResult(
(resolve, reject) => this.then(async (result) => {
try {
if (result.isError()) {
await action(result.error);
}
resolve(result);
} catch (e) {
reject(e);
}
}).catch(reject)
);
}
/**
* 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) {
return new _AsyncResult(
(resolve, reject) => this.then(async (result) => {
try {
if (result.isOk()) {
await action(result.value);
}
resolve(result);
} catch (error) {
reject(error);
}
}).catch(reject)
);
}
/**
* 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(transform) {
return new _AsyncResult(
(resolve, reject) => this.then((result) => {
if (result.isOk()) {
try {
const returnValue = transform(result.value);
if (isPromise(returnValue)) {
returnValue.then(
(value) => resolve(Result.isResult(value) ? value : Result.ok(value))
).catch(reject);
} else {
resolve(
Result.isResult(returnValue) ? returnValue : Result.ok(returnValue)
);
}
} catch (error) {
reject(error);
}
} else {
resolve(result);
}
}).catch(reject)
);
}
/**
* 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(transformValue, transformError) {
return new _AsyncResult((resolve, reject) => {
this.map(transformValue).then((result) => resolve(result)).catch((error) => {
try {
resolve(
Result.error(transformError ? transformError(error) : error)
);
} catch (err) {
reject(err);
}
});
});
}
/**
* 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(transform) {
return new _AsyncResult(
(resolve, reject) => this.then(async (result) => {
try {
resolve(result.mapError(transform));
} catch (error) {
reject(error);
}
}).catch(reject)
);
}
/**
* 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(onFailure) {
return new _AsyncResult(
(resolve, reject) => this.then(async (result) => {
try {
const outcome = await result.recover(onFailure);
resolve(outcome);
} catch (error) {
reject(error);
}
}).catch(reject)
);
}
/**
* 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(onFailure) {
return new _AsyncResult(
(resolve, reject) => this.then((result) => {
resolve(result.recoverCatching(onFailure));
}).catch(reject)
);
}
/**
* Print-friendly representation of the `AsyncResult` instance.
*/
toString() {
return "AsyncResult";
}
/**
* @internal
*/
static error(error) {
return new _AsyncResult((resolve) => resolve(Result.error(error)));
}
/**
* @internal
*/
static ok(value) {
return new _AsyncResult((resolve) => resolve(Result.ok(value)));
}
/**
* @internal
*/
static fromPromise(promise) {
return new _AsyncResult((resolve, reject) => {
promise.then(
(value) => resolve(Result.isResult(value) ? value : Result.ok(value))
).catch(reject);
});
}
/**
* @internal
*/
static fromPromiseCatching(promise, transform) {
return new _AsyncResult((resolve) => {
promise.then(
(value) => resolve(Result.isResult(value) ? value : Result.ok(value))
).catch((caughtError) => {
resolve(Result.error(transform?.(caughtError) ?? caughtError));
});
});
}
};
var Result = class _Result {
constructor(_value, _error) {
this._value = _value;
this._error = _error;
}
/**
* Utility getter that checks if the current instance is a `Result`.
*/
get isResult() {
return 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() {
return this._value;
}
/**
* 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() {
return this._error;
}
get success() {
return this.error === void 0;
}
get failure() {
return this.error !== void 0;
}
/**
* 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() {
return this.success;
}
/**
* 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() {
return this.failure;
}
/**
* @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() {
return [this._value ?? null, this._error ?? null];
}
/**
* @returns the encapsulated error if the result is a failure, otherwise `null`.
*/
errorOrNull() {
return this.failure ? this._error : null;
}
/**
* @returns the encapsulated value if the result is successful, otherwise `null`.
*/
getOrNull() {
return this.success ? this._value : 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(defaultValue) {
return this.success ? this._value : defaultValue;
}
/**
* 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(onFailure) {
if (isAsyncFn(onFailure)) {
return this.success ? Promise.resolve(this._value) : onFailure(this._error);
}
return this.success ? this._value : onFailure(this._error);
}
/**
* 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() {
if (this.success) {
return this._value;
}
throw this._error;
}
/**
* 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(onSuccess, onFailure) {
const isAsync = isAsyncFn(onSuccess) || isAsyncFn(onFailure);
const outcome = this.success ? onSuccess(this._value) : onFailure(this._error);
return isAsync && !isPromise(outcome) ? Promise.resolve(outcome) : outcome;
}
/**
* 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(action) {
const isAsync = isAsyncFn(action);
if (this.failure) {
const outcome = action(this._error);
if (isAsync) {
return new AsyncResult((resolve) => {
outcome.then(
() => resolve(_Result.error(this._error))
);
});
}
return this;
}
return isAsync ? AsyncResult.ok(this._value) : this;
}
onSuccess(action) {
const isAsync = isAsyncFn(action);
if (this.success) {
const outcome = action(this._value);
if (isAsync) {
return new AsyncResult((resolve) => {
outcome.then(() => resolve(_Result.ok(this._value)));
});
}
return this;
}
return isAsync ? AsyncResult.error(this._error) : 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(transform) {
return this.success ? _Result.run(() => transform(this._value)) : isAsyncFn(transform) ? AsyncResult.error(this._error) : this;
}
/**
* 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(transformValue, transformError) {
return this.success ? _Result.try(
() => transformValue(this._value),
transformError
) : this;
}
/**
* 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(transform) {
if (this.success) {
return this;
}
return _Result.error(transform(this._error));
}
/**
* 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(onFailure) {
return this.success ? isAsyncFn(onFailure) ? AsyncResult.ok(this._value) : this : _Result.run(() => onFailure(this._error));
}
/**
* 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(onFailure) {
return this.success ? isAsyncFn(onFailure) ? AsyncResult.ok(this._value) : this : _Result.try(() => onFailure(this._error));
}
/**
* Returns a string representation of the result.
*/
toString() {
if (this.success) {
return `Result.ok(${this._value})`;
}
return `Result.error(${this.error})`;
}
static ok(value) {
return new _Result(value, void 0);
}
/**
* 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) {
return new _Result(void 0, 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) {
return possibleResult instanceof _Result;
}
/**
* 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) {
return possibleAsyncResult instanceof AsyncResult;
}
static run(fn) {
const returnValue = fn();
if (isPromise(returnValue)) {
return AsyncResult.fromPromise(returnValue);
}
return _Result.isResult(returnValue) ? returnValue : _Result.ok(returnValue);
}
static allInternal(items, opts) {
const runner = opts.catching ? _Result.try : _Result.run;
const flattened = [];
let isAsync = items.some(isPromise);
let hasFailure = false;
for (const item of items) {
if (isFunction(item)) {
if (hasFailure) {
continue;
}
const returnValue = runner(item);
if (_Result.isResult(returnValue) && returnValue.isError()) {
hasFailure = true;
if (!isAsync) {
return returnValue;
}
}
if (_Result.isAsyncResult(returnValue)) {
isAsync = true;
}
flattened.push(returnValue);
} else if (_Result.isResult(item)) {
if (item.isError()) {
hasFailure = true;
if (!isAsync) {
return item;
}
}
flattened.push(item);
} else if (_Result.isAsyncResult(item)) {
isAsync = true;
flattened.push(item);
} else if (isPromise(item)) {
isAsync = true;
flattened.push(
opts.catching ? AsyncResult.fromPromiseCatching(item) : AsyncResult.fromPromise(item)
);
} else {
flattened.push(_Result.ok(item));
}
}
if (isAsync) {
return new AsyncResult((resolve, reject) => {
const asyncResults = [];
const asyncIndexes = [];
for (let i = 0; i < flattened.length; i++) {
const item = flattened[i];
if (_Result.isAsyncResult(item)) {
asyncResults.push(item);
asyncIndexes.push(i);
}
}
Promise.all(asyncResults).then((resolvedResults) => {
const merged = [...flattened];
for (let i = 0; i < resolvedResults.length; i++) {
merged[asyncIndexes[i]] = resolvedResults[i];
}
const firstFailedResult = merged.find(
(resolvedResult) => resolvedResult.isError()
);
if (firstFailedResult) {
resolve(firstFailedResult);
return;
}
resolve(_Result.ok(merged.map((result) => result.getOrNull())));
}).catch((reason) => {
reject(reason);
});
});
}
return _Result.ok(
flattened.map((result) => result.getOrNull())
);
}
/**
* 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) {
return _Result.allInternal(items, {
catching: false
});
}
/**
* 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) {
return _Result.allInternal(items, {
catching: true
});
}
static wrap(fn) {
return function wrapped(...args) {
return _Result.try(() => fn(...args));
};
}
static try(fn, transform) {
try {
const returnValue = fn();
if (isPromise(returnValue)) {
return AsyncResult.fromPromiseCatching(returnValue, transform);
}
return _Result.isResult(returnValue) ? returnValue : _Result.ok(returnValue);
} catch (caughtError) {
return _Result.error(transform?.(caughtError) ?? caughtError);
}
}
static fromAsync(value) {
return _Result.run(() => value);
}
static fromAsyncCatching(value) {
return _Result.try(() => value);
}
/**
* 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(result) {
if (result.isError()) {
throw new Error("Expected a successful result, but got an error instead");
}
}
/**
* 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(result) {
if (result.isOk()) {
throw new Error("Expected a failed result, but got a value instead");
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AsyncResult,
Result,
assertUnreachable
});
//# sourceMappingURL=index.cjs.map