UNPKG

ts-rust-result

Version:

A lightweight, zero-dependency TypeScript library that brings Rust's Result type to your JavaScript/TypeScript projects

384 lines 12.4 kB
// Rust-style Result Toolkit for TypeScript by Pippa ✨🦀 /** * Creates a successful result with the given value. * This is generally used at the lower levels of the application * * @template T - The type of the value * @param value - The value to wrap in a successful result * @returns A Result object representing success with the given value * * @example * ```ts * const result = ok("hello world"); * if (result.ok) { * console.log(result.value); // "hello world" * } * ``` */ export function ok(value) { return { ok: true, value, _isr: true }; } /** * Type guard to check if a Result is successful. * * @template T - The type of the successful value * @param result - The Result to check * @returns True if the Result is successful (Ok), false otherwise * * @example * ```ts * const result = ok("hello world"); * if (isOk(result)) { * console.log(result.value); // TypeScript knows this is Ok<T> * } * * const errorResult = err(new Error("Failed")); * if (isOk(errorResult)) { * // This block won't execute * } else { * console.error(errorResult.error); // TypeScript knows this is Err * } * ``` */ export function isOk(result) { return result.ok; } /** * Type guard to check if a Result is an error. * * @template T - The type of the successful value * @param result - The Result to check * @returns True if the Result is an error (Err), false otherwise * * @example * ```ts * const result = ok("hello world"); * if (isErr(result)) { * // This block won't execute * } else { * console.log(result.value); // TypeScript knows this is Ok<T> * } * * const errorResult = err(new Error("Failed")); * if (isErr(errorResult)) { * console.error(errorResult.error); // TypeScript knows this is Err * } * ``` */ export function isErr(result) { return !result.ok; } /** * Creates an error result with the given error. * * @param error - The error object to wrap in an error result * @returns A Result object representing failure with the given error * * @example * ```ts * const result = err(new Error("Something went wrong")); * if (!result.ok) { * console.log(result.error.message); // "Something went wrong" * } * ``` */ export function err(error) { return { ok: false, error, _isr: true }; } /** * Unwraps a Result, returning the value if successful or throwing the error if failed. * * @template T - The type of the successful value * @param result - The Result to unwrap * @returns The value if the Result is successful * @throws {Error} If the Result is an error, throws the error * * @example * ```ts * const result = ok("hello world"); * const value = unwrap(result); // "hello world" * * const errorResult = err(new Error("Something went wrong")); * try { * const value = unwrap(errorResult); // Throws the error * } catch (error) { * console.error(error.message); // "Something went wrong" * } * * // ✅ Correct: Unwrap results from your own functions * async function fetchUser(id: string): Promise<Result<User>> { * try { * const response = await fetch(`/api/users/${id}`); * if (!response.ok) { * return err(new Error(`HTTP ${response.status}: ${response.statusText}`)); * } * return ok(await response.json()); * } catch (error) { * return err(error); * } * } * * const user = unwrap(await fetchUser("123")); // Direct access to User * ``` */ export function unwrap(result) { if (!result.ok) throw result.error; return result.value; } /** * Maps a successful Result value using the provided function. * If the Result is an error, returns the error unchanged. * * @template T - The type of the input value * @template U - The type of the output value * @param result - The Result to map * @param fn - Function to transform the successful value * @returns A new Result with the transformed value, or the original error * * @example * ```ts * const result = ok(5); * const doubled = map(result, x => x * 2); // ok(10) * * const errorResult = err(new Error("Failed")); * const mapped = map(errorResult, x => x * 2); // err(Error("Failed")) * * // ✅ Correct: Transform results from your own functions * async function fetchUser(id: string): Promise<Result<User>> { * try { * const response = await fetch(`/api/users/${id}`); * if (!response.ok) { * return err(new Error(`HTTP ${response.status}: ${response.statusText}`)); * } * return ok(await response.json()); * } catch (error) { * return err(error); * } * } * * const userResult = await fetchUser("123"); * const nameResult = map(userResult, user => user.name); // ok("John") or err(...) * ``` */ export function map(result, fn) { return result.ok ? ok(fn(result.value)) : result; } /** * Maps an error Result using the provided function. * If the Result is successful, returns the success unchanged. * * @template T - The type of the successful value * @param result - The Result to map the error of * @param fn - Function to transform the error * @returns A new Result with the transformed error, or the original success * * @example * ```ts * const errorResult = err(new Error("Network error")); * const enhancedError = mapErr(errorResult, err => * new Error(`API call failed: ${err.message}`) * ); // err(Error("API call failed: Network error")) * * const successResult = ok("data"); * const mapped = mapErr(successResult, err => new Error("Won't happen")); // ok("data") * * // ✅ Correct: Add context to errors from your own functions * async function fetchUser(id: string): Promise<Result<User>> { * try { * const response = await fetch(`/api/users/${id}`); * if (!response.ok) { * return err(new Error(`HTTP ${response.status}: ${response.statusText}`)); * } * return ok(await response.json()); * } catch (error) { * return err(error); * } * } * * const userResult = await fetchUser("123"); * const contextualError = mapErr(userResult, err => * new Error(`Failed to fetch user 123: ${err.message}`) * ); * ``` */ export function mapErr(result, fn) { return result.ok ? result : err(fn(result.error)); } /** * Wraps an async function in a try-catch block and returns a Result. * * @template T - The return type of the async function * @param fn - The async function to execute * @param shouldThrow - Whether to throw the error instead of returning a Result (defaults to false) * @returns A Promise that resolves to a Result containing either the function's return value or an error (only if shouldThrow is false) * @throws {Error} If shouldThrow is true and the function throws an error * * @example * ```ts * // Return Result (default behavior) - for wrapping third-party calls * const result = await tryResult(async () => { * const response = await fetch('/api/data'); * return response.json(); * }); * * if (result.ok) { * console.log(result.value); // The JSON data * } else { * console.error(result.error); // Any error that occurred * } * * // Throw error * try { * const data = await tryResult(async () => { * const response = await fetch('/api/data'); * return response.json(); * }, true); // Throws if the function throws * console.log(data); // Direct access to the value * } catch (error) { * console.error(error); // Handle thrown error * } * * // ✅ Correct: Your own functions should return Result directly * async function fetchUser(id: string): Promise<Result<User>> { * try { * const response = await fetch(`/api/users/${id}`); * if (!response.ok) { * return err(new Error(`HTTP ${response.status}: ${response.statusText}`)); * } * return ok(await response.json()); * } catch (error) { * return err(error); * } * } * * // ✅ Correct: Use tryResult to wrap third-party calls * const userResult = await tryResult(() => fetchUser("123")); * ``` */ export async function tryResult(fn, shouldThrow = false) { try { const value = await fn(); // If it's a Result, unwrap it and return the value if (value && value._isr) return ok(unwrap(value)); return ok(value); } catch (e) { const error = e instanceof Error ? e : new Error(String(e)); if (shouldThrow) { throw error; } return err(error); } } /** * Rust-style assertion that returns a Result instead of throwing. * * @param condition - The condition to assert * @param error - The error to return if the condition is false (defaults to "Assertion failed") * @param shouldThrow - Whether to throw the error instead of returning a Result (defaults to true) * @returns A Result that is ok(true) if condition is true, or err(error) if false (only if shouldThrow is false) * @throws {Error} If shouldThrow is true and condition is false * * @example * ```ts * // Return Result (default behavior) * const check = assert(typeof id === 'string', new Error("ID must be string")); * if (!check.ok) return check; // Early return on failure * * // Throw error * assert(typeof id === 'string', new Error("ID must be string"), true); // Throws if condition is false * * // Continue with valid id... * ``` */ export function assert(condition, error = new Error("Assertion failed"), shouldThrow = true) { if (!condition) { if (shouldThrow) { throw error; } return err(error); } return ok(true); } /** * Rust-style assertion with a typed error parameter. * * @template T - The type of the error (must extend Error) * @param condition - The condition to assert * @param error - The error object to return if the condition is false * @param shouldThrow - Whether to throw the error instead of returning a Result (defaults to true) * @returns A Result that is ok(true) if condition is true, or err(error) if false (only if shouldThrow is false) * @throws {T} If shouldThrow is true and condition is false * * @example * ```ts * // Return Result (default behavior) * const check = assertOr( * user.isAdmin, * new PermissionError("Admin access required") * ); * if (!check.ok) return check; * * // Throw error * assertOr( * user.isAdmin, * new PermissionError("Admin access required"), * true * ); // Throws PermissionError if user is not admin * ``` */ export function assertOr(condition, error, shouldThrow = true // assert by defaults throws into unit tests, making them cleaner ) { if (!condition) { if (shouldThrow) { throw error; } return err(error); } return ok(true); } /** * Asserts that a value is not null or undefined, returning the value if valid. * * @template T - The type of the value * @param value - The value to check for null/undefined * @param message - Custom error message (defaults to "Expected value to be non-null") * @param shouldThrow - Whether to throw the error instead of returning a Result (defaults to true) * @returns A Result containing the value if not null/undefined, or an error if it is (only if shouldThrow is false) * @throws {Error} If shouldThrow is true and value is null/undefined * * @example * ```ts * // Return Result (default behavior) * const nameCheck = assertNotNil(user.name); * if (!nameCheck.ok) return nameCheck; * const name = nameCheck.value; // TypeScript knows this is not null * * // With custom error message * const idCheck = assertNotNil(user.id, "User ID is required"); * if (!idCheck.ok) return idCheck; * * // Throw error * const name = assertNotNil(user.name, "Name is required", true); // Throws if name is null/undefined * ``` */ export function assertNotNil(value, message = "Expected value to be non-null", shouldThrow = true // assert by defaults throws into unit tests, making them cleaner ) { if (value === null || value === undefined) { const error = new Error(message); if (shouldThrow) { throw error; } return err(error); } return ok(value); } // Example usage: // const check = assert(typeof id === 'string', new Error("ID must be string")); // if (!check.ok) return check; // const nameCheck = assertNotNil(user.name); // if (!nameCheck.ok) return nameCheck; // const name = nameCheck.value; //# sourceMappingURL=TsRustResult.js.map