Skip to content

Commit cc3caf0

Browse files
committed
docs: drop redudant sections
1 parent 72c8628 commit cc3caf0

1 file changed

Lines changed: 0 additions & 220 deletions

File tree

apps/web/src/content/docs/v4/guides/modeling-errors.mdx

Lines changed: 0 additions & 220 deletions
Original file line numberDiff line numberDiff line change
@@ -830,176 +830,6 @@ never entered the `Error` type parameter: it became a defect.
830830
with the error. Unknown defects stay fatal, so the handler re-raises them with
831831
`Effect.die`.
832832

833-
### Typed recovery never catches a defect
834-
835-
`Effect.catchTags` from the previous section handles every modeled failure.
836-
Does it also handle defects? Give the job both steps, authorize then capture,
837-
and handle every tag:
838-
839-
```ts twoslash import.meta.vitest
840-
import { Effect, Data } from "effect"
841-
842-
interface Authorization {
843-
readonly authorizationId: string
844-
readonly status: "authorized" | "declined"
845-
}
846-
847-
class NetworkError extends Data.TaggedError("NetworkError")<{
848-
readonly cause?: unknown
849-
}> {}
850-
class PaymentDeclinedError extends Data.TaggedError("PaymentDeclinedError") {}
851-
class ParseError extends Data.TaggedError("ParseError")<{
852-
readonly cause?: unknown
853-
}> {}
854-
class DoubleCaptureError extends Data.TaggedError("DoubleCaptureError")<{
855-
readonly authorizationId: string
856-
}> {}
857-
858-
function isAuthorization(body: unknown): body is Authorization {
859-
const r = body as Authorization
860-
return (
861-
(r?.status === "authorized" || r?.status === "declined") &&
862-
typeof r?.authorizationId === "string"
863-
)
864-
}
865-
866-
type PaymentToken = "tok_visa" | "tok_declined" | "tok_timeout"
867-
868-
const authorizePayment = async (token: PaymentToken): Promise<unknown> => {
869-
if (token === "tok_timeout") {
870-
throw new Error("Provider unreachable")
871-
}
872-
if (token === "tok_declined") {
873-
return { authorizationId: "pauth_123", status: "declined" }
874-
}
875-
return { authorizationId: "pauth_123", status: "authorized" }
876-
}
877-
878-
const processPayment = (token: PaymentToken) =>
879-
Effect.gen(function* () {
880-
const response = yield* Effect.tryPromise({
881-
try: () => authorizePayment(token),
882-
catch: (cause) => new NetworkError({ cause }),
883-
})
884-
885-
if (!isAuthorization(response)) {
886-
return yield* new ParseError({})
887-
}
888-
889-
if (response.status === "declined") {
890-
return yield* new PaymentDeclinedError()
891-
}
892-
893-
return response
894-
})
895-
896-
const captured = new Set(["pauth_123"])
897-
898-
const capturePayment = (
899-
authorization: Authorization,
900-
): Effect.Effect<Authorization> =>
901-
Effect.gen(function* () {
902-
if (captured.has(authorization.authorizationId)) {
903-
return yield* Effect.die(
904-
new DoubleCaptureError({
905-
authorizationId: authorization.authorizationId,
906-
}),
907-
)
908-
}
909-
captured.add(authorization.authorizationId)
910-
return authorization
911-
})
912-
913-
const captureFlow = (token: PaymentToken) =>
914-
Effect.gen(function* () {
915-
const authorization = yield* processPayment(token)
916-
return yield* capturePayment(authorization)
917-
})
918-
919-
// ---cut---
920-
// ┌─ Effect.Effect<string | Authorization, never>
921-
// |
922-
//
923-
const outcome = captureFlow("tok_visa").pipe(
924-
Effect.catchTags({
925-
NetworkError: () => Effect.succeed("handled NetworkError"),
926-
ParseError: () => Effect.succeed("handled ParseError"),
927-
PaymentDeclinedError: () => Effect.succeed("handled PaymentDeclinedError"),
928-
}),
929-
Effect.catchDefect(() => Effect.succeed("the defect passed through")),
930-
)
931-
932-
await Effect.runPromise(outcome) // => "the defect passed through"
933-
```
934-
935-
The table handles every tag, and the `Error` type parameter is `never`. The
936-
defect still terminated the program: typed recovery never catches defects, so
937-
only `Effect.catchDefect` saw it.
938-
939-
### Guard impossible data
940-
941-
Whether a bug throws an error or you write it with `Effect.die`, it lands in
942-
the same place: the `Cause`. `Effect.die` is the deliberate form, for the
943-
invariants you check on purpose.
944-
945-
The third invariant is a status the type says cannot exist. The problem:
946-
947-
1. `authorization.status` has two members: `"authorized"` and `"declined"`.
948-
1. Data from an untyped source, a parsed JSON body or a third-party response,
949-
can carry anything at runtime.
950-
1. A `default` arm in the switch guards the difference. The type says the arm
951-
is unreachable, the runtime says otherwise.
952-
953-
The arm casts before reading the status, because TypeScript considers it dead
954-
code. `Effect.die` is what it returns: an exception you raise on purpose. It
955-
never enters the `Error` type parameter.
956-
957-
```ts twoslash import.meta.vitest
958-
import { Effect } from "effect"
959-
960-
interface Authorization {
961-
readonly authorizationId: string
962-
readonly status: "authorized" | "declined"
963-
}
964-
965-
// ┌─ Effect.Effect<string>: no `Error` type parameter
966-
//
967-
const label = (authorization: Authorization) => {
968-
switch (authorization.status) {
969-
case "authorized":
970-
return Effect.succeed(`charged ${authorization.authorizationId}`)
971-
case "declined":
972-
return Effect.succeed("no charge")
973-
default: {
974-
// Unreachable per the type. Here for the runtime anyway.
975-
const status = (authorization as { status: string }).status
976-
return Effect.die(new Error(`Impossible status: ${status}`))
977-
}
978-
}
979-
}
980-
981-
// The data lies: a status the type says cannot exist.
982-
const forged = {
983-
authorizationId: "pauth_123",
984-
status: "refunded",
985-
} as unknown as Authorization
986-
987-
const outcome = label(forged).pipe(
988-
Effect.catchDefect((defect) =>
989-
defect instanceof Error
990-
? Effect.succeed(`terminated with a defect: ${defect.message}`)
991-
: Effect.die(defect),
992-
),
993-
)
994-
995-
await Effect.runPromise(outcome) // => "terminated with a defect: Impossible status: refunded"
996-
```
997-
998-
The forged value stands in for the untyped source. The status is `"refunded"`:
999-
the type says it cannot exist, and the runtime says otherwise. The impossible
1000-
arm fired, and `Effect.die` terminated with a descriptive `Error`. Pass an
1001-
`Error` with a useful message: it is the report `catchDefect` receives.
1002-
1003833
### Stop tracking failures no caller can respond to
1004834

1005835
`Effect.orDie` is a modeling decision: no caller can do anything with these
@@ -1058,56 +888,6 @@ alerts whoever owns it. Nothing here is recoverable in place, so nothing is
1058888
left to track. The failure crossed the boundary as a defect:
1059889
`Effect.runPromise` rejected with the `PaymentDeclinedError` itself.
1060890

1061-
### Promote one failure to a defect
1062-
1063-
`Effect.orDie` promotes every failure. Promote one failure when it is
1064-
a bug, not an expected outcome. Suppose the provider is your own service, and
1065-
its contract guarantees the response shape. `ParseError` stops being a failure
1066-
a caller can respond to: it is a broken promise. To promote one while keeping
1067-
the rest tracked, catch the tag and die with it:
1068-
1069-
```ts twoslash showLineNumbers=false
1070-
import { Effect, Data } from "effect"
1071-
1072-
interface Authorization {
1073-
readonly authorizationId: string
1074-
readonly status: "authorized" | "declined"
1075-
}
1076-
1077-
class NetworkError extends Data.TaggedError("NetworkError")<{
1078-
readonly cause?: unknown
1079-
}> {}
1080-
class PaymentDeclinedError extends Data.TaggedError("PaymentDeclinedError") {}
1081-
class ParseError extends Data.TaggedError("ParseError")<{
1082-
readonly cause?: unknown
1083-
}> {}
1084-
1085-
// Tutorial fixture: sandbox tokens make provider results reproducible.
1086-
type PaymentToken = "tok_visa" | "tok_declined" | "tok_timeout"
1087-
1088-
declare const processPayment: (
1089-
token: PaymentToken,
1090-
) => Effect.Effect<
1091-
Authorization,
1092-
PaymentDeclinedError | NetworkError | ParseError
1093-
>
1094-
1095-
// ┌─ Effect.Effect<Authorization, PaymentDeclinedError | NetworkError>
1096-
// | `ParseError` is now a defect; `PaymentDeclinedError` and `NetworkError` stay tracked
1097-
//
1098-
const strictProcessPayment = (token: PaymentToken) =>
1099-
processPayment(token).pipe(
1100-
Effect.catchTag("ParseError", (error) => Effect.die(error)),
1101-
)
1102-
```
1103-
1104-
Keep `PaymentDeclinedError` and `NetworkError` as failures:
1105-
1106-
- A decline has a message for the customer.
1107-
- A timeout has a retry.
1108-
1109-
Promoting those would make them invisible to every caller.
1110-
1111891
### Recover at the boundary
1112892

1113893
A defect is a bug. A handler cannot fix it. But the boundary decides what

0 commit comments

Comments
 (0)