Skip to content

Error Analysis

The error analysis module provides deep inspection of how errors flow through your Effect programs. It goes beyond the visual error flow diagram to offer programmatic analysis of error producers, propagation chains, and handler coverage.

The analyzeErrorFlow function extracts all error types, maps them to the steps that produce them, and identifies steps without declared error types:

import { analyze, analyzeErrorFlow } from "effect-analyzer"
import { Effect } from "effect"
const ir = await Effect.runPromise(analyze("./src/transfer.ts").single())
const flow = analyzeErrorFlow(ir)
console.log(flow.allErrors) // ["AccountNotFound", "InsufficientFunds"]
console.log(flow.stepErrors) // Per-step error breakdown
console.log(flow.errorToSteps) // Map: error type → step IDs
console.log(flow.stepsWithoutErrors) // Steps with no declared errors
console.log(flow.allStepsDeclareErrors) // true if every step has error types

The ErrorFlowAnalysis object contains:

FieldTypeDescription
allErrorsstring[]All distinct error types in the program
stepErrorsStepErrorInfo[]Per-step breakdown of errors
errorToStepsMap<string, string[]>Map from error type to the steps that produce it
stepsWithoutErrorsstring[]Step IDs that have no declared error type
allStepsDeclareErrorsbooleanWhether every step has error annotations

Each StepErrorInfo includes:

FieldTypeDescription
stepIdstringIR node ID
stepNamestring | undefinedHuman-readable name
errorsstring[]Error types this step can produce

Use analyzeErrorPropagation to track how errors flow through the program and how handlers narrow the error set:

import { analyzeErrorPropagation } from "effect-analyzer"
const propagation = analyzeErrorPropagation(ir)
for (const entry of propagation.propagation) {
console.log(`At node ${entry.atNode}:`)
console.log(` Possible errors: ${entry.possibleErrors}`)
if (entry.narrowedBy) {
console.log(` Handler: ${entry.narrowedBy.handler}`)
console.log(` Removed: ${entry.narrowedBy.removedErrors}`)
console.log(` Added: ${entry.narrowedBy.addedErrors}`)
}
console.log(` Defects: ${entry.defects}`)
console.log(` Interruptible: ${entry.interruptible}`)
}

Each ErrorPropagation entry records:

FieldTypeDescription
atNodestringThe IR node ID
possibleErrorsstring[]Errors that could exist at this point
narrowedByobject | undefinedHandler that catches errors here
defectsstring[]Defect types (unrecoverable)
interruptiblebooleanWhether the fiber is interruptible at this point

Look up the error state at a particular node:

import { analyzeErrorPropagation, getErrorsAtPoint } from "effect-analyzer"
const propagation = analyzeErrorPropagation(ir)
const errorsAtDebit = getErrorsAtPoint(propagation, "step-3")
// ["InsufficientFunds", "AccountNotFound"]

Get all steps that can produce a specific error type:

import { analyzeErrorFlow, getErrorProducers } from "effect-analyzer"
const flow = analyzeErrorFlow(ir)
const producers = getErrorProducers(flow, "AccountNotFound")
// ["getBalance", "credit"]

Use validateWorkflowErrors to check whether all errors are properly handled:

import { validateWorkflowErrors } from "effect-analyzer"
const validation = validateWorkflowErrors(ir)
console.log(validation.valid) // true if all errors are handled
console.log(validation.undeclaredErrors) // Errors produced but not in the type signature
console.log(validation.unusedDeclared) // Declared errors that no step produces
console.log(validation.computedErrors) // The computed error union

Generate a human-readable error summary:

import { analyzeErrorFlow, formatErrorSummary } from "effect-analyzer"
const flow = analyzeErrorFlow(ir)
const summary = formatErrorSummary(flow)
console.log(summary)

Render the error flow as a Mermaid diagram:

import { analyzeErrorFlow, renderErrorFlowMermaid } from "effect-analyzer"
const flow = analyzeErrorFlow(ir)
const diagram = renderErrorFlowMermaid(flow)

Or via the CLI:

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