5.0 KiB
5.0 KiB
Result Utilities
Operations for working with Result<T, E> from better-result.
Creating Results
import { Result } from "@outfitter/contracts";
// Success
const ok = Result.ok({ name: "Alice", id: "1" });
// Failure
const err = Result.err(new NotFoundError("user", "123"));
Checking Results
// Boolean check
if (result.isOk()) {
console.log(result.value); // TypeScript knows type
}
if (result.isErr()) {
console.log(result.error); // TypeScript knows error type
}
Accessing Values
// Safe access (only after isOk check)
if (result.isOk()) {
const user = result.value;
}
// Unsafe access (throws if error)
const user = result.unwrap(); // Throws if err!
// With default
const user = result.unwrapOr(defaultUser);
// With default factory
const user = result.unwrapOrElse(() => createDefaultUser());
Pattern Matching
const message = result.match({
ok: (user) => `Found ${user.name}`,
err: (error) => `Error: ${error.message}`,
});
Transforming Results
Map (transform success value)
const nameResult = result.map((user) => user.name);
// Result<string, Error>
MapErr (transform error)
const mappedResult = result.mapErr((error) => new WrappedError(error));
// Result<User, WrappedError>
FlatMap / AndThen (chain operations)
const orderResult = getUserResult.flatMap((user) => getOrders(user.id));
// Result<Orders, UserError | OrderError>
Combining Results
combine2, combine3, etc.
Combine multiple results into a tuple:
import { combine2, combine3 } from "@outfitter/contracts";
const result = combine2(userResult, orderResult);
// Result<[User, Order], UserError | OrderError>
if (result.isOk()) {
const [user, order] = result.value;
}
combineAll
Combine an array of results:
import { combineAll } from "@outfitter/contracts";
const results = await Promise.all(ids.map((id) => getUser(id)));
const combined = combineAll(results);
// Result<User[], Error>
combineObject
Combine an object of results:
import { combineObject } from "@outfitter/contracts";
const combined = combineObject({
user: userResult,
orders: ordersResult,
settings: settingsResult,
});
// Result<{ user: User; orders: Order[]; settings: Settings }, Error>
Error Recovery
OrElse (try alternative on error)
const result = primaryResult.orElse(() => fallbackResult);
Recover (convert error to success)
const result = userResult.recover((error) => {
if (error._tag === "NotFoundError") {
return Result.ok(defaultUser);
}
return Result.err(error);
});
Async Patterns
Sequential execution
const result = await getUser(id)
.then((r) => r.isOk() ? getOrders(r.value.id) : Promise.resolve(r));
With async/await
async function getUserWithOrders(id: string): Promise<Result<UserWithOrders, Error>> {
const userResult = await getUser(id);
if (userResult.isErr()) return userResult;
const ordersResult = await getOrders(userResult.value.id);
if (ordersResult.isErr()) return ordersResult;
return Result.ok({
user: userResult.value,
orders: ordersResult.value,
});
}
Validation Helper
The createValidator utility returns Result:
import { createValidator } from "@outfitter/contracts";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
age: z.number().int().positive(),
});
const validate = createValidator(schema);
const result = validate({ email: "test@example.com", age: 25 });
// Result<{ email: string; age: number }, ValidationError>
Common Patterns
Early return on error
const handler: Handler<Input, Output, Error> = async (input, ctx) => {
const validateResult = validate(input);
if (validateResult.isErr()) return validateResult;
const userResult = await getUser(validateResult.value.userId);
if (userResult.isErr()) return userResult;
const orderResult = await createOrder(userResult.value);
if (orderResult.isErr()) return orderResult;
return Result.ok(orderResult.value);
};
Collect all errors
const errors: ValidationError[] = [];
if (!input.name) errors.push(new ValidationError("Name required"));
if (!input.email) errors.push(new ValidationError("Email required"));
if (errors.length > 0) {
return Result.err(new ValidationError("Multiple errors", { errors }));
}
Wrap throwing functions
function wrapThrowable<T>(fn: () => T): Result<T, InternalError> {
try {
return Result.ok(fn());
} catch (error) {
return Result.err(new InternalError("Unexpected error", { cause: error }));
}
}
async function wrapAsync<T>(fn: () => Promise<T>): Promise<Result<T, InternalError>> {
try {
return Result.ok(await fn());
} catch (error) {
return Result.err(new InternalError("Unexpected error", { cause: error }));
}
}