{"id":13550519,"url":"https://github.com/factor18/flo","last_synced_at":"2026-01-17T12:33:30.395Z","repository":{"id":49127623,"uuid":"155968250","full_name":"factor18/flo","owner":"factor18","description":"Flow based programming for elixir","archived":false,"fork":false,"pushed_at":"2021-07-15T20:12:43.000Z","size":104,"stargazers_count":44,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-14T07:31:31.317Z","etag":null,"topics":["elixir","fbp","flow-based-programming","workflow-engine"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/factor18.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-03T09:24:30.000Z","updated_at":"2025-07-01T06:51:15.000Z","dependencies_parsed_at":"2022-08-22T15:50:41.807Z","dependency_job_id":null,"html_url":"https://github.com/factor18/flo","commit_stats":null,"previous_names":["sarat1669/virta"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/factor18/flo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factor18%2Fflo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factor18%2Fflo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factor18%2Fflo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factor18%2Fflo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/factor18","download_url":"https://codeload.github.com/factor18/flo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factor18%2Fflo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28508464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T11:50:55.898Z","status":"ssl_error","status_checked_at":"2026-01-17T11:50:55.569Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","fbp","flow-based-programming","workflow-engine"],"created_at":"2024-08-01T12:01:34.272Z","updated_at":"2026-01-17T12:33:30.343Z","avatar_url":"https://github.com/factor18.png","language":"Elixir","readme":"# Flo\nExtensible workflow orchestration framework\n\n### Installation\n\nThe package can be installed by adding `flo` to your list of dependencies in mix.exs:\n\n```elixir\ndef deps do\n  [{:flo, \"~\u003e 0.2\"}]\nend\n```\n\nAdd to list of applications\n\n```elixir\nextra_applications: [:logger, :flo]\n```\n\n### Flo Framework\nFlo exposes two [behaviours](https://elixir-lang.org/getting-started/typespecs-and-behaviours.html#behaviours) which can be used to create your own triggers and components\n- `Component` is a behaviour for exposing common application logic in a reusable manner. Think of this as a function, such as write to database, publish to Kafka, etc that can be used by all Flo apps\n- `Trigger` is a behaviour for building event-consumers that trigger workflows. The Kafka subscriber is an example of a trigger\n\n \n \n#### Workflow is a combination of Trigger(s) and Components\n\n- Triggers\n  - Invokes the workflow\n  - Can be invoked on its own (interval, cron etc) or by external events (http, queues etc)\n- Components:\n  - Definition of a task\n  - Have a common interface which allows them to be interconnected\n- Connections:\n  - Defines the order of invokation\n  - Can be conditional\n  - Allows branching and merging\n\n#### Component\nHere is the implementation of a component which returns a dog pic of a given breed\n\n`@name` and `@scope` are used for referencing this component in a workflow\n\n`@inports` are a list of properties which can be consumed by the component\n\n`@outports` defines the responses from this component, which can be consumed by other components\n\n```elixir\ndefmodule Flo.Core.Component.Dog do\n  alias Flo.{Port, Context, Outports}\n\n  @name \"dog\"\n\n  @scope \"core\"\n\n  @inports [\n    %Port{required: true, name: \"breed\", schema: %{\"type\" =\u003e \"string\"}}\n  ]\n\n  @outports %Outports{\n    default: [\n      %Port{name: \"url\", required: true, schema: %{\"type\" =\u003e \"string\"}}\n    ],\n    additional: %{\n      \"error\" =\u003e [\n        %Port{name: \"message\", required: true, schema: %{\"type\" =\u003e \"string\"}}\n      ]\n    }\n  }\n\n  use Flo.Component\n\n  @impl true\n  def run(%Context.Element{inports: inports}) do\n    breed = inports |\u003e Map.get(\"breed\")\n\n    url = \"https://dog.ceo/api/breed/#{breed}/images/random/1\"\n\n    case HTTPoison.get(url) do\n      {:ok, %{status_code: 200, body: body}} -\u003e\n        url = Jason.decode!(body) |\u003e Map.get(\"message\") |\u003e Enum.at(0)\n        %Context.Outports{outcome: \"default\", value: %{\"url\" =\u003e url}}\n      _ -\u003e\n        %Context.Outports{outcome: \"error\", value: %{\"message\" =\u003e \"No dog :-(\"}}\n    end\n  end\nend\n\n```\nNow this component can be used in all of your workflows\n\n#### Trigger\nHere is the implementation of a trigger\n`@name` and `@scope` are used for referencing this component in a workflow\n\n`@configs` are a list of configs which can be consumed by the component\n\n`@outports` defines the responses from this component, which can be consumed by other components\n\n`initialize` function will be invoked with a callback function `start`\n\nThe `start` function will receive the outports map and will start the workflow whenever called\n\n`Flo.Trigger` uses a `GenServer` behind the scenes, the return of `initialize` will be the return of GenServer's `init` callback\n\n```elixir\ndefmodule Flo.Core.Trigger.AMQP do\n  alias Flo.{Port, Context, Outports}\n\n  @name \"amqp\"\n\n  @scope \"core\"\n\n  @configs [\n    %Port{\n      name: \"queue\",\n      required: true,\n      schema: %{\"type\" =\u003e \"string\"}\n    },\n    %Port{\n      required: true,\n      name: \"connection_string\",\n      schema: %{\"type\" =\u003e \"string\"}\n    }\n  ]\n\n  @outports %Outports{\n    default: [\n      %Port{\n        required: true,\n        name: \"payload\",\n        schema: %{\"type\" =\u003e \"string\"}\n      }\n    ]\n  }\n\n  use Flo.Trigger\n\n  @impl true\n  def initialize(%Context.Stimulus{configs: configs}, start) do\n    queue = configs |\u003e Map.get(\"queue\")\n    connection_string = configs |\u003e Map.get(\"connection_string\")\n\n    {:ok, conn} = AMQP.Connection.open(connection_string)\n    {:ok, chan} = Channel.open(conn)\n\n    AMQP.Queue.subscribe(chan, queue, fn payload, _meta -\u003e\n      start.(%{\"payload\" =\u003e payload})\n    end)\n\n    {:ok, %{start: start}}\n  end\nend\n\n```\n\n#### Workflow\n![Image of Workflow](https://user-images.githubusercontent.com/11179580/123558812-dfe03f80-d7b5-11eb-8117-800168b87d15.png)\n\nHere is a sample which implements the above workflow\n\n`stimuli` are the list of triggers\n\n`elements` are the list of components\n\n`connections` form the flow between components\n\n```elixir\n%Flo.Workflow{\n  name: \"sample\",\n  description: \"Sample Flow\",\n  stimuli: [\n    %Flo.Stimulus{\n      ref: \"interval-trigger\",\n      inports: %{},\n      scope: \"core\",\n      name: \"interval\",\n      configs: %{\n        \"delay\" =\u003e %Flo.Script{language: Flo.Script.Language.vanilla(), source: 5000}\n      }\n    }\n  ],\n  elements: [\n    %Flo.Element{\n      ref: \"dog-api\",\n      name: \"dog\",\n      scope: \"core\",\n      inports: %{\n        \"breed\" =\u003e %Flo.Script{\n          source: \"shihtzu\",\n          language: Flo.Script.Language.vanilla(),\n        }\n      }\n    },\n    %Flo.Element{\n      ref: \"delay\",\n      name: \"delay\",\n      scope: \"core\",\n      inports: %{\n        \"delay\" =\u003e %Flo.Script{\n          source: 1500,\n          language: Flo.Script.Language.vanilla(),\n        }\n      }\n    },\n    %Flo.Element{\n      ref: \"url-log\",\n      name: \"log\",\n      scope: \"core\",\n      inports: %{\n        \"delay\" =\u003e %Flo.Script{\n          source: \"Check the photo {{elements.dog-api.outports.url.value}}\",\n          language: Flo.Script.Language.liquid(),\n        }\n      }\n    },\n    %Flo.Element{\n      ref: \"error-log\",\n      name: \"log\",\n      scope: \"core\",\n      inports: %{\n        \"delay\" =\u003e %Flo.Script{\n          source: \"Error occured: {{elements.dog-api.outports.error.message.value}}\",\n          language: Flo.Script.Language.liquid(),\n        }\n      }\n    },\n    %Flo.Element{\n      ref: \"end-log\",\n      name: \"log\",\n      scope: \"core\",\n      inports: %{\n        \"delay\" =\u003e %Flo.Script{\n          source: \"Done!!\",\n          language: Flo.Script.Language.vanilla(),\n        }\n      }\n    }\n  ],\n  connections: [\n    %Flo.Connection{\n      source: \"dog-api\",\n      outcome: \"default\",\n      destination: \"delay\",\n    },\n    %Flo.Connection{\n      source: \"delay\",\n      outcome: \"default\",\n      destination: \"url-log\",\n    },\n    %Flo.Connection{\n      source: \"dog-api\",\n      outcome: \"error\",\n      destination: \"error-log\",\n    },\n    %Flo.Connection{\n      source: \"url-log\",\n      outcome: \"default\",\n      destination: \"end-log\",\n    },\n    %Flo.Connection{\n      source: \"error-log\",\n      outcome: \"default\",\n      destination: \"end-log\",\n    }\n  ]\n}\n|\u003e Flo.WorkflowRegistry.register()\n```\n\nA component will be executed when all of the incoming connections are resolved\n\nA connection can be in three states `INITIAL`, `RESOLVED`, `DISABLED`\n\nIf there are multiple connections to the component, the component will be executed only when all of the connections are in `RESOLVED` and `DISABLED` state and at least one connection should be in resolved state\n\nIf a connection is `DISABLED`, it will recursively disable all connections till a component is found which has a connection still in `INITIAL` or `RESOLVED` state\n\n### TODO\n- [x] Workflow execution\n- [x] Branching and merging\n- [x] Conditional branching\n- [x] Multiple outcomes for components\n- [ ] Loops\n- [ ] Visual Editor\n- [ ] Sub flows\n- [ ] Error handling\n- [ ] Documentation\n\n### Contributing\nRequest a new feature by creating an issue or create a pull request with new features or fixes.\n\n### License\n`Flo` source code is released under Mozilla Public License 2.0.\nCheck [LICENSE](https://github.com/factor18/flo/blob/main/LICENSE) file for more information\n","funding_links":[],"categories":["Elixir","elixir"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffactor18%2Fflo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffactor18%2Fflo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffactor18%2Fflo/lists"}