UNPKG

ts-data-forge

Version:

[![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![License](https://img.shields.

546 lines (543 loc) 21.4 kB
import { isRecord } from '../guard/is-record.mjs'; import { unknownToString } from '../others/unknown-to-string.mjs'; import { Optional } from './optional.mjs'; /** @internal String literal tag to identify the 'Ok' variant of Result. */ const OkTypeTagName = 'ts-data-forge::Result.ok'; /** @internal String literal tag to identify the 'Err' variant of Result. */ const ErrTypeTagName = 'ts-data-forge::Result.err'; /** * Namespace for the `Result` type and related functions. * Provides utilities to handle operations that can succeed or fail. */ var Result; (function (Result) { /** * Checks if the given value is a `Result`. * @param maybeOptional The value to check. * @returns `true` if the value is a `Result`, otherwise `false`. */ Result.isResult = (maybeOptional) => isRecord(maybeOptional) && Object.hasOwn(maybeOptional, '$$tag') && Object.hasOwn(maybeOptional, 'value') && (maybeOptional['$$tag'] === ErrTypeTagName || maybeOptional['$$tag'] === OkTypeTagName); /** * Creates a `Result.Ok` containing the given success value. * * Use this constructor when an operation succeeds and you want to wrap * the successful result in a Result type for consistent error handling. * * @template S The type of the success value. * @param value The success value. * @returns A `Result.Ok<S>` containing the value. * @example * ```typescript * // Basic success case * const success = Result.ok(42); * console.log(Result.isOk(success)); // true * console.log(Result.unwrapOk(success)); // 42 * * // Function that returns a Result * function divide(a: number, b: number): Result<number, string> { * if (b === 0) { * return Result.err("Division by zero"); * } * return Result.ok(a / b); * } * * const result = divide(10, 2); * console.log(Result.unwrapOk(result)); // 5 * ``` */ Result.ok = (value) => ({ $$tag: OkTypeTagName, value, }); /** * Creates a `Result.Err` containing the given error value. * * Use this constructor when an operation fails and you want to wrap * the error information in a Result type for consistent error handling. * * @template E The type of the error value. * @param value The error value. * @returns A `Result.Err<E>` containing the value. * @example * ```typescript * // Basic error case * const failure = Result.err("Something went wrong"); * console.log(Result.isErr(failure)); // true * console.log(Result.unwrapErr(failure)); // "Something went wrong" * * // Function that can fail * function parseInteger(input: string): Result<number, string> { * const num = parseInt(input, 10); * if (isNaN(num)) { * return Result.err(`Invalid number format: ${input}`); * } * return Result.ok(num); * } * * const result = parseInteger("abc"); * console.log(Result.unwrapErr(result)); // "Invalid number format: abc" * * // Using custom error types * interface ValidationError { * field: string; * message: string; * } * * const validationError = Result.err<ValidationError>({ * field: "email", * message: "Invalid email format" * }); * ``` */ Result.err = (value) => ({ $$tag: ErrTypeTagName, value, }); /** * @internal * Default string conversion function using native String constructor. */ const toStr_ = String; /** * Checks if a `Result` is `Result.Ok`. * Acts as a type guard, narrowing the type to the success variant. * * This function is essential for type-safe Result handling, allowing * TypeScript to understand that subsequent operations will work with * the success value rather than the error value. * * @template R The `Result.Base` type to check. * @param result The `Result` to check. * @returns `true` if the `Result` is `Result.Ok`, otherwise `false`. * @example * ```typescript * // Basic type guard usage * const result: Result<number, string> = divide(10, 2); * * if (Result.isOk(result)) { * // TypeScript knows result is Result.Ok<number> * console.log(result.value); // Safe to access .value * console.log(Result.unwrapOk(result)); // 5 * } else { * // TypeScript knows result is Result.Err<string> * console.log(result.value); // Error message * } * * // Using in conditional logic * const processResult = (r: Result<string, Error>) => { * return Result.isOk(r) * ? r.value.toUpperCase() // Safe string operations * : "Error occurred"; * }; * * // Filtering arrays of Results * const results: Result<number, string>[] = [ * Result.ok(1), * Result.err("error"), * Result.ok(2) * ]; * const successes = results.filter(Result.isOk); * // successes is Result.Ok<number>[] * ``` */ Result.isOk = (result) => result.$$tag === OkTypeTagName; /** * Checks if a `Result` is `Result.Err`. * Acts as a type guard, narrowing the type to the error variant. * * This function is essential for type-safe Result handling, allowing * TypeScript to understand that subsequent operations will work with * the error value rather than the success value. * * @template R The `Result.Base` type to check. * @param result The `Result` to check. * @returns `true` if the `Result` is `Result.Err`, otherwise `false`. * @example * ```typescript * // Basic type guard usage * const result: Result<number, string> = divide(10, 0); * * if (Result.isErr(result)) { * // TypeScript knows result is Result.Err<string> * console.log(result.value); // Safe to access error .value * console.log(Result.unwrapErr(result)); // "Division by zero" * } else { * // TypeScript knows result is Result.Ok<number> * console.log(result.value); // Success value * } * * // Error handling patterns * const handleResult = (r: Result<Data, ApiError>) => { * if (Result.isErr(r)) { * logError(r.value); // Safe error operations * return null; * } * return processData(r.value); * }; * * // Collecting errors from multiple Results * const results: Result<string, ValidationError>[] = validateForm(); * const errors = results * .filter(Result.isErr) * .map(err => err.value); // ValidationError[] * ``` */ Result.isErr = (result) => result.$$tag === ErrTypeTagName; /** * Unwraps a `Result`, returning the success value. * Throws an error if the `Result` is `Result.Err`. * * This is useful when you're confident that a Result should contain a success value * and want to treat errors as exceptional conditions. The error message will be * constructed from the error value using the provided string conversion function. * * @template R The `Result.Base` type to unwrap. * @param result The `Result` to unwrap. * @param toStr An optional function to convert the error value to a string for the error message. Defaults to `String`. * @returns The success value if `Result.Ok`. * @throws {Error} Error with the stringified error value if the `Result` is `Result.Err`. * @example * ```typescript * const success = Result.ok(42); * console.log(Result.unwrapThrow(success)); // 42 * * const failure = Result.err("Network error"); * try { * Result.unwrapThrow(failure); // throws Error: "Network error" * } catch (error) { * console.log(error.message); // "Network error" * } * ``` */ Result.unwrapThrow = (result, toStr = toStr_) => { if (Result.isErr(result)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion throw new Error(toStr(result.value)); } // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion return result.value; }; function unwrapOk(result) { return Result.isErr(result) ? undefined : // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion result.value; } Result.unwrapOk = unwrapOk; function unwrapOkOr(...args) { switch (args.length) { case 2: { // Direct version: first argument is result const [result, defaultValue] = args; // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion return Result.isErr(result) ? defaultValue : result.value; } case 1: { // Curried version: first argument is default value const [defaultValue] = args; return (result) => unwrapOkOr(result, defaultValue); } } } Result.unwrapOkOr = unwrapOkOr; /** * Unwraps a `Result`, returning the error value. * Throws an error if the `Result` is `Result.Ok`. * * This function is used when you expect a Result to be an error and want to * extract the error value. If the Result is unexpectedly Ok, it will throw * an error with information about the unexpected success value. * * @template R The `Result.Base` type to unwrap. * @param result The `Result` to unwrap. * @param toStr An optional function to convert the success value to a string for the error message when the Result is unexpectedly Ok. Defaults to `String`. * @returns The error value if `Result.Err`. * @throws {Error} Error with message "Expected Err but got Ok: {value}" if the `Result` is `Result.Ok`. * @example * ```typescript * const failure = Result.err("Network timeout"); * console.log(Result.unwrapErrThrow(failure)); // "Network timeout" * * const success = Result.ok(42); * try { * Result.unwrapErrThrow(success); // throws Error: "Expected Err but got Ok: 42" * } catch (error) { * console.log(error.message); // "Expected Err but got Ok: 42" * } * ``` */ Result.unwrapErrThrow = (result, toStr = toStr_) => { if (Result.isOk(result)) { throw new Error( // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion `Expected Err but got Ok: ${toStr(result.value)}`); } // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion return result.value; }; /** * Unwraps a `Result`, returning the error value or `undefined` if it is `Result.Ok`. * * This provides a safe way to extract error values from Results without throwing * exceptions. Useful for error handling patterns where you want to check for * specific error conditions. * * @template R The `Result.Base` type to unwrap. * @param result The `Result` to unwrap. * @returns The error value if `Result.Err`, otherwise `undefined`. * @example * ```typescript * const failure = Result.err("Connection failed"); * console.log(Result.unwrapErr(failure)); // "Connection failed" * * const success = Result.ok(42); * console.log(Result.unwrapErr(success)); // undefined * ``` */ Result.unwrapErr = (result) => // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Result.isErr(result) ? result.value : undefined; function unwrapErrOr(...args) { switch (args.length) { case 2: { // Direct version: first argument is result const [result, defaultValue] = args; // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion return Result.isErr(result) ? result.value : defaultValue; } case 1: { // Curried version: first argument is default value const [defaultValue] = args; return (result) => unwrapErrOr(result, defaultValue); } } } Result.unwrapErrOr = unwrapErrOr; function map(...args) { switch (args.length) { case 2: { const [result, mapFn] = args; return Result.isErr(result) ? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion result : // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Result.ok(mapFn(result.value)); } case 1: { // Curried version: first argument is mapping function const [mapFn] = args; return (result) => map(result, mapFn); } } } Result.map = map; function mapErr(...args) { switch (args.length) { case 2: { const [result, mapFn] = args; return Result.isOk(result) ? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion result : // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Result.err(mapFn(result.value)); } case 1: { // Curried version: first argument is mapping function const [mapFn] = args; return (result) => mapErr(result, mapFn); } } } Result.mapErr = mapErr; function fold(...args) { switch (args.length) { case 3: { const [result, mapFn, mapErrFn] = args; return Result.isOk(result) ? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Result.ok(mapFn(result.value)) : // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Result.err(mapErrFn(result.value)); } case 2: { const [mapFn, mapErrFn] = args; return (result) => Result.isOk(result) ? Result.ok(mapFn(result.value)) : Result.err(mapErrFn(result.value)); } } } Result.fold = fold; function flatMap(...args) { switch (args.length) { case 2: { const [result, flatMapFn] = args; return Result.isErr(result) ? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion result : // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion flatMapFn(result.value); } case 1: { const [flatMapFn] = args; return (result) => Result.isErr(result) ? result : flatMapFn(result.value); } } } Result.flatMap = flatMap; function expectToBe(...args) { switch (args.length) { case 2: { // Direct version: first argument is result const [result, message] = args; if (Result.isOk(result)) { return unwrapOk(result); } throw new Error(message); } case 1: { // Curried version: first argument is message const [message] = args; return (result) => expectToBe(result, message); } } } Result.expectToBe = expectToBe; /** * Converts a Promise into a Promise that resolves to a `Result`. * If the input Promise resolves, the `Result` will be `Ok` with the resolved value. * If the input Promise rejects, the `Result` will be `Err` with the rejection reason. * @template P The type of the input Promise. * @param promise The Promise to convert. * @returns A Promise that resolves to `Result<UnwrapPromise<P>, unknown>`. */ Result.fromPromise = (promise) => // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion promise.then((v) => Result.ok(v)).catch(Result.err); /** * Wraps a function that may throw an exception in a `Result`. * * This is a fundamental utility for converting traditional exception-based error * handling into Result-based error handling. Any thrown value is converted to an * Error object for consistent error handling. * * If the function executes successfully, returns `Result.Ok` with the result. * If the function throws, returns `Result.Err` with the caught error. * * @template T The return type of the function. * @param fn The function to execute that may throw. * @returns A `Result<T, Error>` containing either the successful result or the caught error. * @example * ```typescript * // Wrapping JSON.parse which can throw * const parseJson = <T>(text: string): Result<T, Error> => * Result.fromThrowable(() => JSON.parse(text) as T); * * const validJson = parseJson<{valid: boolean}>('{"valid": true}'); * if (Result.isOk(validJson)) { * console.log(validJson.value.valid); // true * } * * ``` */ Result.fromThrowable = (fn) => { try { return Result.ok(fn()); } catch (error) { if (error instanceof Error) { return Result.err(error); } const msg = unknownToString(error); return Result.err(new Error(msg)); } }; /** * Swaps the success and error values of a `Result`. * @template R The input `Result.Base` type. * @param result The `Result` to swap. * @returns A new `Result` with success and error swapped. * @example * ```typescript * const okResult = Result.ok(42); * const swapped = Result.swap(okResult); * console.log(Result.isErr(swapped)); // true * console.log(Result.unwrapErr(swapped)); // 42 * ``` */ Result.swap = (result) => // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion Result.isOk(result) ? Result.err(unwrapOk(result)) : Result.ok(result.value); /** * Converts a `Result` to an `Optional`. * * This conversion is useful when you want to discard error information and only * care about whether an operation succeeded. The error information is lost in * this conversion, so use it when error details are not needed. * * If the `Result` is `Ok`, returns `Some` with the value. * If the `Result` is `Err`, returns `None`. * * @template R The input `Result.Base` type. * @param result The `Result` to convert. * @returns An `Optional<UnwrapOk<R>>` containing the success value or representing `None`. * @example * ```typescript * // Basic conversion * const okResult = Result.ok(42); * const optional = Result.toOptional(okResult); * console.log(Optional.isSome(optional)); // true * console.log(Optional.unwrap(optional)); // 42 * * const errResult = Result.err("Network error"); * const none = Result.toOptional(errResult); * console.log(Optional.isNone(none)); // true * * ``` */ Result.toOptional = (result) => Result.isOk(result) ? Optional.some(unwrapOk(result)) : Optional.none; function orElse(...args) { switch (args.length) { case 2: { const [result, alternative] = args; return Result.isOk(result) ? result : alternative; } case 1: { // Curried version: one argument (alternative) provided const [alternative] = args; return (result) => orElse(result, alternative); } } } Result.orElse = orElse; /** * Combines two `Result` values into a single `Result` containing a tuple. * If either `Result` is `Err`, returns the first `Err` encountered. * @template S1 The success type of the first `Result`. * @template E1 The error type of the first `Result`. * @template S2 The success type of the second `Result`. * @template E2 The error type of the second `Result`. * @param resultA The first `Result`. * @param resultB The second `Result`. * @returns A `Result` containing a tuple of both values, or the first `Err`. * @example * ```typescript * const a = Result.ok(1); * const b = Result.ok("hello"); * const zipped = Result.zip(a, b); * console.log(Result.unwrapOk(zipped)); // [1, "hello"] * * const withErr = Result.zip(a, Result.err("error")); * console.log(Result.unwrapErr(withErr)); // "error" * ``` */ Result.zip = (resultA, resultB) => Result.isOk(resultA) ? Result.isOk(resultB) ? Result.ok([resultA.value, resultB.value]) : resultB : resultA; })(Result || (Result = {})); export { Result }; //# sourceMappingURL=result.mjs.map