jagreehal/effect-analyzer
GitHub: jagreehal/effect-analyzer
Stars: 19 | Forks: 0
# effect-analyzer
Static analysis for [Effect](https://effect.website/) programs. Visualize service dependencies, error channels, concurrency, and control flow as Mermaid diagrams - without running your code.
## Why
Effect programs are powerful, but their structure - service dependencies, error topology, concurrency patterns - is hard to see in source. effect-analyzer parses your code with [ts-morph](https://ts-morph.com/) and the TypeScript type checker, then produces semantic diagrams and structured analysis. No runtime, no instrumentation.
Use it for **code review**, **onboarding**, **architecture docs**, and **CI** to catch regressions in program shape.
## Install
npm install -D effect-analyzer
`effect` (>=3.0.0) is a required peer dependency. `ts-morph` is bundled automatically.
## Quick Start
# Auto-select the best diagrams for a file
npx effect-analyze ./src/transfer.ts
# Railway diagram (linear happy path with error branches)
npx effect-analyze ./src/transfer.ts --format mermaid-railway
# Plain-English explanation of what a program does
npx effect-analyze ./src/transfer.ts --format explain
# Compare two versions
npx effect-analyze HEAD:src/transfer.ts src/transfer.ts --diff
# Audit an entire project
npx effect-analyze ./src --coverage-audit
## What You Get
Given an Effect program like this:
export const transfer = Effect.gen(function* () {
const repo = yield* AccountRepo
const audit = yield* AuditLog
const balance = yield* repo.getBalance("from-account")
if (balance < 100) {
yield* Effect.fail(new InsufficientFundsError(balance, 100))
}
yield* repo.debit("from-account", 100)
yield* repo.credit("to-account", 100)
yield* audit.record("transfer-complete")
})
The analyzer produces a railway diagram showing the happy path with error branches:
flowchart LR
A["repo <- AccountRepo"] -->|ok| B["audit <- AuditLog"]
B -->|ok| C["balance <- repo.getBalance"]
C -->|ok| D{"balance < 100"}
D -->|ok| E["repo.debit"]
E -->|ok| F["repo.credit"]
F -->|ok| G["audit.record"]
G -->|ok| Done((Success))
C -.->|err| Err1([AccountNotFound])
D -.->|err| Err2([InsufficientFunds])
Or a flowchart showing all control flow paths:
flowchart TB
start((Start))
n2["repo <- AccountRepo"]
n3["audit <- AuditLog"]
n4["balance <- repo.getBalance"]
decision{"balance < 100?"}
n7["Effect.fail(InsufficientFunds)"]
n8["repo.debit"]
n9["repo.credit"]
n10["audit.record"]
end_node((Done))
start --> n2 --> n3 --> n4 --> decision
decision -->|yes| n7
decision -->|no| n8
n7 -.-> end_node
n8 --> n9 --> n10 --> end_node
## Features
### 15+ Diagram Types
Auto-mode picks the most relevant views for your program, or choose explicitly:
| Format | Shows |
|--------|-------|
| `mermaid-railway` | Linear happy path with error branches |
| `mermaid` | Full flowchart with all control flow |
| `mermaid-services` | Service dependency map |
| `mermaid-errors` | Error propagation and handling |
| `mermaid-concurrency` | Parallel and race patterns |
| `mermaid-layers` | Layer composition graph |
| `mermaid-retry` | Retry and timeout strategies |
| `mermaid-timeline` | Step sequence over time |
| `mermaid-statechart` | State machine as a `stateDiagram-v2` |
| `svg-statechart` | Self-contained, XState-styled statechart SVG |
| `statechart-html` | Local visualizer page with SVG, coverage, and XState export |
| `xstate-config` | `createMachine()` config for the [Stately visualizer](https://stately.ai/viz) |
[See all formats →](https://jagreehal.github.io/effect-analyzer/diagrams/all-formats/)
### State Machines Without XState
Write deterministic state machines in plain Effect — a declarative transition
table, a `Match.when` transition function, or nested `Match.tags` state/event
dispatch — and render them as XState-style statecharts. No XState dependency
required. See the full convention guide in
[`state-machine-conventions.md`](./state-machine-conventions.md).
# No flags: the default view surfaces any state machine in the file
npx effect-analyze ./workflow.ts
# A local visualizer page (diagram + coverage + paste-ready config).
# With no -o it writes workflow.statechart.html next to the input
npx effect-analyze ./workflow.ts --format statechart-html
# A stateDiagram-v2 for markdown / GitHub
npx effect-analyze ./workflow.ts --format mermaid-statechart
# An XState createMachine() config — paste into stately.ai/viz for the real
# interactive visualizer, generated straight from your Effect code
npx effect-analyze ./workflow.ts --format xstate-config
These shapes are recognized:
// A) declarative transition table
const transitions = {
Triage: {
RefundRequested: { target: 'Refund', guard: 'canRefund' },
AnswerRequested: 'Answered',
},
Refund: { Resolved: 'Answered' },
Answered: {},
} as const;
// B) Match.when transition function
const transition = (state: State, event: Event): State =>
Match.value([state._tag, event._tag] as const).pipe(
Match.when(['Draft', 'Submit'], () => ({ _tag: 'Review' as const })),
Match.orElse(() => state),
);
// C) nested Match.tags with state tags outside and event tags inside
const transitionWithTags = (state: State, event: Event): State =>
Match.value(state).pipe(
Match.tags({
Draft: () =>
Match.value(event).pipe(
Match.tags({
Submit: () => ({ _tag: 'Review' as const }),
}),
),
Review: () => state,
}),
);
Initial state is read from an `@initial ` annotation or an
`initial`/`initialState` declaration. Table leaves can be strings,
`{ target, guard }`, `{ to }`, or arrays of guarded targets. A handler that can
return more than one state becomes a guarded (multi-target) transition.
#### Completeness checking (Schema-aware)
When the State/Event types are a tagged union or a `Schema`-derived type, the
analyzer reads the **declared alphabet** and checks the machine against it —
turning the statechart from a drawing into a verified machine:
npx effect-analyze ./workflow.ts --format statechart-coverage
# State machine coverage
1 machine, 2 warnings.
## checkoutTransition (alphabet: schema)
Coverage: 33% (2/6 reachable state×event pairs handled)
- ⚠ Unhandled events: `Cancel` # declared, but no state handles it
- ⚠ Unreachable states: `Cancelled` # declared, but nothing transitions to it
It reports **unhandled events**, **unreachable states**, and **undeclared
symbols** (transitions that drifted from the types). The command **exits
non-zero when any warning is found**, so it works as a CI gate. The
`mermaid-statechart` and `svg-statechart` outputs are annotated with the same
findings (orphaned states highlighted, unhandled events noted).
Run it over a whole directory for a summary table, set a coverage floor, or emit
JSON for dashboards:
npx effect-analyze ./src --format statechart-coverage # all machines, summary table
npx effect-analyze ./src --format statechart-coverage --min-coverage 60 # fail under 60%
npx effect-analyze ./src --format statechart-coverage --coverage-json # { machines, summary }
Guarded (conditional) transitions are captured with their condition and shown
on every renderer (`Event [guard]` in diagrams, `{ target, guard }` in the
XState config). State/Event alphabets may be tagged unions, `Schema`-derived
types, `Schema.TaggedClass`/`Schema.TaggedRequest` unions, or plain
string-literal unions (`'a' | 'b'`).
Plain single-level `Match.tags` dispatch is intentionally ignored unless there
is a nested state/event shape, because ordinary variant handling does not have
the source-state dimension required for a statechart.
### Complexity Metrics
Six metrics calculated for every program: cyclomatic complexity, cognitive complexity, path count, nesting depth, parallel breadth, and decision points.
npx effect-analyze ./src/transfer.ts --format stats
[Learn more →](https://jagreehal.github.io/effect-analyzer/analysis/complexity/)
### Semantic Diff
Compare two versions of a program at the structural level - not text diffs, but changes in steps, services, and control flow:
npx effect-analyze HEAD:src/transfer.ts src/transfer.ts --diff
[Learn more →](https://jagreehal.github.io/effect-analyzer/project/diff/)
### Coverage Audit
Scan an entire project to understand Effect usage, identify complex programs, and track analysis quality:
npx effect-analyze ./src --coverage-audit
[Learn more →](https://jagreehal.github.io/effect-analyzer/project/coverage-audit/)
### Interactive HTML Viewer
Generate a self-contained HTML page with search, filtering, path explorer, complexity heatmap, and 6 color themes:
import { renderInteractiveHTML } from "effect-analyzer"
const html = renderInteractiveHTML(ir, { theme: "midnight" })
[Learn more →](https://jagreehal.github.io/effect-analyzer/reference/html-viewer/)
### Library API
Use the programmatic API to integrate analysis into your own tools:
import { analyze } from "effect-analyzer"
import { Effect } from "effect"
const ir = await Effect.runPromise(analyze("./src/transfer.ts").single())
console.log(ir.root.programName) // "transfer"
console.log(ir.root.dependencies) // [{ name: "AccountRepo", ... }, ...]
console.log(ir.root.errorTypes) // ["InsufficientFundsError", "AccountNotFoundError"]
[Full API reference →](https://jagreehal.github.io/effect-analyzer/reference/api/)
## What It Detects
| Area | Patterns |
|------|----------|
| **Programs** | `Effect.gen`, pipe chains, `Effect.sync`, `Effect.async`, `Effect.promise` |
| **Services** | `Context.Tag` via `yield*`, service method calls |
| **Layers** | `Layer.mergeAll`, `Layer.effect`, `Layer.provide`, `Layer.succeed` |
| **Errors** | `catchTag`, `catchAll`, `tapError`, `retry`, `timeout` |
| **Concurrency** | `Effect.all`, `Effect.race`, `Effect.fork`, `Fiber.join` |
| **Resources** | `acquireRelease`, `ensuring`, `Effect.scoped` |
| **Streams** | `Stream.fromIterable`, `Stream.mapEffect`, `Stream.runCollect` |
| **Control flow** | `if/else`, `for..of`, `while`, `try/catch`, `switch` inside generators |
| **Schedules** | `Schedule.recurs`, `Schedule.exponential` |
| **Aliases** | `const E = Effect`, destructured imports, renamed imports |
## Requirements
- Node.js 22+
- TypeScript project with `effect` (>=3.0.0)
## Documentation
Full documentation is available at **[jagreehal.github.io/effect-analyzer](https://jagreehal.github.io/effect-analyzer/)**.
## License
MIT
标签:自动化攻击