-
Intent: All fallible operations must return a
Result<T, E>
(from neverthrow) instead of throwing or returningnull
/undefined
. This makes failures explicit in the type system. -
Tagged Errors: Define every custom error as a class using
Data.TaggedError("Name")<Props>
. Name each error in PascalCase ending withError
. For example:
import { Data } from "effect";
export class InvalidPaymentError extends Data.TaggedError("InvalidPayment")<{
cause: unknown;
}> {}
export class InvalidPaymentAmountError extends Data.TaggedError("InvalidPaymentAmount")<{
amount: unknown;
}> {}
- Fallible Functions: Functions that may fail must return
Result<SuccessType, ErrorType>
. Never usethrow
,null
, orundefined
to signal errors.
import { ok, err, type Result } from "neverthrow";
function parseAmount(input: string): Result<number, InvalidPaymentAmountError> {
const value = Number(input);
return isNaN(value)
? err(new InvalidPaymentAmountError({ amount: input }))
: ok(value);
}
- Composition & Handling: Chain multiple
Result
values using.andThen()
orResult.combine()
. Use.match()
or.mapErr()
to handle outcomes. Example:
const getUser = (id: string): Result<User, FetchError> => { /* ... */ };
const getProfile = (user: User): Result<Profile, ProfileError> => { /* ... */ };
const result = getUser("42").andThen(getProfile);
result.match(
profile => console.log("Success:", profile),
error => console.error(`${error._tag}:`, error)
);
- Structured Flow with Generators: Use
safeTry(yield*)
for linear, composable fallible workflows which compose results into a new more complex one. All yielded calls must returnResult
.
import { safeTry } from "neverthrow";
import { Data } from "effect";
class UserNotFoundError extends Data.TaggedError("UserNotFound")<{ userId: string }> {}
class CompanyPolicyFetchError extends Data.TaggedError("CompanyPolicyFetch")<{ companyId: string }> {}
class UserNotRegisteredError extends Data.TaggedError("UserNotRegistered")<{ userId: string, companyId: string }> {}
const getUser = (id: string): Result<User, UserNotFoundError> => { /* ... */ };
const getCompanyPolicies = (companyId: string): Result<CompanyPolicy[], CompanyPolicyFetchError> => { /* ... */ };
const checkUserInCompany = (userId: string, companyId: string): Result<true, UserNotRegisteredError> => { /* ... */ };
const result = safeTry(function* () {
const user = yield* getUser("u123");
const policies = yield* getCompanyPolicies(user.companyId);
yield* checkUserInCompany(user.id, user.companyId);
return { user, policies };
});
result.match(
({ user, policies }) => console.log("Access granted to", user.name),
error => console.error(`${error._tag}:`, error)
);