UNPKG

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,132 lines (1,131 loc) 39.1 kB
// 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"); } } }; export { AsyncResult, Result, assertUnreachable }; //# sourceMappingURL=index.js.map