Skip to content

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.

Terminal window
npx effect-analyze ./src/transfer.ts --format mermaid-errors

The 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

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 channelStyle

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:

Terminal window
npx effect-analyze ./src/transfer.ts --format mermaid-errors

For programs that use Cause.match or Exit.match, the causes diagram shows the cause hierarchy:

Terminal window
npx effect-analyze ./src/program.ts --format mermaid-causes

This visualizes Fail, Die, Interrupt, and composite causes (Sequential, Parallel).

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"