gleam-type-graph
v0.2.0
Published
Static type-graph extractor for Gleam codebases. Maps types to the functions that transform them, rendered as mermaid / graphviz / json / interactive HTML.
Downloads
296
Maintainers
Readme
gleam-type-graph
Statically extract a directed graph of types and the functions that transform them from a Gleam codebase.
- Nodes = types (
Order,OrderSnapshot,Json,String, ...) - Edges = functions that turn one type into another (
order.snapshot,json.to_string, ...)
Read the resulting graph as: to convert an X into a Y, here's the path
of functions to compose.
Quick start
cd /your/gleam/project
npx gleam-type-graph --openThat's it. From inside any Gleam project (or any subdirectory of one), this
walks up to find your gleam.toml, scans src/, renders an interactive
mermaid diagram, and opens it in your browser.
No gleam toolchain required on your machine — the tool ships pre-compiled
to JavaScript.
Other invocations
# Interactive cytoscape view: compound modules with click-to-expand,
# magnetic drag, focus-narrowing on type click, source-jump on function
# click, sticky right-click menu, undo (Ctrl-Z).
npx gleam-type-graph --format cytoscape --open
# Mermaid to stdout (good for piping into a markdown file)
npx gleam-type-graph
# Sorted plain-text edge list, easiest to grep / diff
npx gleam-type-graph --format text
# Drop edges between stdlib / primitive types — show just the domain core
npx gleam-type-graph --domain-only --open
# Graphviz dot (for `dot -Tpng -o graph.png`)
npx gleam-type-graph --format=dot > graph.dot
dot -Tpng graph.dot -o graph.png
# JSON for downstream tooling
npx gleam-type-graph --format=json > graph.json
# Light-theme HTML
npx gleam-type-graph --open --theme light
# Module-level overview: collapse every type/function into its owning
# module bubble so you see only how modules connect.
npx gleam-type-graph --collapse modules --open
# Point at a specific src/ tree
npx gleam-type-graph /some/other/project/src --openCytoscape view interactions
- Click a type node → focus its 1-hop neighborhood. Click the same node again → also resurrect previously-hidden neighbors. Click a third time → reset.
- Click a function node or labelled edge → opens its source
(hex docs, or GitHub source for
@internalitems). - Click a collapsed module → expand it. Double-click an expanded module → collapse.
- Click an expanded module's empty area → focus on the module's full neighborhood (the module + every node any of its children connects to).
- Right-click anything → sticky menu: focus, expand/collapse, open source, hide.
- Drag a node → connected neighbors follow (magnetic spring-ish
feel). Toggle off with the
freeze dragbutton. - Hover anything → triple-size highlight of involved nodes; everything else dims.
- Ctrl-Z / Cmd-Z → undo focus/hide actions.
Example output
Given a project with Order, OrderSnapshot, Money, CustomerId, and a
JSON encoder:
$ npx gleam-type-graph --format text
Int --[money.from_cents]--> money.Money
String --[customer.new_id]--> customer.CustomerId
String --[order.new_id]--> order.OrderId
money.Money --[money.to_cents]--> Int
money.Money --[money.to_json]--> json.Json
order.Order --[order.order_to_json]--> json.Json
order.Order --[order.snapshot]--> order.OrderSnapshot
order.OrderSnapshot --[order.restore]--> order.OrderTwo seconds of reading and you know:
String → OrderIdandString → CustomerIdare smart constructorsOrder ↔ OrderSnapshotis a symmetric snapshot/restore pair- The path to put an
Orderon disk isOrder → snapshot → OrderSnapshot → order_to_json → Json → ...
With --domain-only the same project collapses to just the user-defined
core:
$ npx gleam-type-graph --format text --domain-only
order.Order --[order.snapshot]--> order.OrderSnapshot
order.OrderSnapshot --[order.restore]--> order.OrderWhen a function takes multiple typed parameters — say
make_order(c: CustomerId, id: OrderId, total: Money) -> Order — the
output groups the inputs to make the and-relation visible:
{customer.CustomerId, money.Money, order.OrderId} --[order.make_order]--> order.OrderIn mermaid / dot this renders as a single fan-in node that all inputs flow into and the return type flows out of, so a reader can see at a glance that all three inputs are required.
CLI
Usage: gleam-type-graph [PATH] [OPTIONS]
Arguments:
PATH Directory to scan. If omitted, walks up from cwd
looking for gleam.toml and uses its src/.
Options:
--format <FMT> Output format: mermaid|dot|json|text|html|cytoscape
(default: html when --open, mermaid otherwise)
--open Render to a temp file and open in your browser
--collapse <MODE> Collapse the graph. Modes: modules
--theme <NAME> Color palette for HTML: dark (default) or light
--include <PAT> Module substring to include (repeatable)
--exclude <PAT> Module substring to exclude (repeatable)
--domain-only Drop edges touching stdlib / primitives
-h, --help Show this help--include and --exclude match the substring against either endpoint
module or the function label, so --include order keeps every edge
involving the order module on either side.
How edges are derived
For a single-arg public function module.fn(p: T) -> R:
- One edge
T → R, labelledmodule.fn
For a multi-arg public function module.fn(p1: T1, p2: T2, ...) -> R:
- A synthetic fan-in node
module.fn()is introduced (rendered as a stadium in mermaid, an ellipse in dot,{...}braces in text). - Each typed parameter type flows into the fan-in node; the return type flows out. Reads as: "you need T1 and T2 and ... to produce R."
- Duplicate input types are deduped, so
merge(Order, Order, String) -> Ordershows{Order, String}flowing into the fan-in, with Order also flowing out as a self-cycle.
Across both cases:
Result(T, E)returns are unwrapped toT(the success path is the transformation; the error type is metadata)Option(T)returns are unwrapped toTList(T) → List(S)is unwrapped toT → S(container-preserving)
Type references are resolved through the module's import table:
import app/order.{type OrderSnapshot}—OrderSnapshotresolves toapp/order.OrderSnapshotimport app/order as o—o.OrderSnapshotresolves toapp/order.OrderSnapshotimport app/order—order.OrderSnapshotresolves the same way
Unresolvable qualified references show up as Unknown(module.Type) rather
than crashing the run.
Parameterized types
A Qualified or Primitive TypeRef can carry parameters. The identity
rule:
- If every parameter is a type variable (
Generic), the node collapses to its bare form:List(a)andList(b)and bareListare all the same node. - If any parameter is concrete, the parameterised form is the node
identity:
List(Order)andList(Customer)are distinct nodes, distinct from bareList.
So list.map(List(a), fn(a) -> b) -> List(b) connects List ↔ Fn ↔
List, but string.concat(List(String)) -> String shows up as a
distinct List(String) → String edge, and any function returning
List(Order) lives in its own corner of the graph.
Limitations (MVP scope)
- Only
src/is parsed.test/andbuild/packages/<dep>/src/aren't crawled yet. - Only public functions are included.
- Type aliases are treated as opaque —
pub type UserId = Stringshows up asUserId, notString(short alias bodies are inlined under the alias name in the HTML / cytoscape views).
Tracked next steps live in docs/todos.md and
docs/orbital-layout.md.
Development
gleam build # Compile (requires Gleam ≥ 1.x)
gleam test # Run the test suite
./bin/gleam-type-graph.mjs ./src # Run the local binary
npm pack && npx ./gleam-type-graph-*.tgz # Smoke-test the npm packageThe pipeline is small enough to read top-to-bottom:
src/type_graph.gleam # CLI + pipeline
src/type_graph/parse.gleam # walk src/, parse with `glance`
src/type_graph/extract.gleam # AST → edges, with import resolution
src/type_graph/graph.gleam # core TypeRef + Edge model
src/type_graph/filter.gleam # primitive / stdlib / pattern filters
src/type_graph/collapse.gleam # graph-level collapse (modules)
src/type_graph/theme.gleam # color palettes (dark / light)
src/type_graph/render.gleam # mermaid | dot | json | text | html | cytoscape
src/type_graph/sys.gleam # tmp file + open-in-browser
src/type_graph_ffi.mjs # JS FFI for sys
bin/gleam-type-graph.mjs # npm bin shimLicense
MIT.
