{"id":17948817,"url":"https://github.com/divvypayhq/absinthe_federation","last_synced_at":"2026-04-01T17:45:06.455Z","repository":{"id":39629756,"uuid":"377962490","full_name":"DivvyPayHQ/absinthe_federation","owner":"DivvyPayHQ","description":"Adds Apollo Federation Spec conformance to the absinthe GraphQL library","archived":false,"fork":false,"pushed_at":"2025-12-12T19:41:23.000Z","size":215,"stargazers_count":85,"open_issues_count":6,"forks_count":19,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-12-14T09:58:34.540Z","etag":null,"topics":["absinthe-graphql","apollo-federation","elixir","graphql"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DivvyPayHQ.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-06-17T21:15:54.000Z","updated_at":"2025-12-12T19:38:11.000Z","dependencies_parsed_at":"2024-04-11T17:42:10.309Z","dependency_job_id":"b144ee33-e7bd-4e5e-b5a4-5a30e1f39c41","html_url":"https://github.com/DivvyPayHQ/absinthe_federation","commit_stats":{"total_commits":66,"total_committers":9,"mean_commits":7.333333333333333,"dds":0.6515151515151515,"last_synced_commit":"f2d49086aac71d3b1c4cf13a1f275896775110f2"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"purl":"pkg:github/DivvyPayHQ/absinthe_federation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DivvyPayHQ%2Fabsinthe_federation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DivvyPayHQ%2Fabsinthe_federation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DivvyPayHQ%2Fabsinthe_federation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DivvyPayHQ%2Fabsinthe_federation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DivvyPayHQ","download_url":"https://codeload.github.com/DivvyPayHQ/absinthe_federation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DivvyPayHQ%2Fabsinthe_federation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29802866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T21:02:39.706Z","status":"ssl_error","status_checked_at":"2026-02-24T21:02:21.834Z","response_time":75,"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":["absinthe-graphql","apollo-federation","elixir","graphql"],"created_at":"2024-10-29T09:10:07.010Z","updated_at":"2026-02-24T22:03:47.660Z","avatar_url":"https://github.com/DivvyPayHQ.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Absinthe.Federation\n\n[![Build Status](https://github.com/DivvyPayHQ/absinthe_federation/workflows/CI/badge.svg)](https://github.com/DivvyPayHQ/absinthe_federation/actions?query=workflow%3ACI)\n[![Hex pm](https://img.shields.io/hexpm/v/absinthe_federation.svg)](https://hex.pm/packages/absinthe_federation)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://hexdocs.pm/absinthe_federation/)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\n[Apollo Federation](https://www.apollographql.com/docs/federation) support for [Absinthe](https://hexdocs.pm/absinthe/overview.html).\n\n## Installation\n\nInstall from [Hex](https://hex.pm/packages/absinthe_federation):\n\n```elixir\ndef deps do\n  [\n    {:absinthe_federation, \"~\u003e 0.9\"}\n  ]\nend\n```\n\nInstall a specific branch from [GitHub](https://github.com/DivvyPayHQ/absinthe_federation):\n\n```elixir\ndef deps do\n  [\n    {:absinthe_federation, github: \"DivvyPayHQ/absinthe_federation\", branch: \"main\"}\n  ]\nend\n```\n\nUse `Absinthe.Federation.Schema` module in your root schema:\n\n```elixir\ndefmodule Example.Schema do\n  use Absinthe.Schema\n+ use Absinthe.Federation.Schema\n\n  query do\n    ...\n  end\nend\n```\n\nValidate everything is wired up correctly:\n\n```bash\nmix absinthe.federation.schema.sdl --schema Example.Schema\n```\n\nYou should see the [Apollo Federation Subgraph Specification](https://www.apollographql.com/docs/federation/subgraph-spec) fields along with any fields you've defined. It can be helpful to add `*.graphql` to your `.gitignore`, at least at your projects root level, while testing your SDL output during development.\n\n## Usage (macro based schemas)\n\nThe following sticks close to the Apollo Federation documentation to better clarify how to achieve the same outcomes with the `Absinthe.Federation` module as you'd get from their JavaScript examples.\n\n### [Defining an entity](https://www.apollographql.com/docs/federation/entities#defining-an-entity)\n\n```elixir\ndefmodule Products.Schema do\n  use Absinthe.Schema\n  use Absinthe.Federation.Schema\n\n  extend schema do\n    directive(:link,\n      url: \"https://specs.apollo.dev/federation/v2.3\",\n      import: [\"@key\", ...]\n    )\n  end\n\n  object :product do\n    directive :key, fields: \"id\"\n\n    # Any subgraph contributing fields MUST define a _resolve_reference field.\n    field :_resolve_reference, :product do\n      resolve \u0026Products.find_by_id/2\n    end\n\n    field :id, non_null(:id)\n    field :name, non_null(:string)\n    field :price, :int\n  end\n\n  query do\n    ...\n  end\nend\n```\n\nYour `:_resolve_reference` must return one of the following:\n\n```elixir\n{:ok, %Product{id: id, ...}}\n```\n\n```elixir\n{:ok, %{__typename: \"Product\", id: id, ...}}\n```\n\n```elixir\n{:ok, %{\"__typename\" =\u003e \"Product\", \"id\" =\u003e id, ...}}\n```\n\n```elixir\n{:ok, nil}\n```\n\nIt is easier to just merge a subgraph's contributed fields back onto the incoming entity reference than rely on a struct to set the `__typename`.\n\n### [Contributing entity fields](https://www.apollographql.com/docs/federation/entities#contributing-entity-fields)\n\nEach subgraph, by default, must return different fields. See the Apollo documentation should you need to [override this behavior](https://www.apollographql.com/docs/federation/entities/resolve-another-subgraphs-fields).\n\n```elixir\ndefmodule Inventory.Schema do\n  use Absinthe.Schema\n  use Absinthe.Federation.Schema\n\n  extend schema do\n    directive(:link,\n      url: \"https://specs.apollo.dev/federation/v2.3\",\n      import: [\"@key\", ...]\n    )\n  end\n\n  object :product do\n    directive :key, fields: \"id\"\n\n    # In this case, only the `Inventory.Schema` should resolve the `inStock` field.\n    field :_resolve_reference, :product do\n      resolve(fn %{__typename: \"Product\", id: id} = entity, _info -\u003e\n        {:ok, Map.merge(entity, %{in_stock: true})}\n      end)\n    end\n\n    field :id, non_null(:string)\n    field :in_stock, non_null(:boolean)\n  end\n\n  query do\n    ...\n  end\nend\n```\n\n### [Referencing an entity without contributing fields](https://www.apollographql.com/docs/federation/entities#referencing-an-entity-without-contributing-fields)\n\n```elixir\ndefmodule Reviews.Schema do\n  use Absinthe.Schema\n  use Absinthe.Federation.Schema\n\n  extend schema do\n    directive(:link,\n      url: \"https://specs.apollo.dev/federation/v2.3\",\n      import: [\"@key\", ...]\n    )\n  end\n\n  # Stubbed entity, marked as unresolvable in this subgraph.\n  object :product do\n    directive :key, fields: \"id\", resolvable: false\n\n    field :id, non_null(:string)\n  end\n\n  object :review do\n    field :id, non_null(:id)\n    field :score, non_null(:int)\n    field :description, non_null(:string)\n\n    # This subgraph only needs to resolve the key fields used to reference the entity.\n    field :product, non_null(:product) do\n      resolve(fn %{product_id: id} = _parent, _args, _info -\u003e\n        {:ok, %{id: id}}\n      end)\n    end\n  end\n\n  query do\n    field :latest_reviews, non_null(list(:review)) do\n      resolve(\u0026ReviewsResolver.find_many/2)\n    end\n  end\nend\n```\n\n### Macro based schema with existing prototype\n\nIf you are already using a schema prototype.\n\n```elixir\ndefmodule Example.Schema do\n  use Absinthe.Schema\n+ use Absinthe.Federation.Schema, prototype_schema: Example.SchemaPrototype\n\n  query do\n    ...\n  end\nend\n```\n\n```elixir\ndefmodule Example.SchemaPrototype do\n  use Absinthe.Schema.Prototype\n+ use Absinthe.Federation.Schema.Prototype.FederatedDirectives\n\n  directive :my_directive do\n    on [:schema]\n  end\nend\n```\n\n### SDL based schemas (experimental)\n\n```elixir\ndefmodule Example.Schema do\n  use Absinthe.Schema\n+ use Absinthe.Federation.Schema\n\n  import_sdl \"\"\"\n    extend type Query {\n      review(id: ID!): Review\n    }\n\n    extend type Product @key(fields: \"upc\") {\n      upc: String! @external\n      reviews: [Review]\n    }\n  \"\"\"\n\n  def hydrate(_, _) do\n    ...\n  end\nend\n```\n\n### Using Dataloader in \\_resolve_reference queries\n\nYou can use Dataloader in to resolve references to specific objects, but it requires manually setting up the batch and item key, as the field has no parent. Resolution for both \\_resolve_reference fields are functionally equivalent.\n\n```elixir\ndefmodule Example.Schema do\n  use Absinthe.Schema\n  use Absinthe.Federation.Schema\n\n  import Absinthe.Resolution.Helpers, only: [on_load: 2, dataloader: 2]\n\n  def context(ctx) do\n    loader =\n      Dataloader.new()\n      |\u003e Dataloader.add_source(Example.Loader, Dataloader.Ecto.new(Example.Repo))\n\n    Map.put(ctx, :loader, loader)\n  end\n\n  def plugins do\n    [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults()\n  end\n\n  object :item do\n    key_fields(\"item_id\")\n\n    # Using the dataloader/2 resolution helper\n    field :_resolve_reference, :item do\n      resolve dataloader(Example.Loader, fn _parent, args, _res -\u003e\n                %{batch: {{:one, Example.Item}, %{}}, item: [item_id: args.item_id]}\n              end)\n    end\n  end\n\n  object :verbose_item do\n    key_fields(\"item_id\")\n\n    # Using the on_load/2 resolution helper\n    field :_resolve_reference, :verbose_item do\n      resolve fn %{item_id: id}, %{context: %{loader: loader}} -\u003e\n        batch_key = {:one, Example.Item, %{}}\n        item_key = [item_id: id]\n\n        loader\n        |\u003e Dataloader.load(Example.Loader, batch_key, item_key)\n        |\u003e on_load(fn loader -\u003e\n          result = Dataloader.get(loader, Example.Loader, batch_key, item_key)\n          {:ok, result}\n        end)\n    end\n  end\nend\n```\n\n### Resolving structs in \\_entities queries\n\nIf you need to resolve your struct to a specific type in your schema you can implement the `Absinthe.Federation.Schema.EntityUnion.Resolver` protocol like this:\n\n```elixir\ndefmodule MySchema do\n  @type t :: %__MODULE__{\n          id: String.t()\n        }\n\n  defstruct id: \"\"\n\n  defimpl Absinthe.Federation.Schema.EntityUnion.Resolver do\n    def resolve_type(_, _), do: :my_schema_object_name\n  end\nend\n```\n\n### Federation v2\n\nYou can import Apollo Federation v2 directives by extending your top-level schema with the `@link` directive.\n\n```elixir\ndefmodule Example.Schema do\n  use Absinthe.Schema\n  use Absinthe.Federation.Schema\n\n+ extend schema do\n+   directive :link,\n+     url: \"https://specs.apollo.dev/federation/v2.7\",\n+     import: [\n        \"@authenticated\",\n        \"@extends\",\n        \"@external\",\n        \"@inaccessible\",\n        \"@key\",\n        \"@override\",\n        \"@policy\",\n        \"@provides\",\n        \"@requires\",\n        \"@requiresScopes\",\n        \"@shareable\",\n        \"@tag\",\n        \"@composeDirective\",\n        \"@interfaceObject\"\n+     ]\n+ end\n\n  query do\n    ...\n  end\nend\n```\n\n### Namespacing and directive renaming with `@link`\n\n`@link` directive supports namespacing and directive renaming (only on **Absinthe \u003e= 1.7.2**) according to the specs.\n\n```elixir\ndefmodule Example.Schema do\n  use Absinthe.Schema\n  use Absinthe.Federation.Schema\n\n+ extend schema do\n+   directive :link,\n+     url: \"https://specs.apollo.dev/federation/v2.3\",\n+     import: [%{\"name\" =\u003e \"@key\", \"as\" =\u003e \"@primaryKey\"}], # directive renaming\n+     as: \"federation\" # namespacing\n+ end\n\n  query do\n    ...\n  end\nend\n```\n\n## More Documentation\n\nSee additional documentation, including guides, in the [Absinthe.Federation hexdocs](https://hexdocs.pm/absinthe_federation).\n\n## Contributing\n\nRefer to the [Contributing Guide](./CONTRIBUTING.md).\n\n## License\n\nSee [LICENSE](./LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdivvypayhq%2Fabsinthe_federation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdivvypayhq%2Fabsinthe_federation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdivvypayhq%2Fabsinthe_federation/lists"}