Last active
August 1, 2025 05:02
-
-
Save thomastheyoung/e88627ac6e7f94c560baea726cee98e5 to your computer and use it in GitHub Desktop.
Rust-inspire Result for Typescript: A tri-state Result type that represents operations that can succeed with a value, fail with an error, or return empty.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Result Type System | |
* | |
* A tri-state Result type that represents operations that can succeed with a value, | |
* fail with an error, or return empty. This pattern replaces traditional try/catch | |
* with explicit error handling and null safety. | |
* | |
* @template T - The type of successful values | |
* @template E - The type of error values | |
* | |
* **Why use Result?** | |
* - Eliminates null/undefined bugs by making empty states explicit | |
* - Forces error handling at compile time through type checking | |
* - Provides a consistent API for operations that may fail or return nothing | |
* - Enables functional programming patterns with map/match operations | |
* | |
* **Use cases:** | |
* - API calls that may fail or return no data | |
* - Database queries that may not find records | |
* - Parsing operations that may encounter invalid input | |
* - File operations that may fail or find no files | |
*/ | |
export type Result<T, E = Error> = | |
| { readonly type: 'some'; readonly value: T } | |
| { readonly type: 'none' } | |
| { readonly type: 'err'; readonly error: E }; | |
/** Async version of Result for operations that return promises */ | |
export type AsyncResult<T, E = Error> = Promise<Result<T, E>>; | |
/** | |
* Constructors - Create Result instances | |
* | |
* These functions create the three possible states of a Result. | |
* Use these instead of manually constructing Result objects. | |
*/ | |
/** Creates a successful Result containing a value */ | |
export const some = <T, E = never>(value: T): Result<T, E> => ({ type: 'some', value }); | |
/** Creates an empty Result (no value, no error) */ | |
export const none = <T, E = never>(): Result<T, E> => ({ type: 'none' }); | |
/** Creates a failed Result containing an error */ | |
export const err = <T = never, E = unknown>(error: E): Result<T, E> => ({ type: 'err', error }); | |
/** Creates a successful Result without a value (for binary success/failure) */ | |
export const ok = <E = never>(): Result<true, E> => ({ type: 'some', value: true }); | |
/** | |
* Type Guards - Safely check and narrow Result types | |
* | |
* These functions determine which variant of Result you have and provide | |
* type-safe access to the contained value or error. | |
*/ | |
/** Checks if Result contains a successful value */ | |
export function isSome<T, E>(res: Result<T, E>): res is { type: 'some'; value: T } { | |
return res.type === 'some'; | |
} | |
/** Checks if Result is empty (no value, no error) */ | |
export function isNone<T, E>(res: Result<T, E>): res is { type: 'none' } { | |
return res.type === 'none'; | |
} | |
/** Checks if Result contains an error */ | |
export function isErr<T, E>(res: Result<T, E>): res is { type: 'err'; error: E } { | |
return res.type === 'err'; | |
} | |
/** | |
* Transformation Functions - Transform Results without unwrapping | |
* | |
* These functions let you work with the contained values while preserving | |
* the Result wrapper and error handling semantics. | |
*/ | |
/** Transforms the success value while preserving error and empty states */ | |
export function map<T, U, E>(res: Result<T, E>, fn: (t: T) => U): Result<U, E> { | |
if (isSome(res)) { | |
return some(fn(res.value)); | |
} | |
if (isErr(res)) { | |
return res as Result<U, E>; | |
} | |
return none(); | |
} | |
/** Transforms the error value while preserving success and empty states */ | |
export function mapErr<T, E, F>(res: Result<T, E>, fn: (e: E) => F): Result<T, F> { | |
if (isErr(res)) { | |
return err(fn(res.error)); | |
} | |
return res as Result<T, F>; | |
} | |
/** | |
* Extraction Functions - Get values out of Results safely | |
* | |
* These functions provide safe ways to extract values from Results | |
* with appropriate fallbacks for error and empty cases. | |
*/ | |
/** Extracts the value if successful, otherwise returns the fallback */ | |
export function unwrapOr<T, E>(res: Result<T, E>, fallback: T): T { | |
return isSome(res) ? res.value : fallback; | |
} | |
/** | |
* Pattern Matching - Handle all Result cases explicitly | |
* | |
* These functions provide exhaustive handling of all possible Result states. | |
* The compiler ensures you handle every case, preventing runtime errors. | |
*/ | |
/** Handles all three Result cases with provided functions */ | |
export function match<T, E, U>( | |
res: Result<T, E>, | |
handlers: { | |
some: (t: T) => U; | |
none: () => U; | |
err: (e: E) => U; | |
}, | |
): U { | |
if (isSome(res)) return handlers.some(res.value); | |
if (isErr(res)) return handlers.err(res.error); | |
return handlers.none(); | |
} | |
/** Handles error and empty cases, returns value directly if successful */ | |
export function matchOr<T, E, U>( | |
res: Result<T, E>, | |
handlers: { | |
none: () => U; | |
err: (e: E) => U; | |
}, | |
): T | U { | |
if (isSome(res)) return res.value; | |
if (isErr(res)) return handlers.err(res.error); | |
return handlers.none(); | |
} | |
/** | |
* Assertion Functions - Force unwrapping with runtime checks | |
* | |
* Use sparingly when you're certain a Result contains a value. | |
* Throws errors for empty or error states. | |
*/ | |
/** Asserts that Result contains a value, throws if empty or error */ | |
export function assertSome<T, E>( | |
res: Result<T, E>, | |
message?: string, | |
): asserts res is { type: 'some'; value: T } { | |
if (isNone(res)) { | |
throw new Error(message || 'Empty result'); | |
} else if (isErr(res)) { | |
throw new Error(message || 'Error while trying to fetch result'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment