Error Flow Diagrams
The error flow diagram visualizes how errors propagate through your Effect program - which steps produce errors, which handlers catch them, and which errors remain unhandled at the program boundary.
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
- Error handlers -
catch,catchTag,catchTagsnodes that intercept errors - Unhandled errors - errors that propagate out of the program without being caught
For a program with AccountNotFoundError and InsufficientFundsError:
graph TB getBalance -->|AccountNotFoundError| ERR1[AccountNotFoundError] decision -->|InsufficientFundsError| ERR2[InsufficientFundsError] debit -->|AccountNotFoundError| ERR1 credit -->|AccountNotFoundError| ERR1 ERR1 -.->|unhandled| BOUNDARY[Program Boundary] ERR2 -.->|unhandled| BOUNDARY
import { Context, Effect } from 'effect';
export class AccountNotFoundError { readonly _tag = 'AccountNotFoundError'; constructor(readonly accountId: string) {}}
export class InsufficientFundsError { readonly _tag = 'InsufficientFundsError'; constructor( readonly available: number, readonly requested: number, ) {}}
export class AccountService extends Context.Tag('AccountService')< AccountService, { readonly getBalance: ( accountId: string, ) => Effect.Effect<number, AccountNotFoundError>; readonly debit: ( accountId: string, amount: number, ) => Effect.Effect<void, AccountNotFoundError>; readonly credit: ( accountId: string, amount: number, ) => Effect.Effect<void, AccountNotFoundError>; }>() {}
export class AuditLog extends Context.Tag('AuditLog')< AuditLog, { readonly record: (message: string) => Effect.Effect<void>; }>() {}
export const transferWorkflow = ( fromAccountId: string, toAccountId: string, amount: number,) => Effect.gen(function* () { const accounts = yield* AccountService; const audit = yield* AuditLog;
const balance = yield* accounts.getBalance(fromAccountId);
if (balance < amount) { return yield* Effect.fail( new InsufficientFundsError(balance, amount), ); }
yield* accounts.debit(fromAccountId, amount); yield* accounts.credit(toAccountId, amount); yield* audit.record( `Transferred ${amount} from ${fromAccountId} to ${toAccountId}`, );
return { fromAccountId, toAccountId, amount, }; });If the program includes a catchTag handler, the diagram shows the error being intercepted:
graph TB getBalance -->|AccountNotFoundError| CATCH[catchTag AccountNotFoundError] CATCH --> recovery[fallback logic]
When Auto Mode Selects Error Flow
Section titled “When Auto Mode Selects Error Flow”Auto mode includes the error flow view when your program has:
- Multiple distinct error types
- One or more error handler nodes (
catch,catchTag,catchTags) - A mix of handled and unhandled errors
Cause 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)const diagram = renderErrorsMermaid(ir)
console.log(diagram)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