Error Flow Diagrams
The error flow diagram visualizes how errors move through your Effect program - which steps produce them, what each handler does to them, and which ones reach the caller in E.
Generating an Error Flow Diagram
Section titled “Generating an Error Flow Diagram”npx effect-analyze ./src/transfer.ts --format mermaid-errorsThe diagram shows:
- Error producers - steps that can fail, annotated with their error types
- Handlers - what intercepts each error, and what it does to it
- Channel - errors that reach the caller still typed, which is the normal case
What each edge means
Section titled “What each edge means”catchTag and orDie both make E smaller. They are opposites, and the
diagram labels them differently:
| Edge | Combinators | Meaning |
|---|---|---|
caught by |
catch, catchTag, catchTags, catchReason, match, orElse |
Recovered from. |
mapped by |
mapError, mapBoth, orElseFail, sandbox |
Still in E, under a different type. |
dies at |
orDie, orDieWith |
Left E as a defect. E reads never, the fiber can still die. |
swallowed by |
ignore, ignoreLogged, orElseSucceed |
Silently became a success. |
--format explain reads a program’s error paths from its type, so an error that
a handler catches or remaps drops out of the list. For catchTags, explain
lists each tag with its own handler.
The filter* operators are absent by design: they test the success value, so
they add to E (or add a defect) without taking anything out of it.
An error reaching the caller is not a fault. E is a declared part of the
signature, and a caller that has to handle TransferRejectedError is the type
system working. Red is reserved for the two rows that take an error out of E
without dealing with it.
flowchart LR
subgraph Steps
step_effect_1["fetchRate"]
step_effect_6["recordAudit"]
step_effect_11["writeLedger"]
step_effect_16["loadConfig"]
step_effect_21["submit"]
end
subgraph Errors
err_AuditFailedError("AuditFailed")
err_ConfigError("Config")
err_LedgerError("Ledger")
err_RateUnavailableError("RateUnavailable")
err_TransferRejectedError("TransferRejected")
end
subgraph Handlers
handler_effect_4["catchTag"]
handler_effect_8["ignore"]
handler_effect_13["orDie"]
handler_effect_19["mapError"]
end
CHANNEL["E #lpar;reaches caller#rpar;"]
step_effect_1 --produces--> err_RateUnavailableError
step_effect_6 --produces--> err_AuditFailedError
step_effect_11 --produces--> err_LedgerError
step_effect_16 --produces--> err_ConfigError
step_effect_21 --produces--> err_TransferRejectedError
err_RateUnavailableError --caught by--> handler_effect_4
err_AuditFailedError --swallowed by--> handler_effect_8
err_LedgerError --dies at--> handler_effect_13
err_ConfigError --mapped by--> handler_effect_19
err_TransferRejectedError --> CHANNEL
classDef stepStyle fill:#BBDEFB
classDef errorStyle fill:#FFE0B2
classDef handledStyle fill:#C8E6C9
classDef transformedStyle fill:#E0E0E0
classDef channelStyle fill:#E3F2FD,stroke:#1565C0
classDef defectStyle fill:#FFCDD2,stroke:#C62828
classDef swallowedStyle fill:#FFE082,stroke:#F9A825
class step_effect_1 stepStyle
class step_effect_6 stepStyle
class step_effect_11 stepStyle
class step_effect_16 stepStyle
class step_effect_21 stepStyle
class err_AuditFailedError errorStyle
class err_ConfigError errorStyle
class err_LedgerError errorStyle
class err_RateUnavailableError errorStyle
class err_TransferRejectedError errorStyle
class handler_effect_4 handledStyle
class handler_effect_8 swallowedStyle
class handler_effect_13 defectStyle
class handler_effect_19 transformedStyle
class CHANNEL channelStyleimport { Effect } from 'effect';
/** * Docs fixture: one program covering every disposition the error diagram * distinguishes — caught, mapped, turned into a defect, swallowed, and left * in the channel for the caller. */
export class RateUnavailableError { readonly _tag = 'RateUnavailableError';}export class AuditFailedError { readonly _tag = 'AuditFailedError';}export class LedgerError { readonly _tag = 'LedgerError';}export class ConfigError { readonly _tag = 'ConfigError';}export class TransferRejectedError { readonly _tag = 'TransferRejectedError';}
declare const fetchRate: () => Effect.Effect<number, RateUnavailableError>;declare const recordAudit: () => Effect.Effect<void, AuditFailedError>;declare const writeLedger: () => Effect.Effect<void, LedgerError>;declare const loadConfig: () => Effect.Effect<string, ConfigError>;declare const submit: () => Effect.Effect<string, TransferRejectedError>;
export const transferWithHandling = Effect.gen(function* () { // Caught: a fallback rate keeps the transfer moving. const rate = yield* fetchRate().pipe( Effect.catchTag('RateUnavailableError', () => Effect.succeed(1)), );
// Swallowed: the audit write becomes a success no matter what happened. yield* recordAudit().pipe(Effect.ignore);
// Defect: `E` now reads `never`, but the fiber still dies on a ledger fault. yield* writeLedger().pipe(Effect.orDie);
// Transformed: still in `E`, under a different type. const config = yield* loadConfig().pipe( Effect.mapError(() => new TransferRejectedError()), );
// Left in the channel: the caller receives this one, typed. const id = yield* submit();
return { id, rate, config };});When the diagram renders itself
Section titled “When the diagram renders itself”If nothing in the program touches the error channel, every error simply reaches the caller - which the railway diagram already shows per step, in flow order. There is no second fact to draw, so the default renders a marker instead and auto mode drops the diagram:
flowchart LR NoHandlers((No handlers - see railway))Asking for it by name overrides that:
npx effect-analyze ./src/transfer.ts --format mermaid-errorsCause Diagrams
Section titled “Cause Diagrams”For programs that use Cause.match or Exit.match, the causes diagram shows the cause hierarchy:
npx effect-analyze ./src/program.ts --format mermaid-causesThis visualizes Fail, Die, Interrupt, and composite causes (Sequential, Parallel).
Programmatic Usage
Section titled “Programmatic Usage”Generate error flow diagrams through the library API:
import { analyze } from "effect-analyzer/analysis"import { renderErrorsMermaid } from "effect-analyzer/diagram"import { Effect } from "effect"
const ir = await Effect.runPromise(analyze("./src/transfer.ts").single)
// Default: renders a marker when no handler touches the channel.const diagram = renderErrorsMermaid(ir)
// Render it regardless of whether anything is handled.const always = renderErrorsMermaid(ir, { when: "always" })
console.log(diagram, always)errorDisposition exposes the same classification to your own tooling:
import { errorDisposition } from "effect-analyzer/analysis"
errorDisposition("catchTag") // "handled"errorDisposition("mapError") // "transformed"errorDisposition("orDie") // "defect"errorDisposition("ignore") // "swallowed"Related
Section titled “Related”- Error Analysis - programmatic error flow analysis with
analyzeErrorFlowandanalyzeErrorPropagation - Railway Diagrams - shows errors as branches off the happy path
- Strict Diagnostics - lint rules for missing error types