import type { ServiceError } from "../errors/serviceError"; import { type ServiceResult } from "./serviceResult"; /** A service error with a literal discriminant used for exhaustive matching. */ export type TaggedServiceError = ServiceError & { readonly _tag: string; }; /** A handler for every tagged member of a ServiceResult error union. */ export type ServiceResultErrorHandlers = { readonly [Tag in E["_tag"]]: (error: Extract) => unknown; }; type ErrorHandlerOutput = { readonly [Tag in keyof Handlers]: Handlers[Tag] extends (...arguments_: infer _Arguments) => infer Output ? Output : never; }[keyof Handlers]; type ExactErrorHandlers> = Handlers & Record, never>; type IsUnion = Value extends unknown ? [Whole] extends [Value] ? false : true : never; type IsSingleLiteralTag = [Tag] extends [never] ? false : string extends Tag ? false : IsUnion extends true ? false : true; type InvalidLiteralTagMember = E extends unknown ? IsSingleLiteralTag extends true ? never : E : never; type LiteralErrorTagConstraint = [ InvalidLiteralTagMember ] extends [never] ? unknown : never; /** * Exhaustively maps a ServiceResult with tagged errors to a single output value. * * Each member of the error union must expose exactly one literal `_tag`. * * Adding a member to the error union requires adding its `_tag` handler before * the call will compile. * * @throws {TypeError} If a runtime caller omits the handler for a failure's tag. * * @example * * * ```ts * import { strictEqual } from "node:assert/strict"; * * import { * failure, * matchServiceResult, * ServiceError, * type ServiceResult, * success, * } from "@clipboard-health/util-ts"; * * interface User { * id: string; * name: string; * } * * class InvalidUserIdError extends ServiceError { * public readonly _tag = "InvalidUserIdError" as const; * * public constructor() { * super("User ID is required"); * } * } * * class UserNotFoundError extends ServiceError { * public readonly _tag = "UserNotFoundError" as const; * * public readonly userId: string; * * public constructor({ userId }: { userId: string }) { * super(`User ${userId} was not found`); * this.userId = userId; * } * } * * type FindUserError = InvalidUserIdError | UserNotFoundError; * * function findUser({ userId }: { userId: string }): ServiceResult { * if (userId.length === 0) { * return failure(new InvalidUserIdError()); * } * * if (userId === "missing") { * return failure(new UserNotFoundError({ userId })); * } * * return success({ id: userId, name: "Ada" }); * } * * const message = matchServiceResult(findUser({ userId: "missing" }), { * onSuccess: (user) => `Found ${user.name}`, * onError: { * InvalidUserIdError: (error) => error.message, * UserNotFoundError: (error) => `User ${error.userId} was not found`, * }, * }); * * strictEqual(message, "User missing was not found"); * ``` * * */ export declare function matchServiceResult>(result: ServiceResult & LiteralErrorTagConstraint, options: { readonly onSuccess: (value: A) => SuccessOutput; readonly onError: ExactErrorHandlers; }): SuccessOutput | ErrorHandlerOutput; export {};