Software dependency graph builder on top of Yog.
Choreo.Dependency models component relationships — modules, libraries,
applications, interfaces, and tests — to help visualize and analyze coupling,
layering, and circular dependencies.
When to use
Use Choreo.Dependency when refactoring a codebase, onboarding new
developers, or enforcing architectural boundaries. It surfaces hidden
cycles, measures instability, and identifies the deepest dependency chains
that slow down builds and tests.
Node types
:application— deployable service or app:library— external or shared library:module— internal code module:interface— API, contract, or protocol definition:test— test suite or spec
Edge types
:uses— general dependency:imports— explicit import / require:calls— runtime function call:inherits— inheritance / implementation:dev— development-only dependency
Further reading
Quick Start
deps =
Choreo.Dependency.new()
|> Choreo.Dependency.add_application(:api, label: "API Gateway")
|> Choreo.Dependency.add_library(:phoenix, label: "Phoenix")
|> Choreo.Dependency.add_module(:auth, label: "Auth Module")
|> Choreo.Dependency.depends_on(:api, :phoenix, type: :uses)
|> Choreo.Dependency.depends_on(:api, :auth, type: :calls)
dot = Choreo.Dependency.to_dot(deps)Diagram
Analysis
# Find circular dependencies
Choreo.Dependency.Analysis.cyclic_dependencies(deps)
#=> [[:repo, :service, :repo]]
# Impact analysis: what breaks if :auth changes?
Choreo.Dependency.Analysis.affected_by(deps, :auth)
#=> [:api, :web]
# Check layer violations
layers = %{api: 3, web: 2, repo: 1}
Choreo.Dependency.Analysis.layer_violations(deps, layers)
#=> [{:repo, :api, "repo (layer 1) calls api (layer 3)"}]
Summary
Functions
Adds an application node (deployable service or app).
Defines a cluster for grouping nodes visually (e.g., by team or layer).
Adds an interface node (API, contract, protocol).
Adds a library node (external or shared dependency).
Adds a module node (internal code unit).
Adds a test node (test suite or spec).
Creates a dependency edge from one component to another.
Returns all dependency edges as {from, to, weight} tuples.
Returns all edges with their metadata as {from, to, weight, meta} tuples.
Creates a new empty dependency graph.
Returns all node IDs in the dependency graph.
Returns all nodes of a given type.
Returns a theme for Choreo.Dependency.
Renders the dependency graph to DOT format.
Returns the raw Yog.Graph struct underpinning the dependency graph.
Renders the dependency graph to Mermaid.js flowchart syntax.
Collapses parallel edges into a simple Graph for algorithm analysis.
Types
@type t() :: %Choreo.Dependency{ clusters: %{required(String.t()) => map()}, edge_meta: %{optional(Yog.Multi.Graph.edge_id()) => map()}, graph: Yog.Multi.Graph.t(), strict: boolean() }
Functions
Adds an application node (deployable service or app).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_application(deps, :api, label: "API")
iex> Choreo.Dependency.nodes(deps)
[:api]
iex> Map.get(deps.graph.nodes, :api).node_type
:application
iex> Map.get(deps.graph.nodes, :api).label
"API"Diagram
Defines a cluster for grouping nodes visually (e.g., by team or layer).
Options
:parent(String.t/0):label(String.t/0):style(String.t/0):fillcolor(String.t/0):color(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_cluster(deps, "core", label: "Core")
iex> deps.clusters["cluster_core"].label
"Core"
Adds an interface node (API, contract, protocol).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_interface(deps, :contract)
iex> Choreo.Dependency.nodes(deps)
[:contract]
iex> Map.get(deps.graph.nodes, :contract).node_type
:interfaceDiagram
Adds a library node (external or shared dependency).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_library(deps, :phx, label: "Phoenix")
iex> Choreo.Dependency.nodes(deps)
[:phx]
iex> Map.get(deps.graph.nodes, :phx).node_type
:libraryDiagram
Adds a module node (internal code unit).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_module(deps, :auth)
iex> Choreo.Dependency.nodes(deps)
[:auth]
iex> Map.get(deps.graph.nodes, :auth).node_type
:moduleDiagram
Adds a test node (test suite or spec).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_test(deps, :auth_test)
iex> Choreo.Dependency.nodes(deps)
[:auth_test]
iex> Map.get(deps.graph.nodes, :auth_test).node_type
:testDiagram
Creates a dependency edge from one component to another.
Direction reads as "from depends on to".
Parallel edges are supported: the same (from, to) pair can be added
multiple times with different :type values (e.g. :uses and :dev).
Options
:type:label(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> [{_, _, _, meta}] = Choreo.Dependency.edges_with_meta(deps)
iex> meta.type
:usesDiagram
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
...> |> Choreo.Dependency.depends_on(:api, :auth, type: :calls)
iex> [{_, _, _, meta}] = Choreo.Dependency.edges_with_meta(deps)
iex> meta.type
:calls
iex> meta.label
"calls"
@spec edges(t()) :: [{Yog.node_id(), Yog.node_id(), integer()}]
Returns all dependency edges as {from, to, weight} tuples.
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> Choreo.Dependency.edges(deps)
[{:api, :auth, 1}]
@spec edges_with_meta(t()) :: [{Yog.node_id(), Yog.node_id(), any(), map()}]
Returns all edges with their metadata as {from, to, weight, meta} tuples.
Creates a new empty dependency graph.
Dependency graphs are always directed.
Options
:strict— iftrue,depends_on/4raises when an endpoint does not already exist. Defaultfalse(auto-creates missing nodes as:module).
Examples
iex> deps = Choreo.Dependency.new()
iex> Choreo.Dependency.nodes(deps)
[]
iex> Choreo.Dependency.edges(deps)
[]
@spec nodes(t()) :: [Yog.node_id()]
Returns all node IDs in the dependency graph.
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
iex> Enum.sort(Choreo.Dependency.nodes(deps))
[:api, :auth]
@spec nodes_of_type(t(), atom()) :: [Yog.node_id()]
Returns all nodes of a given type.
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:a)
...> |> Choreo.Dependency.add_application(:b)
...> |> Choreo.Dependency.add_library(:c)
iex> Enum.sort(Choreo.Dependency.nodes_of_type(deps, :application))
[:a, :b]
iex> Choreo.Dependency.nodes_of_type(deps, :library)
[:c]
iex> Choreo.Dependency.nodes_of_type(deps, :module)
[]
@spec theme( atom(), keyword() ) :: Choreo.Theme.t()
Returns a theme for Choreo.Dependency.
Examples
iex> theme = Choreo.Dependency.theme(:default, graph_rankdir: :lr)
iex> theme.graph_rankdir
:lr
Renders the dependency graph to DOT format.
Options
:theme—:default,:dark, or aChoreo.Themestruct
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api, label: "API")
...> |> Choreo.Dependency.add_module(:auth, label: "Auth")
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> dot = Choreo.Dependency.to_dot(deps)
iex> String.contains?(dot, "digraph")
true
iex> String.contains?(dot, "API")
true
iex> String.contains?(dot, "Auth")
true
Returns the raw Yog.Graph struct underpinning the dependency graph.
Examples
iex> deps = Choreo.Dependency.new()
iex> graph = Choreo.Dependency.to_graph(deps)
iex> graph.kind
:directed
Renders the dependency graph to Mermaid.js flowchart syntax.
Options
:theme—:default,:dark,:warm,:forest,:ocean, or aChoreo.Themestruct:direction—:td(default),:lr,:bt,:rl
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api, label: "API")
...> |> Choreo.Dependency.add_module(:auth, label: "Auth")
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> mermaid = Choreo.Dependency.to_mermaid(deps)
iex> String.contains?(mermaid, "graph TD")
true
@spec to_simple_graph( t(), keyword() ) :: Yog.Graph.t()
Collapses parallel edges into a simple Graph for algorithm analysis.