{"id":13879756,"url":"https://github.com/anycable/graphql-anycable","last_synced_at":"2026-05-14T22:05:17.229Z","repository":{"id":43258118,"uuid":"146111564","full_name":"anycable/graphql-anycable","owner":"anycable","description":"A drop-in replacement for GraphQL ActionCable subscriptions. Works with AnyCable.","archived":false,"fork":false,"pushed_at":"2026-04-02T16:21:12.000Z","size":199,"stargazers_count":114,"open_issues_count":12,"forks_count":19,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-05-09T01:13:33.668Z","etag":null,"topics":["anycable","graphql","hacktoberfest","rails","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/anycable.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"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":"2018-08-25T16:31:05.000Z","updated_at":"2026-04-02T16:21:16.000Z","dependencies_parsed_at":"2024-01-04T10:42:25.206Z","dependency_job_id":"dfe4f0b1-27e2-4186-831e-585282c698f7","html_url":"https://github.com/anycable/graphql-anycable","commit_stats":null,"previous_names":["envek/graphql-anycable"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/anycable/graphql-anycable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fgraphql-anycable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fgraphql-anycable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fgraphql-anycable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fgraphql-anycable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anycable","download_url":"https://codeload.github.com/anycable/graphql-anycable/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fgraphql-anycable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33045149,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["anycable","graphql","hacktoberfest","rails","ruby"],"created_at":"2024-08-06T08:02:31.710Z","updated_at":"2026-05-14T22:05:17.210Z","avatar_url":"https://github.com/anycable.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# GraphQL subscriptions for AnyCable\n\nA (mostly) drop-in replacement for default ActionCable subscriptions adapter shipped with [graphql gem] but works with [AnyCable]!\n\n[![Gem Version](https://badge.fury.io/rb/graphql-anycable.svg)](https://badge.fury.io/rb/graphql-anycable)\n[![Tests](https://github.com/anycable/graphql-anycable/actions/workflows/test.yml/badge.svg)](https://github.com/anycable/graphql-anycable/actions/workflows/test.yml)\n\n\u003ca href=\"https://evilmartians.com/?utm_source=graphql-anycable\u0026utm_campaign=project_page\"\u003e\n\u003cimg src=\"https://evilmartians.com/badges/sponsored-by-evil-martians.svg\" alt=\"Sponsored by Evil Martians\" width=\"236\" height=\"54\"\u003e\n\u003c/a\u003e\n\n## Why?\n\nAnyCable is fast because it does not execute any Ruby code. But default subscription implementation shipped with [graphql gem] requires to do exactly that: re-evaluate GraphQL queries in Action Cable process. AnyCable doesn't support this (it's possible but hard to implement).\n\nSee https://github.com/anycable/anycable-rails/issues/40 for more details and discussion.\n\n## Differences\n\n- Subscription information is stored in a Redis database. By default, we use AnyCable Redis configuration. Expiration or data cleanup should be configured separately (see below).\n- GraphQL queries for all subscriptions are re-executed in the process that triggers event (it may be web server, async jobs, rake tasks or whatever)\n\n## Compatibility\n\n- Works with Action Cable (e.g., in development/test)\n- Works without Rails (e.g., via [LiteCable][])\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"graphql-anycable\", \"~\u003e 1.0\"\n```\n\nAnd then execute:\n\n```sh\nbundle install\n```\n\n## Usage\n\n 1. Plug it into the schema (replace the Action Cable adapter if you have one):\n\n    ```ruby\n    class MySchema \u003c GraphQL::Schema\n      use GraphQL::AnyCable, broadcast: true\n\n      subscription SubscriptionType\n    end\n    ```\n\n 2. Execute a query within an Action Cable/LiteCable channel.\n\n    ```ruby\n    class GraphqlChannel \u003c ApplicationCable::Channel\n      def execute(data)\n        result =\n          MySchema.execute(\n            query: data[\"query\"],\n            context: context,\n            variables: Hash(data[\"variables\"]),\n            operation_name: data[\"operationName\"],\n          )\n\n        transmit(\n          result: result.subscription? ? { data: nil } : result.to_h,\n          more: result.subscription?,\n        )\n      end\n\n      def unsubscribed\n        MySchema.subscriptions.delete_channel_subscriptions(self)\n      end\n\n      private\n\n      def context\n        {\n          account_id: account\u0026.id,\n          channel: self,\n        }\n      end\n    end\n    ```\n\n    Make sure that you're passing channel instance as `channel` key to the context.\n\n 3. Trigger events as usual:\n\n    ```ruby\n    MySchema.subscriptions.trigger(:product_updated, {}, Product.first!, scope: account.id)\n    ```\n\n 4. (Optional) When using other AnyCable broadcasting adapters than Redis, you MUST configure Redis for graphql-anycable yourself:\n\n    ```ruby\n    GraphQL::AnyCable.redis = Redis.new(url: ENV[\"REDIS_URL\"])\n\n    # you can also use a Proc (e.g., if you want to use a connection pool)\n    redis_pool = ConnectionPool.new(size: 10) { Redis.new(url: ENV[\"REDIS_URL\"]) }\n\n    GraphQL::AnyCable.redis = -\u003e(\u0026block) { redis_pool.with { |conn| block.call(conn) } }\n    ```\n\n## Broadcasting\n\nBy default, graphql-anycable evaluates queries and transmits results for every subscription client individually. Of course, it is a waste of resources if you have hundreds or thousands clients subscribed to the same data (and has huge negative impact on performance).\n\nThankfully, GraphQL-Ruby has added [Subscriptions Broadcast](https://graphql-ruby.org/subscriptions/broadcast.html) feature that allows to group exact same subscriptions, execute them and transmit results only once.\n\nTo enable this feature, pass the `broadcast` option set to `true` to graphql-anycable.\n\nBy default all fields are marked as _not safe for broadcasting_. If a subscription has at least one non-broadcastable field in its query, GraphQL-Ruby will execute every subscription for every client independently. If you sure that all your fields are safe to be broadcasted, you can pass `default_broadcastable` option set to `true` (but be aware that it can have security impllications!)\n\n```ruby\nclass MySchema \u003c GraphQL::Schema\n  use GraphQL::AnyCable, broadcast: true, default_broadcastable: true\n\n  subscription SubscriptionType\nend\n```\n\nSee GraphQL-Ruby [broadcasting docs](https://graphql-ruby.org/subscriptions/broadcast.html) for more details.\n\n## Operations\n\nTo avoid filling Redis storage with stale subscription data:\n\n 1. Set `subscription_expiration_seconds` setting to number of seconds (e.g. `604800` for 1 week). See [configuration](#configuration) section below for details.\n\n 2. Execute `rake graphql:anycable:clean` once in a while to clean up stale subscription data.\n\n    Heroku users should set up `use_redis_object_on_cleanup` setting to `false` due to [limitations in Heroku Redis](https://devcenter.heroku.com/articles/heroku-redis#connection-permissions).\n\n## Configuration\n\nGraphQL-AnyCable uses [anyway_config] to configure itself. There are several possibilities to configure this gem:\n\n 1. Environment variables:\n\n    ```.env\n    GRAPHQL_ANYCABLE_SUBSCRIPTION_EXPIRATION_SECONDS=604800\n    GRAPHQL_ANYCABLE_USE_REDIS_OBJECT_ON_CLEANUP=true\n    GRAPHQL_ANYCABLE_REDIS_PREFIX=graphql\n    ```\n\n 2. YAML configuration files (note that this is `config/graphql_anycable.yml`, *not* `config/anycable.yml`):\n\n    ```yaml\n    # config/graphql_anycable.yml\n    production:\n      subscription_expiration_seconds: 300 # 5 minutes\n      use_redis_object_on_cleanup: false # For restricted redis installations\n      redis_prefix: graphql # You can configure redis_prefix for anycable-graphql subscription prefixes. Default value \"graphql\"\n    ```\n\n 3. Configuration from your application code:\n\n    ```ruby\n    GraphQL::AnyCable.configure do |config|\n      config.subscription_expiration_seconds = 3600 # 1 hour\n      config.redis_prefix = \"graphql\" # on our side, we add `-` ourselves after the redis_prefix\n    end\n    ```\n\nAnd any other way provided by [anyway_config]. Check its documentation!\n\n## Emergency actions\n\nIn situations when you don't set `subscription_expiration_seconds`, have a lot of inactive subscriptions, and `GraphQL::AnyCable::Cleaner` does`t help in that,\nyou can do the following actions for clearing subscriptions\n\n1. Set `config.subscription_expiration_seconds`. After that, the new subscriptions will have `TTL`\n\n2. Run the script\n\n```ruby\nconfig = GraphQL::AnyCable.config\n\nGraphQL::AnyCable.with_redis do |redis|\n  # do it for subscriptions\n  redis.scan_each(\"graphql-subscription:*\") do |key|\n    redis.expire(key, config.subscription_expiration_seconds) if redis.ttl(key) \u003c 0\n    # or you can just remove it immediately\n    # redis.del(key) if redis.ttl(key) \u003c 0\n  end\n\n  # do it for channels\n  redis.scan_each(\"graphql-channel:*\") do |key|\n    redis.expire(key, config.subscription_expiration_seconds) if redis.ttl(key) \u003c 0\n    # or you can just remove it immediately\n    # redis.del(key) if redis.ttl(key) \u003c 0\n  end\nend\n```\n\nOr you can change the `redis_prefix` in the `configuration` and then remove all records with the old_prefix. For instance:\n\n1. Change the `redis_prefix`. The default `redis_prefix` is `graphql`.\n\n2. Run the ruby script, which remove all records with `old prefix`:\n\n```ruby\nGraphQL::AnyCable.with_redis do |redis|\n  redis.scan_each(\"graphql-*\") do |key|\n    redis.del(key)\n  end\nend\n```\n\n## Data model\n\nAs in AnyCable there is no place to store subscription data in-memory, it should be persisted somewhere to be retrieved on `GraphQLSchema.subscriptions.trigger` and sent to subscribed clients. `graphql-anycable` uses the same Redis database as AnyCable itself.\n\n 1. Grouped event subscriptions: `graphql-fingerprints:#{event.topic}` sorted set. Used to find all subscriptions on `GraphQLSchema.subscriptions.trigger`.\n\n    ```sh\n    ZREVRANGE graphql-fingerprints:1:myStats: 0 -1\n    =\u003e 1:myStats:/MyStats/fBDZmJU1UGTorQWvOyUeaHVwUxJ3T9SEqnetj6SKGXc=/0/RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o=\n    ```\n\n 2. Event subscriptions: `graphql-subscriptions:#{event.fingerptint}` set containing identifiers for all subscriptions for given operation with certain context and arguments (serialized in _topic_). Fingerprints are already scoped by topic.\n\n    ```sh\n    SMEMBERS graphql-subscriptions:1:myStats:/MyStats/fBDZmJU1UGTorQWvOyUeaHVwUxJ3T9SEqnetj6SKGXc=/0/RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o=\n    =\u003e 52ee8d65-275e-4d22-94af-313129116388\n    ```\n\n 3. Subscription data: `graphql-subscription:#{subscription_id}` hash contains everything required to evaluate subscription on trigger and create data for client.\n\n    ```sh\n    HGETALL graphql-subscription:52ee8d65-275e-4d22-94af-313129116388\n    =\u003e {\n      context:        '{\"user_id\":1,\"user\":{\"__gid__\":\"Z2lkOi8vZWJheS1tYWcyL1VzZXIvMQ\"}}',\n      variables:      '{}',\n      operation_name: 'MyStats'\n      query_string:   'subscription MyStats { myStatsUpdated { completed total processed __typename } }',\n    }\n    ```\n\n 4. Channel subscriptions: `graphql-channel:#{subscription_id}` set containing identifiers for subscriptions created in ActionCable channel to delete them on client disconnect.\n\n    ```sh\n    SMEMBERS graphql-channel:17420c6ed9e\n    =\u003e 52ee8d65-275e-4d22-94af-313129116388\n    ```\n\n## Stats\n\nYou can grab Redis subscription statistics by calling:\n\n```ruby\nGraphQL::AnyCable.stats\n```\n\nIt will return a total of the amount of the key with the following prefixes:\n\n```txt\ngraphql-subscription\ngraphql-fingerprints\ngraphql-subscriptions\ngraphql-channel\n```\n\nThe response will look like this:\n\n```json\n  {\n    \"total\": {\n      \"subscription\":22646,\n      \"fingerprints\":3200,\n      \"subscriptions\":20101,\n      \"channel\": 4900\n    }\n  }\n```\n\nYou can also grab the number of subscribers grouped by subscriptions:\n\n```ruby\nGraphQL::AnyCable.stats(include_subscriptions: true)\n```\n\nIt will return the response that contains `subscriptions`:\n\n```json\n  {\n    \"total\": {\n      \"subscription\":22646,\n      \"fingerprints\":3200,\n      \"subscriptions\":20101,\n      \"channel\": 4900\n    },\n    \"subscriptions\": {\n      \"productCreated\": 11323,\n      \"productUpdated\": 11323\n    }\n  }\n```\n\nAlso, you can set another `scan_count`, if needed. The default value is 1_000:\n\n```ruby\nGraphQL::AnyCable.stats(scan_count: 100)\n```\n\nWe can set statistics data to [Yabeda][] for tracking amount of subscriptions:\n\n```ruby\n# config/initializers/metrics.rb\nYabeda.configure do\n  group :graphql_anycable_statistics do\n    gauge :subscriptions_count,  comment: \"Number of graphql-anycable subscriptions\"\n  end\nend\n```\n\n```ruby\n# in your app\nstatistics = GraphQL::AnyCable.stats[:total]\n\nstatistics.each do |key , value|\n  Yabeda.graphql_anycable_statistics.subscriptions_count.set({name: key}, value)\nend\n```\n\nOr you can use `collect`:\n\n```ruby\n# config/initializers/metrics.rb\nYabeda.configure do\n  group :graphql_anycable_statistics do\n    gauge :subscriptions_count,  comment: \"Number of graphql-anycable subscriptions\"\n  end\n\n  collect do\n    statistics = GraphQL::AnyCable.stats[:total]\n\n    statistics.each do |redis_prefix, value|\n      graphql_anycable_statistics.subscriptions_count.set({name: redis_prefix}, value)\n    end\n  end\nend\n```\n\n## Testing applications which use `graphql-anycable`\n\nYou can pass custom redis-server URL to AnyCable using ENV variable.\n\n```bash\nREDIS_URL=redis://localhost:6379/5 bundle exec rspec\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n### Releasing new versions\n\n1. Bump version number in `lib/graphql/anycable/version.rb`\n\n   In case of pre-releases keep in mind [rubygems/rubygems#3086](https://github.com/rubygems/rubygems/issues/3086) and check version with command like `Gem::Version.new(AfterCommitEverywhere::VERSION).to_s`\n\n2. Fill `CHANGELOG.md` with missing changes, add header with version and date.\n\n3. Make a commit:\n\n   ```sh\n   git add lib/graphql/anycable/version.rb CHANGELOG.md\n   version=$(ruby -r ./lib/graphql/anycable/version.rb -e \"puts Gem::Version.new(GraphQL::AnyCable::VERSION)\")\n   git commit --message=\"${version}: \" --edit\n   ```\n\n4. Create annotated tag:\n\n   ```sh\n   git tag v${version} --annotate --message=\"${version}: \" --edit --sign\n   ```\n\n5. Fill version name into subject line and (optionally) some description (list of changes will be taken from `CHANGELOG.md` and appended automatically)\n\n6. Push it:\n\n   ```sh\n   git push --follow-tags\n   ```\n\n7. GitHub Actions will create a new release, build and push gem into [rubygems.org](https://rubygems.org)! You're done!\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/Envek/graphql-anycable.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n[graphql gem]: https://github.com/rmosolgo/graphql-ruby \"Ruby implementation of GraphQL\"\n[AnyCable]: https://github.com/anycable/anycable \"Polyglot replacement for Ruby WebSocket servers with Action Cable protocol\"\n[LiteCable]: https://github.com/palkan/litecable \"Lightweight Action Cable implementation (Rails-free)\"\n[anyway_config]: https://github.com/palkan/anyway_config \"Ruby libraries and applications configuration on steroids!\"\n[Yabeda]: https://github.com/yabeda-rb/yabeda \"Extendable solution for easy setup of monitoring in your Ruby apps\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanycable%2Fgraphql-anycable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanycable%2Fgraphql-anycable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanycable%2Fgraphql-anycable/lists"}