UNPKG

wellcrafted

Version:

Delightful TypeScript patterns for elegant, type-safe applications

333 lines (332 loc) 13 kB
//#region src/result/result.ts /** * Constructs an `Ok<T>` variant, representing a successful outcome. * * This factory function creates the success variant of a `Result`. * It wraps the provided `data` (the success value) and ensures the `error` property is `null`. * * @template T - The type of the success value. * @param data - The success value to be wrapped in the `Ok` variant. * @returns An `Ok<T>` object with the provided data and `error` set to `null`. * @example * ```ts * const successfulResult = Ok("Operation completed successfully"); * // successfulResult is { data: "Operation completed successfully", error: null } * ``` */ const Ok = (data) => ({ data, error: null }); /** * Constructs an `Err<E>` variant, representing a failure outcome. * * This factory function creates the error variant of a `Result`. * It wraps the provided `error` (the error value) and ensures the `data` property is `null`. * * @template E - The type of the error value. * @param error - The error value to be wrapped in the `Err` variant. This value represents the specific error that occurred. * @returns An `Err<E>` object with the provided error and `data` set to `null`. * @example * ```ts * const failedResult = Err(new TypeError("Invalid input")); * // failedResult is { error: TypeError("Invalid input"), data: null } * ``` */ const Err = (error) => ({ error, data: null }); /** * Type guard to runtime check if an unknown value is a valid `Result<T, E>`. * * A value is considered a valid `Result` if: * 1. It is a non-null object. * 2. It has both `data` and `error` properties. * 3. Exactly one of `data` or `error` is `null`. The other must be non-`null`. * * This function does not validate the types of `data` or `error` beyond `null` checks. * * @template T - The expected type of the success value if the value is an `Ok` variant (defaults to `unknown`). * @template E - The expected type of the error value if the value is an `Err` variant (defaults to `unknown`). * @param value - The value to check. * @returns `true` if the value conforms to the `Result` structure, `false` otherwise. * If `true`, TypeScript's type system will narrow `value` to `Result<T, E>`. * @example * ```ts * declare const someValue: unknown; * * if (isResult<string, Error>(someValue)) { * // someValue is now typed as Result<string, Error> * if (isOk(someValue)) { * console.log(someValue.data); // string * } else { * console.error(someValue.error); // Error * } * } * ``` */ function isResult(value) { const isNonNullObject = typeof value === "object" && value !== null; if (!isNonNullObject) return false; const hasDataProperty = "data" in value; const hasErrorProperty = "error" in value; if (!hasDataProperty || !hasErrorProperty) return false; const isBothNull = value.data === null && value.error === null; if (isBothNull) return false; const isNeitherNull = value.data !== null && value.error !== null; if (isNeitherNull) return false; return true; } /** * Type guard to runtime check if a `Result<T, E>` is an `Ok<T>` variant. * * This function narrows the type of a `Result` to `Ok<T>` if it represents a successful outcome. * An `Ok<T>` variant is identified by its `error` property being `null`. * * @template T - The success value type. * @template E - The error value type. * @param result - The `Result<T, E>` to check. * @returns `true` if the `result` is an `Ok<T>` variant, `false` otherwise. * If `true`, TypeScript's type system will narrow `result` to `Ok<T>`. * @example * ```ts * declare const myResult: Result<number, string>; * * if (isOk(myResult)) { * // myResult is now typed as Ok<number> * console.log("Success value:", myResult.data); // myResult.data is number * } * ``` */ function isOk(result) { return result.error === null; } /** * Type guard to runtime check if a `Result<T, E>` is an `Err<E>` variant. * * This function narrows the type of a `Result` to `Err<E>` if it represents a failure outcome. * An `Err<E>` variant is identified by its `error` property being non-`null` (and thus `data` being `null`). * * @template T - The success value type. * @template E - The error value type. * @param result - The `Result<T, E>` to check. * @returns `true` if the `result` is an `Err<E>` variant, `false` otherwise. * If `true`, TypeScript's type system will narrow `result` to `Err<E>`. * @example * ```ts * declare const myResult: Result<number, string>; * * if (isErr(myResult)) { * // myResult is now typed as Err<string> * console.error("Error value:", myResult.error); // myResult.error is string * } * ``` */ function isErr(result) { return result.error !== null; } /** * Executes a synchronous operation and wraps its outcome (success or failure) in a `Result<T, E>`. * * This function attempts to execute the `operation`. * - If `operation` completes successfully, its return value is wrapped in an `Ok<T>` variant. * - If `operation` throws an exception, the caught exception (of type `unknown`) is passed to * the `mapErr` function. `mapErr` is responsible for transforming this `unknown` * exception into an `Err<E>` variant containing a well-typed error value of type `E`. * * @template T - The type of the success value returned by the `operation` if it succeeds. * @template E - The type of the error value produced by `mapErr` if the `operation` fails. * @param options - An object containing the operation and error mapping function. * @param options.try - The synchronous operation to execute. This function is expected to return a value of type `T`. * @param options.mapErr - A function that takes the `unknown` exception caught from `options.try` * and transforms it into an `Err<E>` variant containing a specific error value of type `E`. * @returns A `Result<T, E>`: `Ok<T>` if `options.try` succeeds, or `Err<E>` if it throws and `options.mapErr` provides an error variant. * @example * ```ts * function parseJson(jsonString: string): Result<object, SyntaxError> { * return trySync({ * try: () => JSON.parse(jsonString), * mapErr: (err: unknown) => { * if (err instanceof SyntaxError) return Err(err); * return Err(new SyntaxError("Unknown parsing error")); * } * }); * } * * const validResult = parseJson('{"name":"Result"}'); // Ok<{name: string}> * const invalidResult = parseJson('invalid json'); // Err<SyntaxError> * * if (isOk(validResult)) console.log(validResult.data); * if (isErr(invalidResult)) console.error(invalidResult.error.message); * ``` */ function trySync({ try: operation, mapErr }) { try { const data = operation(); return Ok(data); } catch (error) { return mapErr(error); } } /** * Executes an asynchronous operation (returning a `Promise`) and wraps its outcome in a `Promise<Result<T, E>>`. * * This function attempts to execute the asynchronous `operation`. * - If the `Promise` returned by `operation` resolves successfully, its resolved value is wrapped in an `Ok<T>` variant. * - If the `Promise` returned by `operation` rejects, or if `operation` itself throws an exception synchronously, * the caught exception/rejection reason (of type `unknown`) is passed to the `mapErr` function. * `mapErr` is responsible for transforming this `unknown` error into an `Err<E>` variant containing * a well-typed error value of type `E`. * * The entire outcome (`Ok<T>` or `Err<E>`) is wrapped in a `Promise`. * * @template T - The type of the success value the `Promise` from `operation` resolves to. * @template E - The type of the error value produced by `mapErr` if the `operation` fails or rejects. * @param options - An object containing the asynchronous operation and error mapping function. * @param options.try - The asynchronous operation to execute. This function must return a `Promise<T>`. * @param options.mapErr - A function that takes the `unknown` exception/rejection reason caught from `options.try` * and transforms it into an `Err<E>` variant containing a specific error value of type `E`. * This function must return `Err<E>` directly. * @returns A `Promise` that resolves to a `Result<T, E>`: `Ok<T>` if `options.try`'s `Promise` resolves, * or `Err<E>` if it rejects/throws and `options.mapErr` provides an error variant. * @example * ```ts * async function fetchData(url: string): Promise<Result<Response, Error>> { * return tryAsync({ * try: async () => fetch(url), * mapErr: (err: unknown) => { * if (err instanceof Error) return Err(err); * return Err(new Error("Network request failed")); * } * }); * } * * async function processData() { * const result = await fetchData("/api/data"); * if (isOk(result)) { * const response = result.data; * console.log("Data fetched:", await response.json()); * } else { * console.error("Fetch error:", result.error.message); * } * } * processData(); * ``` */ async function tryAsync({ try: operation, mapErr }) { try { const data = await operation(); return Ok(data); } catch (error) { return mapErr(error); } } /** * Resolves a value that may or may not be wrapped in a `Result`, returning the final value. * * This function handles the common pattern where a value might be a `Result<T, E>` or a plain `T`: * - If `value` is an `Ok<T>` variant, returns the contained success value. * - If `value` is an `Err<E>` variant, throws the contained error value. * - If `value` is not a `Result` (i.e., it's already a plain value of type `T`), * returns it as-is. * * This is useful when working with APIs that might return either direct values or Results, * allowing you to normalize them to the actual value or propagate errors via throwing. * * Use `resolve` when the input might or might not be a Result. * Use `unwrap` when you know the input is definitely a Result. * * @template T - The type of the success value (if `value` is `Ok<T>`) or the type of the plain value. * @template E - The type of the error value (if `value` is `Err<E>`). * @param value - The value to resolve. Can be a `Result<T, E>` or a plain value of type `T`. * @returns The final value of type `T` if `value` is `Ok<T>` or if `value` is already a plain `T`. * @throws The error value `E` if `value` is an `Err<E>` variant. * * @example * ```ts * // Example with an Ok variant * const okResult = Ok("success data"); * const resolved = resolve(okResult); // "success data" * * // Example with an Err variant * const errResult = Err(new Error("failure")); * try { * resolve(errResult); * } catch (e) { * console.error(e.message); // "failure" * } * * // Example with a plain value * const plainValue = "plain data"; * const resolved = resolve(plainValue); // "plain data" * * // Example with a function that might return Result or plain value * declare function mightReturnResult(): string | Result<string, Error>; * const outcome = mightReturnResult(); * try { * const finalValue = resolve(outcome); // handles both cases * console.log("Final value:", finalValue); * } catch (e) { * console.error("Operation failed:", e); * } * ``` */ /** * Unwraps a `Result<T, E>`, returning the success value or throwing the error. * * This function extracts the data from a `Result`: * - If the `Result` is an `Ok<T>` variant, returns the contained success value of type `T`. * - If the `Result` is an `Err<E>` variant, throws the contained error value of type `E`. * * Unlike `resolve`, this function expects the input to always be a `Result` type, * making it more direct for cases where you know you're working with a `Result`. * * @template T - The type of the success value contained in the `Ok<T>` variant. * @template E - The type of the error value contained in the `Err<E>` variant. * @param result - The `Result<T, E>` to unwrap. * @returns The success value of type `T` if the `Result` is `Ok<T>`. * @throws The error value of type `E` if the `Result` is `Err<E>`. * * @example * ```ts * // Example with an Ok variant * const okResult = Ok("success data"); * const value = unwrap(okResult); // "success data" * * // Example with an Err variant * const errResult = Err(new Error("something went wrong")); * try { * unwrap(errResult); * } catch (error) { * console.error(error.message); // "something went wrong" * } * * // Usage in a function that returns Result * function divide(a: number, b: number): Result<number, string> { * if (b === 0) return Err("Division by zero"); * return Ok(a / b); * } * * try { * const result = unwrap(divide(10, 2)); // 5 * console.log("Result:", result); * } catch (error) { * console.error("Division failed:", error); * } * ``` */ function unwrap(result) { if (isOk(result)) return result.data; throw result.error; } function resolve(value) { if (isResult(value)) { if (isOk(value)) return value.data; throw value.error; } return value; } //#endregion export { Err, Ok, isErr, isOk, isResult, resolve, tryAsync, trySync, unwrap }; //# sourceMappingURL=result-BJtW-Wuc.js.map