UNPKG

typescript-monads

Version:
1,150 lines (1,122 loc) 41.5 kB
import { IMaybe } from '../maybe/maybe.interface' export type Predicate = () => boolean export interface IResultMatchPattern<T, E, U> { readonly ok: (val: T) => U readonly fail: (val: E) => U } export interface IResult<T, E> { /** * Type guard that determines if this Result is an Ok variant. * * This method acts as a TypeScript type guard, narrowing the type of the Result * when used in conditional blocks. * * @returns true if this Result is an Ok variant, false otherwise * * @example * const result = getUser(userId); * * if (result.isOk()) { * // TypeScript knows that result is an Ok variant here * const user = result.unwrap(); // Safe to call * console.log(user.name); * } else { * // TypeScript knows that result is a Fail variant here * const error = result.unwrapFail(); // Safe to call * console.error(error.message); * } */ isOk(): boolean /** * Type guard that determines if this Result is a Fail variant. * * This method acts as a TypeScript type guard, narrowing the type of the Result * when used in conditional blocks. * * @returns true if this Result is a Fail variant, false otherwise * * @example * const result = authenticate(credentials); * * if (result.isFail()) { * // TypeScript knows that result is a Fail variant here * const error = result.unwrapFail(); // Safe to call * handleAuthError(error); * } else { * // TypeScript knows that result is an Ok variant here * startSession(result.unwrap()); // Safe to call * } */ isFail(): boolean /** * Converts this Result's Ok value to a Maybe. * * This method transforms a Result into a Maybe, focusing on the success path: * - If this Result is an Ok variant, returns a Some Maybe containing the value * - If this Result is a Fail variant, returns a None Maybe * * This is useful when you want to continue with Maybe operations and no longer * need to track the specific error type. * * @returns A Maybe containing the Ok value if present, or None * * @example * // Converting from Result to Maybe * const userResult: Result<User, ApiError> = fetchUser(userId); * * // Focus only on the success case by converting to Maybe * const userMaybe: Maybe<User> = userResult.maybeOk(); * * // Continue with Maybe operations * const userName = userMaybe * .map(user => user.name) * .valueOr("Unknown User"); */ maybeOk(): IMaybe<NonNullable<T>> /** * Converts this Result's Fail value to a Maybe. * * This method transforms a Result into a Maybe, focusing on the failure path: * - If this Result is a Fail variant, returns a Some Maybe containing the error * - If this Result is an Ok variant, returns a None Maybe * * This is useful when you want to specifically work with errors when they occur, * but don't care about the success value. * * @returns A Maybe containing the Fail value if present, or None * * @example * // Collecting errors across multiple operations * const results: Array<Result<any, Error>> = runValidations(); * * const errors = results * .map(result => result.maybeFail()) * .filter(maybeErr => maybeErr.isSome()) * .map(maybeErr => maybeErr.valueOrThrow()); * * if (errors.length > 0) { * displayErrors(errors); * } */ maybeFail(): IMaybe<E> /** * Extracts the Ok value from this Result. * * This method should only be called when you're certain that the Result is an Ok variant. * If the Result is a Fail variant, this method will throw an exception. * * @returns The contained Ok value * @throws Error if the Result is a Fail variant * * @example * // Safe usage with type guard * const result = parseJson<Config>(jsonString); * * if (result.isOk()) { * const config = result.unwrap(); * initializeApp(config); * } * * // Alternative safe usage with pattern matching * result.match({ * ok: config => initializeApp(config), * fail: error => showError(error) * }); * * // Unsafe usage (might throw) * const config = result.unwrap(); // Throws if result is Fail */ /** * Extracts the Ok value from this Result. * * This method should only be called when you're certain that the Result is an Ok variant. * If the Result is a Fail variant, this method will throw an exception. * * @returns The contained Ok value * @throws Error if the Result is a Fail variant * * @example * // Safe usage with type guard * const result = parseJson<Config>(jsonString); * * if (result.isOk()) { * const config = result.unwrap(); * initializeApp(config); * } * * // Alternative safe usage with pattern matching * result.match({ * ok: config => initializeApp(config), * fail: error => showError(error) * }); * * // Unsafe usage (might throw) * const config = result.unwrap(); // Throws if result is Fail */ unwrap(): T | never /** * Extracts the Ok value from this Result or returns a default value. * * This method provides a safe way to unwrap a Result without risking exceptions. * If the Result is an Ok variant, the contained value is returned. * If the Result is a Fail variant, the provided default value is returned. * * @param opt The default value to return if this Result is a Fail variant * @returns The contained Ok value or the provided default * * @example * // Using unwrapOr for default values * const userResult = getUserById(userId); * * // If user not found, use a guest user * const user = userResult.unwrapOr({ * id: 0, * name: "Guest", * role: "visitor" * }); * * // Using unwrapOr in a chain * const userName = getUserById(userId) * .map(user => user.name) * .unwrapOr("Unknown User"); */ unwrapOr(opt: T): T /** * Extracts the Fail value from this Result. * * This method should only be called when you're certain that the Result is a Fail variant. * If the Result is an Ok variant, this method will throw an exception. * * @returns The contained Fail value * @throws ReferenceError if the Result is an Ok variant * * @example * // Safe usage with type guard * const result = validateInput(formData); * * if (result.isFail()) { * const validationErrors = result.unwrapFail(); * displayErrors(validationErrors); * } * * // Alternative safe usage with pattern matching * result.match({ * ok: data => submitForm(data), * fail: errors => displayErrors(errors) * }); */ unwrapFail(): E | never /** * Applies a pattern matching object to this Result. * * This method provides a functional way to handle both Ok and Fail variants * in a single expression, enforcing exhaustive handling of all cases. * * @typeParam M - The return type of the pattern matching functions * @param fn - An object containing functions to handle each variant: * - `ok`: Function that processes the Ok value * - `fail`: Function that processes the Fail value * @returns The result of applying the matching function to the contained value * * @example * // Basic pattern matching with different return types * const result = fetchData(); * * const message = result.match({ * ok: data => `Successfully loaded ${data.items.length} items`, * fail: error => `Error: ${error.message}` * }); * * // Pattern matching for control flow * result.match({ * ok: data => { * renderData(data); * updateLastFetchTime(); * }, * fail: error => { * logError(error); * showRetryButton(); * } * }); * * // Pattern matching with transformations * const apiResponse = makeApiCall() * .match({ * ok: successResponse => ({ * status: 'success', * data: successResponse * }), * fail: errorResponse => ({ * status: 'error', * message: errorResponse.message, * code: errorResponse.code * }) * }); */ match<M>(fn: IResultMatchPattern<T, E, M>): M /** * Maps the Ok value of this Result using the provided function. * * If this Result is an Ok variant, this method transforms the contained value using * the provided function and returns a new Ok Result with the transformed value. * If this Result is a Fail variant, it returns a new Fail Result with the same error. * * This operation is similar to Array.map but for a Result's Ok value. * * @typeParam M - The type of the mapped value * @param fn - A function that transforms the Ok value * @returns A new Result with the transformed value if Ok, or the original error if Fail * * @example * // Transforming user data * const userResult = fetchUser(userId); * * const userProfileResult = userResult.map(user => ({ * name: user.name, * email: user.email, * avatar: user.avatarUrl || 'default-avatar.png' * })); * * // Chaining multiple transformations * const userNameResult = fetchUser(userId) * .map(user => user.profile) * .map(profile => profile.name) * .map(name => name.toUpperCase()); * * // Error case is automatically propagated * const result = validate("invalid-input") // Returns Fail * .map(value => process(value)) // Map is not applied * .map(result => format(result)); // Map is not applied * * // result is still the original Fail value */ map<M>(fn: (val: T) => M): IResult<M, E> /** * Maps the Fail value of this Result using the provided function. * * If this Result is a Fail variant, this method transforms the error using * the provided function and returns a new Fail Result with the transformed error. * If this Result is an Ok variant, it returns a new Ok Result with the same value. * * This is useful for transforming errors while preserving their failure state. * * @typeParam M - The type of the mapped error * @param fn - A function that transforms the Fail value * @returns A new Result with the transformed error if Fail, or the original value if Ok * * @example * // Enriching error information * const result = fetchData() * .mapFail(error => ({ * ...error, * timestamp: new Date(), * context: 'fetchData' * })); * * // Converting between error types * const standardizedResult = externalApiCall() * .mapFail(apiError => new AppError({ * code: mapErrorCode(apiError.code), * message: apiError.message, * source: 'ExternalAPI' * })); * * // Localizing error messages * const localizedResult = validateInput(form) * .mapFail(errors => errors.map(err => ({ * ...err, * message: translateErrorMessage(err.message, currentLocale) * }))); */ mapFail<M>(fn: (err: E) => M): IResult<T, M> /** * Chains a function that returns another Result. * * If this Result is an Ok variant, this method applies the function to the contained value, * which returns a new Result. This allows for sequencing operations that might fail. * If this Result is a Fail variant, it returns a new Fail Result with the same error without * calling the function. * * This operation is similar to flatMap/bind in other functional programming contexts. * * @typeParam M - The type of the value in the returned Result * @param fn - A function that takes the Ok value and returns a new Result * @returns The Result returned by fn if this Result is Ok, or a Fail Result with the original error * * @example * // Sequential operations that might fail * const result = parseConfigFile(filePath) * .flatMap(config => validateConfig(config)) * .flatMap(validConfig => initializeSystem(validConfig)); * * // Database transaction example * const transactionResult = connectToDatabase() * .flatMap(connection => beginTransaction(connection)) * .flatMap(transaction => executeQueries(transaction)) * .flatMap(transaction => commitTransaction(transaction)); * * // Early return on failure * const userResult = authenticateUser(credentials) // Might return Fail * .flatMap(user => authorizeUser(user, resource)) // Only called if authentication succeeds * .flatMap(user => loadUserProfile(user)); // Only called if authorization succeeds */ flatMap<M>(fn: (val: T) => IResult<M, E>): IResult<M, E> /** * Maps the success value of this Result to a Maybe, and then flattens the resulting structure. * * This method is particularly useful when working with optional properties or values that might be undefined/null. * It allows for seamless chaining of Result and Maybe monads without explicit unwrapping and re-wrapping. * * @typeParam M - The type of the value contained in the returned Result if successful * @param fn - A function that takes the success value of this Result and returns a Maybe * @param err - The error value to use if the Maybe is None * @returns * - If this Result is a Fail: A Fail Result containing the original error * - If this Result is an Ok and fn returns Some: An Ok Result containing the unwrapped value * - If this Result is an Ok but fn returns None: A Fail Result containing the provided err * * @example * // Type definitions * interface User { * id: number; * profile?: { * name: string; * email: string; * }; * } * * // Success path with Some * const getUser = (): Result<User, Error> => * ok({ id: 1, profile: { name: "Alice", email: "alice@example.com" } }); * * // Chain to access a potentially undefined property safely * const getName = getUser() * .flatMapMaybe( * user => maybe(user.profile), * new Error("Profile not found") * ) * .map(profile => profile.name); * * // getName is Result<string, Error> containing "Alice" * * // Handling None case * const getUserWithoutProfile = (): Result<User, Error> => * ok({ id: 2 }); // No profile property * * const getName2 = getUserWithoutProfile() * .flatMapMaybe( * user => maybe(user.profile), * new Error("Profile not found") * ) * .map(profile => profile.name); * * // getName2 is Result<string, Error> containing Error("Profile not found") * * // Using with nullish values * type Response = { data?: { value: number } | null }; * * const response: Response = { data: null }; * const value = ok<Response, string>(response) * .flatMapMaybe( * res => maybe(res.data), * "No data available" * ) * .flatMapMaybe( * data => maybe(data.value), * "Value not present" * ); * * // value is Result<number, string> containing "No data available" * * // Working with arrays and optional chaining * interface Post { comments?: Array<{ id: number, text: string }> } * * const getFirstComment = (post: Post): Result<string, string> => * ok(post) * .flatMapMaybe( * p => maybe(p.comments), * "No comments found" * ) * .flatMapMaybe( * comments => maybe(comments[0]), * "Comment list is empty" * ) * .map(comment => comment.text); */ flatMapMaybe<M>(fn: (val: T) => IMaybe<M>, err: E): IResult<M, E> /** * Executes side-effect functions based on the Result variant without changing the Result. * * This method allows you to perform actions that don't affect the Result's value: * - If this Result is an Ok variant, it calls the provided `ok` function with the contained value * - If this Result is a Fail variant, it calls the provided `fail` function with the error * * Both functions are optional; if not provided, nothing happens for that variant. * * @param val - An object containing optional functions for Ok and Fail variants * * @example * // Logging based on Result variant * fetchData().tap({ * ok: data => console.log("Data fetched successfully:", data), * fail: error => console.error("Failed to fetch data:", error) * }); * * // Metrics and analytics * processPayment(paymentInfo).tap({ * ok: result => { * analytics.trackEvent("payment_success", { * amount: result.amount, * method: result.method * }); * }, * fail: error => { * analytics.trackEvent("payment_failure", { * error: error.code, * message: error.message * }); * } * }); * * // Partial application (only handling one variant) * validateInput(formData).tap({ * fail: errors => highlightFormErrors(errors) * }); */ tap(val: Partial<IResultMatchPattern<T, E, void>>): void /** * Executes a side-effect function when this Result is a Fail variant. * * This method is a specialized version of `tap` that only handles the Fail case: * - If this Result is a Fail variant, it calls the provided function with the error * - If this Result is an Ok variant, it does nothing * * @param f - A function to execute with the Fail value * * @example * // Error logging * processRequest(req).tapFail(error => { * logger.error("Request processing failed", { * error: error.message, * stack: error.stack, * requestId: req.id * }); * }); * * // UI error handling * submitForm(formData).tapFail(errors => { * displayErrors(errors); * highlightInvalidFields(errors); * scrollToFirstError(); * }); * * // Monitoring and alerting * criticalOperation().tapFail(error => { * if (error.severity === 'high') { * alertOps(error); * incrementFailureCounter(); * } * }); */ tapFail(f: (val: E) => void): void /** * Executes a side-effect function when this Result is an Ok variant. * * This method is a specialized version of `tap` that only handles the Ok case: * - If this Result is an Ok variant, it calls the provided function with the contained value * - If this Result is a Fail variant, it does nothing * * @param f - A function to execute with the Ok value * * @example * // Logging successful operations * saveUser(userData).tapOk(user => { * console.log(`User ${user.id} saved successfully`); * updateLastSavedTimestamp(); * }); * * // UI updates on success * fetchData().tapOk(data => { * updateUI(data); * hideLoadingIndicator(); * }); * * // Analytics for successful operations * checkout(cart).tapOk(order => { * analytics.trackPurchase({ * orderId: order.id, * amount: order.total, * items: order.items.length * }); * }); */ tapOk(f: (val: T) => void): void /** * Converts an Ok Result into a Fail Result using a transformation function. * * This method inverts the Result's state by: * - If this Result is an Ok variant, it applies the function to the contained value * to generate an error and returns a Fail Result with that error * - If this Result is a Fail variant, it returns the original Fail Result unchanged * * This is useful for scenarios where success conditions need to be converted to failures * based on the content of the success value. * * @param fn - A function that transforms the Ok value into a Fail value * @returns A Fail Result with the generated error, or the original Fail Result * * @example * // Implementing validation logic * const userResult = getUserById(userId); * * const validatedUser = userResult.toFailWhenOk(user => { * if (!user.isActive) return new Error("User account is inactive"); * if (user.accessLevel < requiredLevel) return new Error("Insufficient access level"); * return null; // This case won't happen due to TypeScript's return type checking * }); * * // Implementing business rule validation * const orderResult = createOrder(orderData); * * const validatedOrder = orderResult.toFailWhenOk(order => { * if (order.items.length === 0) return new ValidationError("Order must contain at least one item"); * if (order.total < minimumOrderAmount) return new ValidationError(`Order total must be at least ${minimumOrderAmount}`); * return new ValidationError(""); // TypeScript requires a return, but this won't be reached in valid code * }); */ toFailWhenOk(fn: (val: T) => E): IResult<T, E> /** * Converts an Ok Result into a Fail Result using a provided error value. * * This method inverts the Result's state by: * - If this Result is an Ok variant, it returns a Fail Result with the provided error * - If this Result is a Fail variant, it returns a Fail Result with the provided error, * replacing the original error * * This is useful for scenarios where you want to override or standardize error values. * * @param val - The error value to use in the returned Fail Result * @returns A Fail Result containing the provided error * * @example * // Standardizing error messages * const result = parseInput(rawInput) * .toFailWhenOkFrom(new Error("Input validation failed")); * * // Conditional error replacement * let result = processData(data); * * if (shouldUseStandardError) { * result = result.toFailWhenOkFrom(standardError); * } * * // Overriding authentication errors with a generic message * const authResult = authenticate(credentials) * .toFailWhenOkFrom(new Error("Authentication failed")); */ toFailWhenOkFrom(val: E): IResult<T, E> /** * Executes side-effect functions and returns the original Result for chaining. * * This method is similar to `tap`, but returns the Result itself to allow for * further method chaining: * - If this Result is an Ok variant, it calls the provided `ok` function with the contained value * - If this Result is a Fail variant, it calls the provided `fail` function with the error * * Both functions are optional; if not provided, nothing happens for that variant. * * @param val - An object containing optional functions for Ok and Fail variants * @returns This Result, unchanged * * @example * // Chaining operations with logging * return fetchUser(userId) * .tapThru({ * ok: user => console.log(`User ${user.id} fetched`), * fail: err => console.error(`Failed to fetch user: ${err.message}`) * }) * .map(user => transformUser(user)) * .tapThru({ * ok: profile => console.log(`User profile created`) * }); * * // Progressive UI updates in a chain * processForm(data) * .tapThru({ * ok: () => updateProgressBar(0.33) * }) * .flatMap(validated => saveToDatabase(validated)) * .tapThru({ * ok: () => updateProgressBar(0.66), * fail: err => showErrorNotification(err) * }) * .flatMap(saved => notifyUser(saved)) * .tapThru({ * ok: () => updateProgressBar(1.0), * fail: err => showErrorNotification(err) * }); */ tapThru(val: Partial<IResultMatchPattern<T, E, void>>): IResult<T, E> /** * Executes a side-effect function when this Result is an Ok variant and returns the original * Result for chaining. * * This method is a specialized version of `tapThru` that only handles the Ok case: * - If this Result is an Ok variant, it calls the provided function with the contained value * - If this Result is a Fail variant, it does nothing * * In both cases, it returns the original Result unchanged. * * @param fn - A function to execute with the Ok value * @returns This Result, unchanged * * @example * // Chaining with logging for successful operations * return getUserById(userId) * .tapOkThru(user => console.log(`Found user: ${user.name}`)) * .map(user => user.profile) * .tapOkThru(profile => console.log(`Profile accessed`)) * .flatMap(profile => getProfileSettings(profile.id)); * * // Progressive UI updates on success * submitOrder(order) * .tapOkThru(() => { * showMessage("Order submitted"); * updateProgressStep(1); * }) * .flatMap(order => processPayment(order)) * .tapOkThru(() => { * showMessage("Payment processed"); * updateProgressStep(2); * }) * .flatMap(order => finalizeOrder(order)); */ tapOkThru(fn: (val: T) => void): IResult<T, E> /** * Executes a side-effect function when this Result is a Fail variant and returns the original * Result for chaining. * * This method is a specialized version of `tapThru` that only handles the Fail case: * - If this Result is a Fail variant, it calls the provided function with the error * - If this Result is an Ok variant, it does nothing * * In both cases, it returns the original Result unchanged. * * @param fn - A function to execute with the Fail value * @returns This Result, unchanged * * @example * // Chaining with error logging * return validateInput(input) * .tapFailThru(errors => logValidationErrors(errors)) * .flatMap(input => processInput(input)) * .tapFailThru(error => logProcessingError(error)) * .flatMap(result => saveResult(result)); * * // Progressive error handling * authenticateUser(credentials) * .tapFailThru(error => { * logAuthFailure(error); * updateLoginAttempts(); * }) * .flatMap(user => authorizeUser(user, resource)) * .tapFailThru(error => { * logAuthorizationFailure(error); * recordAccessAttempt(resource); * }); * * // Analytics tracking in a chain * checkout(cart) * .tapFailThru(error => { * analytics.trackEvent("checkout_failure", { * error: error.code, * step: "initial_validation" * }); * }) * .flatMap(validCart => processPayment(validCart)) * .tapFailThru(error => { * analytics.trackEvent("checkout_failure", { * error: error.code, * step: "payment_processing" * }); * }); */ tapFailThru(fn: (val: E) => void): IResult<T, E> /** * Transforms a Fail Result into an Ok Result using a recovery function. * * This method provides error handling by: * - If this Result is a Fail variant, it applies the function to the error to generate a recovery value * and returns a new Ok Result with that value * - If this Result is an Ok variant, it returns the original Result unchanged * * This is similar to a catch block in try/catch, but in a functional style. * * @param fn - A function that transforms the Fail value into a recovery value * @returns An Ok Result with either the original value or the recovered value * * @example * // Providing default values * const userResult = getUserById(userId) * .recover(error => ({ * id: 0, * name: "Guest User", * isGuest: true * })); * * // userResult is guaranteed to be Ok * const user = userResult.unwrap(); // Safe, will never throw * * // Error logging with recovery * fetchData() * .tapFail(error => logError(error)) * .recover(error => { * const fallbackData = getLocalData(); * trackRecovery("data_fetch", error); * return fallbackData; * }) * .map(data => processData(data)); // This will always run with either fetched or fallback data */ recover(fn: (err: E) => T): IResult<T, E> /** * Transforms a Fail Result by applying a function that returns another Result. * * This method provides advanced error recovery by: * - If this Result is a Fail variant, it applies the function to the error, which returns a new Result * - If this Result is an Ok variant, it returns the original Result unchanged * * This is useful for fallback operations that might themselves fail. * * @param fn - A function that takes the Fail value and returns a new Result * @returns The original Result if Ok, or the Result returned by the function if Fail * * @example * // Trying a fallback operation that might also fail * fetchFromPrimaryAPI() * .recoverWith(error => { * logFailure(error, "primary_api"); * // Try the backup API, which might also fail * return fetchFromBackupAPI(); * }) * .match({ * ok: data => renderData(data), * fail: error => showFatalError("All data sources failed") * }); * * // Authentication with multiple strategies * authenticateWithPassword(credentials) * .recoverWith(error => { * if (error.code === "CREDENTIALS_EXPIRED") { * return authenticateWithToken(refreshToken); * } * return fail(error); // Pass through other errors * }) * .recoverWith(error => { * if (error.code === "TOKEN_EXPIRED") { * return authenticateWithOAuth(); * } * return fail(error); // Pass through other errors * }); */ recoverWith(fn: (err: E) => IResult<T, E>): IResult<T, E> /** * Returns this Result if it's Ok, otherwise returns the provided fallback Result. * * This method allows for specifying an alternative Result: * - If this Result is an Ok variant, it is returned unchanged * - If this Result is a Fail variant, the fallback Result is returned * * @param fallback - The Result to return if this Result is Fail * @returns This Result if Ok, or the fallback Result if Fail * * @example * // Try multiple data sources in order * const userData = getUserFromCache(userId) * .orElse(getUserFromDatabase(userId)) * .orElse(getUserFromBackupService(userId)); * * // Providing a default value as a Result * const config = loadConfig() * .orElse(ok(DEFAULT_CONFIG)); * * // config is guaranteed to be Ok * const configValue = config.unwrap(); // Safe, will never throw */ orElse(fallback: IResult<T, E>): IResult<T, E> /** * Swaps the Ok and Fail values, creating a new Result with inversed variants. * * This method transforms: * - An Ok Result into a Fail Result with the same value (now as an error) * - A Fail Result into an Ok Result with the same error (now as a value) * * This can be useful for inverting logic or for protocols where the error case * is actually the expected or desired outcome. * * @returns A new Result with the Ok and Fail variants swapped * * @example * // Inverting validation logic * const isInvalid = validateInput(input) // Returns Ok if valid, Fail if invalid * .swap() // Returns Fail if valid, Ok if invalid * .isOk(); // true if the input was invalid * * // Working with negative conditions * const userNotFound = findUser(userId) * .swap() * .isOk(); // true if the user was not found * * // Converting between error domains * checkPermission(user, resource) // Returns Ok(true) if permitted, Fail(error) if not * .swap() // Returns Fail(true) if permitted, Ok(error) if not * .map(error => ({ // Only runs for permission errors * type: 'ACCESS_DENIED', * message: `Access denied: ${error.message}`, * resource * })) * .swap(); // Back to Ok for permitted, Fail for denied */ swap(): IResult<E, T> /** * Combines this Result with another Result using a combining function. * * This method allows for working with two independent Results together: * - If both Results are Ok, applies the function to both values and returns an Ok Result * - If either Result is Fail, returns the first Fail Result encountered * * This is useful for combining data that comes from different sources where both * are needed to proceed. * * @param other - Another Result to combine with this one * @param fn - A function that combines the two Ok values * @returns A new Result containing either the combined values or the first error * * @example * // Combining user data and preferences that are loaded separately * const userData = fetchUserData(userId); * const userPrefs = fetchUserPreferences(userId); * * const userProfile = userData.zipWith( * userPrefs, * (data, prefs) => ({ * ...data, * preferences: prefs, * theme: prefs.theme || 'default' * }) * ); * * // Working with multiple API responses * const orders = fetchOrders(userId); * const payments = fetchPayments(userId); * * const combinedData = orders.zipWith( * payments, * (orderList, paymentList) => { * return orderList.map(order => ({ * ...order, * payments: paymentList.filter(p => p.orderId === order.id) * })); * } * ); */ zipWith<U, R>(other: IResult<U, E>, fn: (a: T, b: U) => R): IResult<R, E> /** * Maps the Ok value of this Result to a Promise, and then flattens the resulting structure. * * This method allows for seamless integration with asynchronous code: * - If this Result is an Ok variant, it applies the function to the contained value, * awaits the Promise, and wraps the resolved value in a new Ok Result * - If the Promise rejects, it returns a Fail Result with the rejection reason * - If this Result is a Fail variant, it returns a Promise that resolves to the * original Fail Result without calling the function * * @param fn - A function that takes the Ok value and returns a Promise * @returns A Promise that resolves to a new Result * * @example * // Chaining synchronous and asynchronous operations * validateUser(userData) * .flatMapPromise(user => saveUserToDatabase(user)) * .then(result => result.match({ * ok: savedUser => sendWelcomeEmail(savedUser), * fail: error => logError("Failed to save user", error) * })); * * // Multi-step asynchronous workflow * async function processOrder(orderData) { * // Start with sync validation returning a Result * const result = await validateOrder(orderData) * .flatMapPromise(async order => { * // Async payment processing * const paymentResult = await processPayment(order.paymentDetails); * if (!paymentResult.success) { * throw new Error(`Payment failed: ${paymentResult.message}`); * } * * // Async inventory check and allocation * await allocateInventory(order.items); * * // Return the processed order * return { * ...order, * status: 'PAID', * paymentId: paymentResult.id * }; * }) * .flatMapPromise(async paidOrder => { * // Final database save * const orderId = await saveOrderToDatabase(paidOrder); * return { ...paidOrder, id: orderId }; * }); * * return result; * } */ flatMapPromise<M>(fn: (val: T) => Promise<M>): Promise<IResult<M, E>> /** * Maps the Ok value of this Result to an Observable, and then flattens the resulting structure. * * This method allows for seamless integration with reactive code: * - If this Result is an Ok variant, it applies the function to the contained value, * subscribes to the Observable, and wraps the first emitted value in a new Ok Result * - If the Observable errors, it returns a Fail Result with the error * - If the Observable completes without emitting, it returns a Fail Result with the provided default error * - If this Result is a Fail variant, it returns a Promise that resolves to the * original Fail Result without calling the function * * @param fn - A function that takes the Ok value and returns an Observable * @param defaultError - The error to use if the Observable completes without emitting * @returns A Promise that resolves to a new Result * * @requires rxjs@^7.0 * @example * // Chaining Result with reactive code * validateUser(userData) * .flatMapObservable( * user => userService.save(user), * new Error("Failed to save user") * ) * .then(result => result.match({ * ok: savedUser => notifyUserCreated(savedUser), * fail: error => logError("User creation failed", error) * })); * * // Processing real-time data * getSensorData() * .flatMapObservable( * config => sensorApi.connectAndGetReading(config), * new Error("No sensor reading received") * ) * .then(result => result.match({ * ok: reading => updateDashboard(reading), * fail: error => showSensorError(error) * })); */ flatMapObservable<M>( fn: (val: T) => import('rxjs').Observable<M>, defaultError: E ): Promise<IResult<M, E>> } export interface IResultOk<T, E = never> extends IResult<T, E> { unwrap(): T unwrapOr(opt: T): T unwrapFail(): never match<M>(fn: IResultMatchPattern<T, never, M>): M map<M>(fn: (val: T) => M): IResultOk<M, never> mapFail<M>(fn: (err: E) => M): IResultOk<T, never> /** * Specialized version of flatMapMaybe for Ok Results. * * This method always applies the provided function to the contained value and * returns either an Ok result with the unwrapped Some value or a Fail result with * the provided error if the Maybe is None. * * @typeParam M - The type of the value contained in the returned Result if successful * @param fn - A function that takes the success value of this Result and returns a Maybe * @param err - The error value to use if the Maybe is None * @returns Either a Result<M, E> containing the unwrapped value or a failure containing the provided error * * @example * // Using flatMapMaybe with a chain of optional properties * interface Config { * settings?: { * database?: { * url?: string * } * } * } * * // Traditional approach with null checks * function getDatabaseUrl(config: Config): Result<string, string> { * if (!config.settings) return fail("No settings found"); * if (!config.settings.database) return fail("No database settings found"); * if (!config.settings.database.url) return fail("No database URL found"); * return ok(config.settings.database.url); * } * * // Using flatMapMaybe * function getDatabaseUrlMonadic(config: Config): Result<string, string> { * return ok(config) * .flatMapMaybe( * c => maybe(c.settings), * "No settings found" * ) * .flatMapMaybe( * settings => maybe(settings.database), * "No database settings found" * ) * .flatMapMaybe( * database => maybe(database.url), * "No database URL found" * ); * } */ flatMapMaybe<M>(fn: (val: T) => IMaybe<M>, err: E): IResult<M, E> } export interface IResultFail<T, E> extends IResult<T, E> { unwrap(): never unwrapOr(opt: T): T unwrapFail(): E match<M>(fn: IResultMatchPattern<never, E, M>): M map<M>(fn: (val: T) => M): IResultFail<never, E> mapFail<M>(fn: (err: E) => M): IResultFail<never, M> flatMap<M>(fn: (val: T) => IResult<M, E>): IResultFail<never, E> /** * Specialized version of flatMapMaybe for Fail Results. * * This method short-circuits the operation and always returns a Fail result * containing the original error, without executing the provided function. * This maintains the monadic law that operations on Fail do not execute the transformation. * * @typeParam M - The type parameter for the potential success value (never used in this case) * @returns A Fail Result containing the original error * * @example * // Error short-circuiting in a chain of operations * const result = fail<User, string>("User not found") * .flatMapMaybe( * user => maybe(user.profile), // This function is never called * "Profile not found" // This error is never used * ) * .flatMapMaybe( * profile => maybe(profile.email), // This function is never called * "Email not found" // This error is never used * ); * * // result is Result<string, string> containing "User not found" (the original error) * * // Type safety with never * type ApiError = { code: number, message: string }; * * const apiResult = fail<never, ApiError>({ code: 404, message: "Not found" }) * .flatMapMaybe( * // TypeScript ensures we can't access properties on 'never' * // The parameter is essentially inaccessible * _ => maybe(null as never), * { code: 500, message: "Server error" } * ); * * // apiResult is Result<never, ApiError> containing { code: 404, message: "Not found" } */ flatMapMaybe(): IResultFail<never, E> }