{"id":22840563,"url":"https://github.com/shouya/ecto-tx","last_synced_at":"2025-04-28T10:49:58.477Z","repository":{"id":62430856,"uuid":"531830864","full_name":"shouya/ecto-tx","owner":"shouya","description":"Composable transaction as an alternative to Ecto.Multi","archived":false,"fork":false,"pushed_at":"2023-03-02T07:02:04.000Z","size":34,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-20T08:58:23.810Z","etag":null,"topics":["database","ecto","elixir","transaction"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/tx","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shouya.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.org","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":"2022-09-02T07:45:37.000Z","updated_at":"2023-06-27T08:08:02.000Z","dependencies_parsed_at":"2023-02-01T01:55:17.480Z","dependency_job_id":null,"html_url":"https://github.com/shouya/ecto-tx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shouya%2Fecto-tx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shouya%2Fecto-tx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shouya%2Fecto-tx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shouya%2Fecto-tx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shouya","download_url":"https://codeload.github.com/shouya/ecto-tx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251299568,"owners_count":21567247,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["database","ecto","elixir","transaction"],"created_at":"2024-12-13T01:12:39.973Z","updated_at":"2025-04-28T10:49:58.456Z","avatar_url":"https://github.com/shouya.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tx\n\nComposable transaction as an alternative to Ecto.Multi.\n\n- Hex.pm: https://hex.pm/packages/tx\n- API references: https://hexdocs.pm/tx\n\n## Examples\n\n``` elixir\ndefmodule Foo do\n  import Tx.Macro\n\n  @spec create_post_tx(map()) :: Tx.t(Post.t())\n  def create_post_tx(params) do\n\n    # DB operations can be used in a transaction.\n    tx repo do\n      repo.insert(Post.changeset(params))\n    end\n  end\n\n  @spec create_comment_tx(Post.t(), map()) :: Tx.t(Comment.t())\n  def create_comment_tx(post, params) do\n    params = Map.put(params, :post_id, post.id)\n\n    # Ecto.Multi operation can be used directly within transaction.\n    tx do\n      {:ok, %{comment: comment}} \u003c-\n        Multi.insert(Multi.new(), :comment, changeset(params))\n\n      {:ok, comment}\n    end\n  end\n\n  @spec create_dummy_post_tx([map()]) :: Tx.t(map())\n  def create_dummy_post_tx(comments) do\n    # You can compose multiple transactions freely, without\n    # worrying about name collision.\n\n    tx do\n      {:ok, post} \u003c- create_post_tx(%{content: \"Hello\"})\n      # Tx.concat turns [Tx.t(a)] into Tx.t([a]).\n      {:ok, comments} \u003c- comments\n                           |\u003e Enum.map(\u0026create_comment_tx(post, \u00261))\n                           |\u003e Tx.concat()\n\n      response = %{\n        id: post.id,\n        comment_ids: Enum.map(comments, \u0026 \u00261.d)\n      }\n\n      {:ok, response}\n    end\n  end\n\n  @spec actually_create_post() :: map() | no_return()\n  def actually_create_post do\n    comments = [\n      %{content: \"first comment\"},\n      %{content: \"second comment\"},\n      %{content: \"third comment\"}\n    ]\n\n    # Tx.execute/2 is equivalent to the Repo.transaction/1 callback.\n    case Tx.execute(create_dummy_post_tx(comments), Repo) do\n      {:ok, post} -\u003e post\n      {:error, changeset} -\u003e raise RuntimeError\n    end\n  end\nend\n```\n\n## Implementation detail\n\nInternally, a `Tx.t(a)` is defined as a closure with type `Ecto.Repo -\u003e {:ok, a} | {:error, any}`.\n\nComposing two transactions is then equivalent to the Monad \"bind\" operation (See `Tx.and_then/2`):\n\n``` elixir\n@spec and_then(Tx.t(a), (a -\u003e Tx.t(b))) :: Tx.t(b)\ndef and_then(ta, tb) do\n  fn repo -\u003e\n    ta_result \u003c- ta.(repo)\n    tb.(ta_result).(repo)\n  end\nend\n```\n\nThe `tx` macro is used to strip out most of the boilerplate. For\nexample,\n\n``` elixir\ntx do\n  {:ok, a} \u003c- foo()\n  b \u003c- bar(a)\n  c = d\n  {:ok, {a, b, c}}\nend\n```\n\nis equivalent to:\n\n``` elixir\nfn repo -\u003e\n  with {:ok, a} \u003c- foo().(repo)\n       b \u003c- bar(a).(repo)\n       c = d do\n    {:ok, {a, b, c}}\n  end\nend\n```\n\nOn top of that, `Tx` implements adaptation to use Ecto.Multi as a\n`Tx.t(%{name =\u003e result})` by executing the `Ecto.Multi` via a\nnested transaction.\n\n## Installation\n\nThe package can be installed by adding `tx` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:tx, \"~\u003e 0.1.1\"}\n  ]\nend\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshouya%2Fecto-tx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshouya%2Fecto-tx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshouya%2Fecto-tx/lists"}