Conversation
cf0bb48 to
9d9e2df
Compare
Summary of ChangesHello @enisdenjo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces comprehensive support for GraphQL subscriptions within the router. It fundamentally changes how the router handles long-lived connections and streaming data, moving from a single-response model to one that can manage continuous data flows. This enables real-time data updates for clients and subgraphs, complete with dynamic protocol negotiation and on-the-fly entity resolution for each event in a subscription stream. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant new functionality for GraphQL subscriptions, including support for SSE and multipart protocols, entity resolution for subscription events, and updates to the query planner. The overall architecture is well-thought-out, especially the separation of concerns with QueryPlanExecutionResult and the use of an owned context for long-lived subscriptions.
My review has identified a critical safety issue regarding an unsafe block that could lead to a use-after-free bug. I've also found several areas where manual JSON construction is used, which is brittle and should be replaced with safer serialization. Additionally, there are opportunities to improve performance in the stream handling logic by reducing string allocations, and a potential panic in the benchmark code. Please see the detailed comments for suggestions on how to address these points.
lib/executor/src/execution/plan.rs
Outdated
| ) -> Result<PlanExecutionOutput, PlanExecutionError> { | ||
| // Clone initial_data to make it 'static for ExecutionContext | ||
| // SAFETY: We're creating a new owned value that will be used within this function | ||
| let owned_data: Value<'exec> = unsafe { std::mem::transmute(initial_data.clone()) }; |
There was a problem hiding this comment.
The use of unsafe and mem::transmute here to extend the lifetime of initial_data is dangerous and likely unsound. The initial_data is a Value<'_> that borrows from response_body, which is only valid for the current loop iteration. Because execute_plan_with_initial_data is async, the future it returns can be suspended and resumed after response_body is no longer valid, leading to a use-after-free.
To fix this, you should create a fully owned Value<'static>. You can do this by implementing a helper function that recursively clones the Value data, converting any borrowed Cows to owned ones. This avoids the need for unsafe code.
For example, you could create a function fn to_owned_value<'a>(v: &Value<'a>) -> Value<'static> and use it like let owned_data: Value<'exec> = to_owned_value(&initial_data);.
✅
|
|
🐋 This PR was built and pushed to the following Docker images: Image Names: Platforms: Image Tags: Docker metadata{
"buildx.build.provenance/linux/amd64": {
"builder": {
"id": "https://github.com/graphql-hive/router/actions/runs/22363476760/attempts/1"
},
"buildType": "https://mobyproject.org/buildkit@v1",
"materials": [
{
"uri": "pkg:docker/docker/dockerfile@1.21",
"digest": {
"sha256": "27f9262d43452075f3c410287a2c43f5ef1bf7ec2bb06e8c9eeb1b8d453087bc"
}
},
{
"uri": "pkg:docker/gcr.io/distroless/cc-debian12@latest?platform=linux%2Famd64",
"digest": {
"sha256": "329e54034ce498f9c6b345044e8f530c6691f99e94a92446f68c0adf9baa8464"
}
}
],
"invocation": {
"configSource": {
"entryPoint": "router.Dockerfile"
},
"parameters": {
"frontend": "gateway.v0",
"args": {
"cmdline": "docker/dockerfile:1.21",
"label:org.opencontainers.image.created": "2026-02-24T18:11:55.263Z",
"label:org.opencontainers.image.description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"label:org.opencontainers.image.licenses": "MIT",
"label:org.opencontainers.image.revision": "084431481b49364cde3f5439b936821ccadaf639",
"label:org.opencontainers.image.source": "https://github.com/graphql-hive/router",
"label:org.opencontainers.image.title": "router",
"label:org.opencontainers.image.url": "https://github.com/graphql-hive/router",
"label:org.opencontainers.image.vendor": "theguild",
"label:org.opencontainers.image.version": "pr-620",
"source": "docker/dockerfile:1.21"
},
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"environment": {
"github_actor": "enisdenjo",
"github_actor_id": "11807600",
"github_event_name": "pull_request",
"github_event_payload": {
"action": "synchronize",
"after": "06567a85fbeaf763e771b12ccb159b324101fee2",
"before": "0701a22069f2045830a5a87754d75706278cbdf1",
"enterprise": {
"avatar_url": "https://avatars.githubusercontent.com/b/187753?v=4",
"created_at": "2024-07-02T08:52:28Z",
"description": "",
"html_url": "https://github.com/enterprises/the-guild",
"id": 187753,
"name": "The Guild",
"node_id": "E_kgDOAALdaQ",
"slug": "the-guild",
"updated_at": "2026-02-11T08:01:14Z",
"website_url": "https://the-guild.dev/"
},
"number": 620,
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"description": "Schema registry, analytics and gateway for GraphQL federation and other GraphQL APIs.",
"events_url": "https://api.github.com/orgs/graphql-hive/events",
"hooks_url": "https://api.github.com/orgs/graphql-hive/hooks",
"id": 182742256,
"issues_url": "https://api.github.com/orgs/graphql-hive/issues",
"login": "graphql-hive",
"members_url": "https://api.github.com/orgs/graphql-hive/members{/member}",
"node_id": "O_kgDOCuRs8A",
"public_members_url": "https://api.github.com/orgs/graphql-hive/public_members{/member}",
"repos_url": "https://api.github.com/orgs/graphql-hive/repos",
"url": "https://api.github.com/orgs/graphql-hive"
},
"pull_request": {
"_links": {
"comments": {
"href": "https://api.github.com/repos/graphql-hive/router/issues/620/comments"
},
"commits": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/620/commits"
},
"html": {
"href": "https://github.com/graphql-hive/router/pull/620"
},
"issue": {
"href": "https://api.github.com/repos/graphql-hive/router/issues/620"
},
"review_comment": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}"
},
"review_comments": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/620/comments"
},
"self": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/620"
},
"statuses": {
"href": "https://api.github.com/repos/graphql-hive/router/statuses/06567a85fbeaf763e771b12ccb159b324101fee2"
}
},
"active_lock_reason": null,
"additions": 10096,
"assignee": null,
"assignees": [],
"author_association": "MEMBER",
"auto_merge": null,
"base": {
"label": "graphql-hive:main",
"ref": "main",
"repo": {
"allow_auto_merge": false,
"allow_forking": true,
"allow_merge_commit": false,
"allow_rebase_merge": false,
"allow_squash_merge": true,
"allow_update_branch": true,
"archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
"blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
"clone_url": "https://github.com/graphql-hive/router.git",
"collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
"commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
"compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
"contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
"created_at": "2024-11-20T16:16:12Z",
"default_branch": "main",
"delete_branch_on_merge": true,
"deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
"description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"disabled": false,
"downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
"events_url": "https://api.github.com/repos/graphql-hive/router/events",
"fork": false,
"forks": 8,
"forks_count": 8,
"forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
"full_name": "graphql-hive/router",
"git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
"git_url": "git://github.com/graphql-hive/router.git",
"has_discussions": false,
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": false,
"has_pull_requests": true,
"has_wiki": false,
"homepage": "https://the-guild.dev/graphql/hive/docs/router",
"hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
"html_url": "https://github.com/graphql-hive/router",
"id": 891604244,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
"issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
"keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
"labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
"language": "Rust",
"languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"merge_commit_message": "PR_TITLE",
"merge_commit_title": "MERGE_MESSAGE",
"merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
"milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
"mirror_url": null,
"name": "router",
"node_id": "R_kgDONSTNFA",
"notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
"open_issues": 58,
"open_issues_count": 58,
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
},
"private": false,
"pull_request_creation_policy": "all",
"pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
"pushed_at": "2026-02-24T17:59:06Z",
"releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
"size": 5056,
"squash_merge_commit_message": "PR_BODY",
"squash_merge_commit_title": "PR_TITLE",
"ssh_url": "git@github.com:graphql-hive/router.git",
"stargazers_count": 72,
"stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
"subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
"svn_url": "https://github.com/graphql-hive/router",
"tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
"teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
"topics": [
"apollo-federation",
"federation",
"federation-gateway",
"graphql",
"graphql-federation",
"router"
],
"trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
"updated_at": "2026-02-24T08:52:58Z",
"url": "https://api.github.com/repos/graphql-hive/router",
"use_squash_pr_title_as_default": true,
"visibility": "public",
"watchers": 72,
"watchers_count": 72,
"web_commit_signoff_required": false
},
"sha": "c5ca9577b8d13e76faa088f9cbb61fbf984b4606",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
}
},
"body": "Ref router-165\r\n\r\nImplement [SSE](https://github.com/enisdenjo/graphql-sse/blob/918b9d414a4f938c715a8f8bc13184c0849eac50/PROTOCOL.md#distinct-connections-mode), [Incremental Delivery over HTTP](https://github.com/graphql/graphql-over-http/blob/d312e43384006fa323b918d49cfd9fbd76ac1257/rfcs/IncrementalDelivery.md), [Apollo's Multipart HTTP](https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol) and [WebSocket](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) specs for subscriptions when communicating with subgraphs or clients with entity resolution capabilities.\r\n\r\n# What changed?\r\n\r\n## Streaming execution result\r\n\r\n```\r\nlib/executor/src/execution/plan.rs@QueryPlanExecutionResult\r\n```\r\n\r\nThe execution pipeline now returns `QueryPlanExecutionResult` - an enum that can be either a single response or a stream:\r\n\r\n```rust\r\npub enum QueryPlanExecutionResult {\r\n Single(PlanExecutionOutput),\r\n Stream(PlanSubscriptionOutput),\r\n}\r\n```\r\n\r\nThis separation allows the query planner and executor to operate independently from transport concerns, making future improvements (like connection repair and silent retries) easier to implement.\r\n\r\n## Owned query plan execution context values for streaming\r\n\r\n```\r\nlib/executor/src/execution/plan.rs\r\n```\r\n\r\nSubscriptions require long-lived contexts that outlive request lifetimes, so we clone all of the necessary context values:\r\n\r\n- `Arc`-wrapped shared data (executors, schema metadata, projection/header plans)\r\n- Enables entity resolution to happen independently for each subscription event\r\n- Processes remaining plan nodes after the subscription fetch\r\n\r\n## Subscription handlers\r\n\r\nThe router respects the client's `Accept` header to determine response format:\r\n\r\n- `text/event-stream` → SSE responses\r\n- `multipart/mixed` → Incremental Delivery over HTTP\r\n- `multipart/mixed;subscriptionSpec=\"1.0\"` → Apollo multipart HTTP\r\n- Returns `406 Not Acceptable` if subscription is requested over unsupported transport\r\n- Handles errors by emitting an error event and completing the stream\r\n- Heartbeats every 10 seconds (except for incremental delivery, doesn't have it)\r\n- Of course protocols used between router and subgraphs and clients can be different\r\n\r\nSame behavior is expected when communicating with subgraphs.\r\n\r\n### SSE\r\n\r\n```\r\nlib/executor/src/executors/sse.rs\r\n```\r\n\r\nImplements the [GraphQL over SSE spec distinct connection mode](https://github.com/enisdenjo/graphql-sse/blob/918b9d414a4f938c715a8f8bc13184c0849eac50/PROTOCOL.md#distinct-connections-mode).\r\n\r\n### Multipart protocol\r\n\r\n```\r\nlib/executor/src/executors/multipart_subscribe.rs\r\n```\r\n\r\nImplements [Apollo's multipart HTTP spec](https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol) and [GraphQL's Incremental Delivery over HTTP RFC](https://github.com/graphql/graphql-over-http/blob/d312e43384006fa323b918d49cfd9fbd76ac1257/rfcs/IncrementalDelivery.md).\r\n\r\n## WebSockets\r\n\r\nRead stacked PR #738\r\n\r\n## Entity resolution\r\n\r\n```\r\nlib/executor/src/executors/multipart_subscribe.rs@execute_plan_with_initial_data\r\n```\r\n\r\nWhen a subscription emits data that references entities from other subgraphs, the router:\r\n\r\n1. Receives subscription event from primary subgraph\r\n2. Executes remaining plan nodes (flatten, fetch, etc.) to populate missing fields\r\n3. Projects the complete response\r\n4. Streams to client\r\n\r\nThis is handled in the async stream generator in `execute_query_plan()`:\r\n\r\n```rust\r\nwhile let Some(response) = response_stream.next().await {\r\n // Parse subgraph response\r\n // Execute entity resolution if needed (remaining plan nodes)\r\n // Project and yield to client\r\n}\r\n```\r\n\r\n## Subscription node in query plan\r\n\r\n```\r\nlib/query-planner/src/planner/query_plan.rs@wrap_subscription_fetch_nodes\r\n```\r\n\r\n`SubscriptionNode` is used and now wraps subscription fetch operations:\r\n\r\n```rust\r\npub struct SubscriptionNode {\r\n // It will practically always be a FetchNode\r\n pub primary: FetchNode,\r\n}\r\n```\r\n\r\nThe query planner detects subscription operations and wraps them appropriately, enabling plans with entity resolution like:\r\n\r\n```\r\nSequence [\r\n SubscriptionNode { primary: Fetch(reviews) },\r\n Flatten(products),\r\n Fetch(products)\r\n]\r\n```\r\n\r\n## Subgraph executor subscribe method\r\n\r\n```\r\nlib/executor/src/executors/common.rs@subscribe\r\n```\r\n\r\nThe HTTP executor gains a `subscribe()` method that:\r\n\r\n- Negotiates content-type (prefers multipart, falls back to SSE)\r\n- Establishes long-lived connections to subgraphs\r\n- Returns a `BoxStream<HttpExecutionResponse>` for downstream processing\r\n\r\n## Configure to only enable/disable subscriptions\r\n\r\nThe supported subscription protocols in this PR are inherintly HTTP and do not need a \"protocol\" configuration option. Hive Router will send an accept header listing all supported protocols for subscriptions over HTTP and the subgraph is free to choose whichever one it supports.\r\n\r\nWhether we really _want_ to limit specific protocols is up to discussion but objectively there is no benefit since they're all streamed HTTP.\r\n\r\nHence, you can only:\r\n\r\n```yaml\r\nsupergraph:\r\n source: file\r\n path: supergraph.graphql\r\nsubscriptions:\r\n enabled: true\r\n```\r\n\r\nP.S. if the subscriptions are disabled, the router will respond with when receiving a subscription request:\r\n\r\n```\r\nHTTP/1.1 415 Unsupported Media Type\r\nContent-Type: application/json\r\n\r\n{\"errors\":[{\"message\":\"Subscriptions are not supported\",\"extensions\":{\"code\":\"SUBSCRIPTIONS_NOT_SUPPORT\"}}]}\r\n```\r\n\r\n# What didn't change?\r\n\r\n## No HTTP Callback Protocol\r\n\r\nIntentionally excluded to keep the PR focused. This would require webhook infrastructure in the router, which adds significant complexity. The SSE and multipart protocols cover the vast majority of use \r\n\r\n## Silent Retries & Upstream Connection Repair\r\n\r\nMost GraphQL subgraph implementations are stateless for subscriptions and have no concept of \"continuing\" from where they left off after a connection loss. Implementing retry logic on the router side would create false expectations - users would assume all events are delivered, but some would be lost when the subgraph creates a fresh subscription.\r\n\r\nThis is fundamentally why the EDFS (Event-Driven Federated Subscriptions) and callback protocols exist. To avoid misleading behavior and keep the PR focused on the core functionality, connection repair is not implemented.\r\n\r\n# TODO\r\n\r\n- [x] [documentation](https://github.com/graphql-hive/console/pull/7472)\r\n- [ ] changesets\r\n- [x] e2e tests for error handling\r\n- [x] e2e tests for header propagation\r\n- [x] use details from the actual client request for entity resolution towards subgraphs of events\r\n- [x] test subgraph disconnect propagation\r\n- [ ] use apollo's multipart http payload for transport erros\r\n- [ ] ~~test client disconnect propagation~~\r\n - hard to test, would need changing the source code because async being state machines allows it just to stop execution in any suspended state\r\n- [ ] usage reporting (bin/router/src/pipeline/mod.rs#305)\r\n- [ ] http callback protocol\r\n- [ ] performance considerations\r\n- [x] entity resolution failures dont end the stream, should they? a subgraph might recover, usually you dont end the whole stream. **they should not end the stream**\r\n- [ ] subscriptions deduplication (same subs same selection sets same headers)\r\n- [ ] reloading the supergraph notifies the subscribers\r\n- [ ] limits, things like client connection or event queue\r\n- [x] configure enable/disable subscriptions\r\n- [ ] ~~configure accepted protocols~~\r\n - no need, all these here fall under \"http\" protocols and can be negotiated over http. read description\r\n- [ ] stream errors when accepting only streaming content types",
"changed_files": 80,
"closed_at": null,
"comments": 3,
"comments_url": "https://api.github.com/repos/graphql-hive/router/issues/620/comments",
"commits": 146,
"commits_url": "https://api.github.com/repos/graphql-hive/router/pulls/620/commits",
"created_at": "2025-12-13T05:59:56Z",
"deletions": 3614,
"diff_url": "https://github.com/graphql-hive/router/pull/620.diff",
"draft": true,
"head": {
"label": "graphql-hive:not-kamil-subs",
"ref": "not-kamil-subs",
"repo": {
"allow_auto_merge": false,
"allow_forking": true,
"allow_merge_commit": false,
"allow_rebase_merge": false,
"allow_squash_merge": true,
"allow_update_branch": true,
"archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
"blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
"clone_url": "https://github.com/graphql-hive/router.git",
"collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
"commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
"compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
"contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
"created_at": "2024-11-20T16:16:12Z",
"default_branch": "main",
"delete_branch_on_merge": true,
"deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
"description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"disabled": false,
"downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
"events_url": "https://api.github.com/repos/graphql-hive/router/events",
"fork": false,
"forks": 8,
"forks_count": 8,
"forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
"full_name": "graphql-hive/router",
"git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
"git_url": "git://github.com/graphql-hive/router.git",
"has_discussions": false,
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": false,
"has_pull_requests": true,
"has_wiki": false,
"homepage": "https://the-guild.dev/graphql/hive/docs/router",
"hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
"html_url": "https://github.com/graphql-hive/router",
"id": 891604244,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
"issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
"keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
"labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
"language": "Rust",
"languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"merge_commit_message": "PR_TITLE",
"merge_commit_title": "MERGE_MESSAGE",
"merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
"milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
"mirror_url": null,
"name": "router",
"node_id": "R_kgDONSTNFA",
"notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
"open_issues": 58,
"open_issues_count": 58,
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
},
"private": false,
"pull_request_creation_policy": "all",
"pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
"pushed_at": "2026-02-24T17:59:06Z",
"releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
"size": 5056,
"squash_merge_commit_message": "PR_BODY",
"squash_merge_commit_title": "PR_TITLE",
"ssh_url": "git@github.com:graphql-hive/router.git",
"stargazers_count": 72,
"stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
"subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
"svn_url": "https://github.com/graphql-hive/router",
"tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
"teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
"topics": [
"apollo-federation",
"federation",
"federation-gateway",
"graphql",
"graphql-federation",
"router"
],
"trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
"updated_at": "2026-02-24T08:52:58Z",
"url": "https://api.github.com/repos/graphql-hive/router",
"use_squash_pr_title_as_default": true,
"visibility": "public",
"watchers": 72,
"watchers_count": 72,
"web_commit_signoff_required": false
},
"sha": "06567a85fbeaf763e771b12ccb159b324101fee2",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
}
},
"html_url": "https://github.com/graphql-hive/router/pull/620",
"id": 3098392050,
"issue_url": "https://api.github.com/repos/graphql-hive/router/issues/620",
"labels": [],
"locked": false,
"maintainer_can_modify": false,
"merge_commit_sha": "6f41a601180d99e299f1a7e894e91594d5aa2dc2",
"mergeable": null,
"mergeable_state": "unknown",
"merged": false,
"merged_at": null,
"merged_by": null,
"milestone": null,
"node_id": "PR_kwDONSTNFM64rbXy",
"number": 620,
"patch_url": "https://github.com/graphql-hive/router/pull/620.patch",
"rebaseable": null,
"requested_reviewers": [
{
"avatar_url": "https://avatars.githubusercontent.com/u/3680083?v=4",
"events_url": "https://api.github.com/users/dotansimha/events{/privacy}",
"followers_url": "https://api.github.com/users/dotansimha/followers",
"following_url": "https://api.github.com/users/dotansimha/following{/other_user}",
"gists_url": "https://api.github.com/users/dotansimha/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/dotansimha",
"id": 3680083,
"login": "dotansimha",
"node_id": "MDQ6VXNlcjM2ODAwODM=",
"organizations_url": "https://api.github.com/users/dotansimha/orgs",
"received_events_url": "https://api.github.com/users/dotansimha/received_events",
"repos_url": "https://api.github.com/users/dotansimha/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/dotansimha/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/dotansimha/subscriptions",
"type": "User",
"url": "https://api.github.com/users/dotansimha",
"user_view_type": "public"
},
{
"avatar_url": "https://avatars.githubusercontent.com/u/8167190?v=4",
"events_url": "https://api.github.com/users/kamilkisiela/events{/privacy}",
"followers_url": "https://api.github.com/users/kamilkisiela/followers",
"following_url": "https://api.github.com/users/kamilkisiela/following{/other_user}",
"gists_url": "https://api.github.com/users/kamilkisiela/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/kamilkisiela",
"id": 8167190,
"login": "kamilkisiela",
"node_id": "MDQ6VXNlcjgxNjcxOTA=",
"organizations_url": "https://api.github.com/users/kamilkisiela/orgs",
"received_events_url": "https://api.github.com/users/kamilkisiela/received_events",
"repos_url": "https://api.github.com/users/kamilkisiela/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/kamilkisiela/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/kamilkisiela/subscriptions",
"type": "User",
"url": "https://api.github.com/users/kamilkisiela",
"user_view_type": "public"
}
],
"requested_teams": [],
"review_comment_url": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}",
"review_comments": 19,
"review_comments_url": "https://api.github.com/repos/graphql-hive/router/pulls/620/comments",
"state": "open",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/06567a85fbeaf763e771b12ccb159b324101fee2",
"title": "feat: Subscriptions",
"updated_at": "2026-02-24T17:59:07Z",
"url": "https://api.github.com/repos/graphql-hive/router/pulls/620",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/11807600?v=4",
"events_url": "https://api.github.com/users/enisdenjo/events{/privacy}",
"followers_url": "https://api.github.com/users/enisdenjo/followers",
"following_url": "https://api.github.com/users/enisdenjo/following{/other_user}",
"gists_url": "https://api.github.com/users/enisdenjo/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/enisdenjo",
"id": 11807600,
"login": "enisdenjo",
"node_id": "MDQ6VXNlcjExODA3NjAw",
"organizations_url": "https://api.github.com/users/enisdenjo/orgs",
"received_events_url": "https://api.github.com/users/enisdenjo/received_events",
"repos_url": "https://api.github.com/users/enisdenjo/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/enisdenjo/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/enisdenjo/subscriptions",
"type": "User",
"url": "https://api.github.com/users/enisdenjo",
"user_view_type": "public"
}
},
"repository": {
"allow_forking": true,
"archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
"blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
"clone_url": "https://github.com/graphql-hive/router.git",
"collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
"commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
"compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
"contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
"created_at": "2024-11-20T16:16:12Z",
"custom_properties": {
"vanta_production_branch_name": "main"
},
"default_branch": "main",
"deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
"description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"disabled": false,
"downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
"events_url": "https://api.github.com/repos/graphql-hive/router/events",
"fork": false,
"forks": 8,
"forks_count": 8,
"forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
"full_name": "graphql-hive/router",
"git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
"git_url": "git://github.com/graphql-hive/router.git",
"has_discussions": false,
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": false,
"has_pull_requests": true,
"has_wiki": false,
"homepage": "https://the-guild.dev/graphql/hive/docs/router",
"hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
"html_url": "https://github.com/graphql-hive/router",
"id": 891604244,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
"issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
"keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
"labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
"language": "Rust",
"languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
"milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
"mirror_url": null,
"name": "router",
"node_id": "R_kgDONSTNFA",
"notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
"open_issues": 58,
"open_issues_count": 58,
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
},
"private": false,
"pull_request_creation_policy": "all",
"pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
"pushed_at": "2026-02-24T17:59:06Z",
"releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
"size": 5056,
"ssh_url": "git@github.com:graphql-hive/router.git",
"stargazers_count": 72,
"stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
"subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
"svn_url": "https://github.com/graphql-hive/router",
"tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
"teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
"topics": [
"apollo-federation",
"federation",
"federation-gateway",
"graphql",
"graphql-federation",
"router"
],
"trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
"updated_at": "2026-02-24T08:52:58Z",
"url": "https://api.github.com/repos/graphql-hive/router",
"visibility": "public",
"watchers": 72,
"watchers_count": 72,
"web_commit_signoff_required": false
},
"sender": {
"avatar_url": "https://avatars.githubusercontent.com/u/11807600?v=4",
"events_url": "https://api.github.com/users/enisdenjo/events{/privacy}",
"followers_url": "https://api.github.com/users/enisdenjo/followers",
"following_url": "https://api.github.com/users/enisdenjo/following{/other_user}",
"gists_url": "https://api.github.com/users/enisdenjo/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/enisdenjo",
"id": 11807600,
"login": "enisdenjo",
"node_id": "MDQ6VXNlcjExODA3NjAw",
"organizations_url": "https://api.github.com/users/enisdenjo/orgs",
"received_events_url": "https://api.github.com/users/enisdenjo/received_events",
"repos_url": "https://api.github.com/users/enisdenjo/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/enisdenjo/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/enisdenjo/subscriptions",
"type": "User",
"url": "https://api.github.com/users/enisdenjo",
"user_view_type": "public"
}
},
"github_job": "docker",
"github_ref": "refs/pull/620/merge",
"github_ref_name": "620/merge",
"github_ref_protected": "false",
"github_ref_type": "branch",
"github_repository": "graphql-hive/router",
"github_repository_id": "891604244",
"github_repository_owner": "graphql-hive",
"github_repository_owner_id": "182742256",
"github_run_attempt": "1",
"github_run_id": "22363476760",
"github_run_number": "1572",
"github_runner_arch": "X64",
"github_runner_environment": "github-hosted",
"github_runner_image_os": "ubuntu24",
"github_runner_image_version": "20260201.15.1",
"github_runner_name": "GitHub Actions 1000588607",
"github_runner_os": "Linux",
"github_runner_tracking_id": "github_22149c10-8abe-4f7b-8046-1de7c95e28bd",
"github_server_url": "https://github.com",
"github_triggering_actor": "enisdenjo",
"github_workflow": "build-router",
"github_workflow_ref": "graphql-hive/router/.github/workflows/build-router.yaml@refs/pull/620/merge",
"github_workflow_sha": "084431481b49364cde3f5439b936821ccadaf639",
"platform": "linux/amd64"
}
}
},
"buildx.build.provenance/linux/arm64": {
"builder": {
"id": "https://github.com/graphql-hive/router/actions/runs/22363476760/attempts/1"
},
"buildType": "https://mobyproject.org/buildkit@v1",
"materials": [
{
"uri": "pkg:docker/docker/dockerfile@1.21",
"digest": {
"sha256": "27f9262d43452075f3c410287a2c43f5ef1bf7ec2bb06e8c9eeb1b8d453087bc"
}
},
{
"uri": "pkg:docker/gcr.io/distroless/cc-debian12@latest?platform=linux%2Farm64",
"digest": {
"sha256": "329e54034ce498f9c6b345044e8f530c6691f99e94a92446f68c0adf9baa8464"
}
}
],
"invocation": {
"configSource": {
"entryPoint": "router.Dockerfile"
},
"parameters": {
"frontend": "gateway.v0",
"args": {
"cmdline": "docker/dockerfile:1.21",
"label:org.opencontainers.image.created": "2026-02-24T18:11:55.263Z",
"label:org.opencontainers.image.description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"label:org.opencontainers.image.licenses": "MIT",
"label:org.opencontainers.image.revision": "084431481b49364cde3f5439b936821ccadaf639",
"label:org.opencontainers.image.source": "https://github.com/graphql-hive/router",
"label:org.opencontainers.image.title": "router",
"label:org.opencontainers.image.url": "https://github.com/graphql-hive/router",
"label:org.opencontainers.image.vendor": "theguild",
"label:org.opencontainers.image.version": "pr-620",
"source": "docker/dockerfile:1.21"
},
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"environment": {
"github_actor": "enisdenjo",
"github_actor_id": "11807600",
"github_event_name": "pull_request",
"github_event_payload": {
"action": "synchronize",
"after": "06567a85fbeaf763e771b12ccb159b324101fee2",
"before": "0701a22069f2045830a5a87754d75706278cbdf1",
"enterprise": {
"avatar_url": "https://avatars.githubusercontent.com/b/187753?v=4",
"created_at": "2024-07-02T08:52:28Z",
"description": "",
"html_url": "https://github.com/enterprises/the-guild",
"id": 187753,
"name": "The Guild",
"node_id": "E_kgDOAALdaQ",
"slug": "the-guild",
"updated_at": "2026-02-11T08:01:14Z",
"website_url": "https://the-guild.dev/"
},
"number": 620,
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"description": "Schema registry, analytics and gateway for GraphQL federation and other GraphQL APIs.",
"events_url": "https://api.github.com/orgs/graphql-hive/events",
"hooks_url": "https://api.github.com/orgs/graphql-hive/hooks",
"id": 182742256,
"issues_url": "https://api.github.com/orgs/graphql-hive/issues",
"login": "graphql-hive",
"members_url": "https://api.github.com/orgs/graphql-hive/members{/member}",
"node_id": "O_kgDOCuRs8A",
"public_members_url": "https://api.github.com/orgs/graphql-hive/public_members{/member}",
"repos_url": "https://api.github.com/orgs/graphql-hive/repos",
"url": "https://api.github.com/orgs/graphql-hive"
},
"pull_request": {
"_links": {
"comments": {
"href": "https://api.github.com/repos/graphql-hive/router/issues/620/comments"
},
"commits": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/620/commits"
},
"html": {
"href": "https://github.com/graphql-hive/router/pull/620"
},
"issue": {
"href": "https://api.github.com/repos/graphql-hive/router/issues/620"
},
"review_comment": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}"
},
"review_comments": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/620/comments"
},
"self": {
"href": "https://api.github.com/repos/graphql-hive/router/pulls/620"
},
"statuses": {
"href": "https://api.github.com/repos/graphql-hive/router/statuses/06567a85fbeaf763e771b12ccb159b324101fee2"
}
},
"active_lock_reason": null,
"additions": 10096,
"assignee": null,
"assignees": [],
"author_association": "MEMBER",
"auto_merge": null,
"base": {
"label": "graphql-hive:main",
"ref": "main",
"repo": {
"allow_auto_merge": false,
"allow_forking": true,
"allow_merge_commit": false,
"allow_rebase_merge": false,
"allow_squash_merge": true,
"allow_update_branch": true,
"archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
"blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
"clone_url": "https://github.com/graphql-hive/router.git",
"collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
"commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
"compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
"contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
"created_at": "2024-11-20T16:16:12Z",
"default_branch": "main",
"delete_branch_on_merge": true,
"deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
"description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"disabled": false,
"downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
"events_url": "https://api.github.com/repos/graphql-hive/router/events",
"fork": false,
"forks": 8,
"forks_count": 8,
"forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
"full_name": "graphql-hive/router",
"git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
"git_url": "git://github.com/graphql-hive/router.git",
"has_discussions": false,
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": false,
"has_pull_requests": true,
"has_wiki": false,
"homepage": "https://the-guild.dev/graphql/hive/docs/router",
"hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
"html_url": "https://github.com/graphql-hive/router",
"id": 891604244,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
"issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
"keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
"labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
"language": "Rust",
"languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"merge_commit_message": "PR_TITLE",
"merge_commit_title": "MERGE_MESSAGE",
"merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
"milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
"mirror_url": null,
"name": "router",
"node_id": "R_kgDONSTNFA",
"notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
"open_issues": 58,
"open_issues_count": 58,
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
},
"private": false,
"pull_request_creation_policy": "all",
"pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
"pushed_at": "2026-02-24T17:59:06Z",
"releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
"size": 5056,
"squash_merge_commit_message": "PR_BODY",
"squash_merge_commit_title": "PR_TITLE",
"ssh_url": "git@github.com:graphql-hive/router.git",
"stargazers_count": 72,
"stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
"subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
"svn_url": "https://github.com/graphql-hive/router",
"tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
"teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
"topics": [
"apollo-federation",
"federation",
"federation-gateway",
"graphql",
"graphql-federation",
"router"
],
"trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
"updated_at": "2026-02-24T08:52:58Z",
"url": "https://api.github.com/repos/graphql-hive/router",
"use_squash_pr_title_as_default": true,
"visibility": "public",
"watchers": 72,
"watchers_count": 72,
"web_commit_signoff_required": false
},
"sha": "c5ca9577b8d13e76faa088f9cbb61fbf984b4606",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
}
},
"body": "Ref router-165\r\n\r\nImplement [SSE](https://github.com/enisdenjo/graphql-sse/blob/918b9d414a4f938c715a8f8bc13184c0849eac50/PROTOCOL.md#distinct-connections-mode), [Incremental Delivery over HTTP](https://github.com/graphql/graphql-over-http/blob/d312e43384006fa323b918d49cfd9fbd76ac1257/rfcs/IncrementalDelivery.md), [Apollo's Multipart HTTP](https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol) and [WebSocket](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) specs for subscriptions when communicating with subgraphs or clients with entity resolution capabilities.\r\n\r\n# What changed?\r\n\r\n## Streaming execution result\r\n\r\n```\r\nlib/executor/src/execution/plan.rs@QueryPlanExecutionResult\r\n```\r\n\r\nThe execution pipeline now returns `QueryPlanExecutionResult` - an enum that can be either a single response or a stream:\r\n\r\n```rust\r\npub enum QueryPlanExecutionResult {\r\n Single(PlanExecutionOutput),\r\n Stream(PlanSubscriptionOutput),\r\n}\r\n```\r\n\r\nThis separation allows the query planner and executor to operate independently from transport concerns, making future improvements (like connection repair and silent retries) easier to implement.\r\n\r\n## Owned query plan execution context values for streaming\r\n\r\n```\r\nlib/executor/src/execution/plan.rs\r\n```\r\n\r\nSubscriptions require long-lived contexts that outlive request lifetimes, so we clone all of the necessary context values:\r\n\r\n- `Arc`-wrapped shared data (executors, schema metadata, projection/header plans)\r\n- Enables entity resolution to happen independently for each subscription event\r\n- Processes remaining plan nodes after the subscription fetch\r\n\r\n## Subscription handlers\r\n\r\nThe router respects the client's `Accept` header to determine response format:\r\n\r\n- `text/event-stream` → SSE responses\r\n- `multipart/mixed` → Incremental Delivery over HTTP\r\n- `multipart/mixed;subscriptionSpec=\"1.0\"` → Apollo multipart HTTP\r\n- Returns `406 Not Acceptable` if subscription is requested over unsupported transport\r\n- Handles errors by emitting an error event and completing the stream\r\n- Heartbeats every 10 seconds (except for incremental delivery, doesn't have it)\r\n- Of course protocols used between router and subgraphs and clients can be different\r\n\r\nSame behavior is expected when communicating with subgraphs.\r\n\r\n### SSE\r\n\r\n```\r\nlib/executor/src/executors/sse.rs\r\n```\r\n\r\nImplements the [GraphQL over SSE spec distinct connection mode](https://github.com/enisdenjo/graphql-sse/blob/918b9d414a4f938c715a8f8bc13184c0849eac50/PROTOCOL.md#distinct-connections-mode).\r\n\r\n### Multipart protocol\r\n\r\n```\r\nlib/executor/src/executors/multipart_subscribe.rs\r\n```\r\n\r\nImplements [Apollo's multipart HTTP spec](https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol) and [GraphQL's Incremental Delivery over HTTP RFC](https://github.com/graphql/graphql-over-http/blob/d312e43384006fa323b918d49cfd9fbd76ac1257/rfcs/IncrementalDelivery.md).\r\n\r\n## WebSockets\r\n\r\nRead stacked PR #738\r\n\r\n## Entity resolution\r\n\r\n```\r\nlib/executor/src/executors/multipart_subscribe.rs@execute_plan_with_initial_data\r\n```\r\n\r\nWhen a subscription emits data that references entities from other subgraphs, the router:\r\n\r\n1. Receives subscription event from primary subgraph\r\n2. Executes remaining plan nodes (flatten, fetch, etc.) to populate missing fields\r\n3. Projects the complete response\r\n4. Streams to client\r\n\r\nThis is handled in the async stream generator in `execute_query_plan()`:\r\n\r\n```rust\r\nwhile let Some(response) = response_stream.next().await {\r\n // Parse subgraph response\r\n // Execute entity resolution if needed (remaining plan nodes)\r\n // Project and yield to client\r\n}\r\n```\r\n\r\n## Subscription node in query plan\r\n\r\n```\r\nlib/query-planner/src/planner/query_plan.rs@wrap_subscription_fetch_nodes\r\n```\r\n\r\n`SubscriptionNode` is used and now wraps subscription fetch operations:\r\n\r\n```rust\r\npub struct SubscriptionNode {\r\n // It will practically always be a FetchNode\r\n pub primary: FetchNode,\r\n}\r\n```\r\n\r\nThe query planner detects subscription operations and wraps them appropriately, enabling plans with entity resolution like:\r\n\r\n```\r\nSequence [\r\n SubscriptionNode { primary: Fetch(reviews) },\r\n Flatten(products),\r\n Fetch(products)\r\n]\r\n```\r\n\r\n## Subgraph executor subscribe method\r\n\r\n```\r\nlib/executor/src/executors/common.rs@subscribe\r\n```\r\n\r\nThe HTTP executor gains a `subscribe()` method that:\r\n\r\n- Negotiates content-type (prefers multipart, falls back to SSE)\r\n- Establishes long-lived connections to subgraphs\r\n- Returns a `BoxStream<HttpExecutionResponse>` for downstream processing\r\n\r\n## Configure to only enable/disable subscriptions\r\n\r\nThe supported subscription protocols in this PR are inherintly HTTP and do not need a \"protocol\" configuration option. Hive Router will send an accept header listing all supported protocols for subscriptions over HTTP and the subgraph is free to choose whichever one it supports.\r\n\r\nWhether we really _want_ to limit specific protocols is up to discussion but objectively there is no benefit since they're all streamed HTTP.\r\n\r\nHence, you can only:\r\n\r\n```yaml\r\nsupergraph:\r\n source: file\r\n path: supergraph.graphql\r\nsubscriptions:\r\n enabled: true\r\n```\r\n\r\nP.S. if the subscriptions are disabled, the router will respond with when receiving a subscription request:\r\n\r\n```\r\nHTTP/1.1 415 Unsupported Media Type\r\nContent-Type: application/json\r\n\r\n{\"errors\":[{\"message\":\"Subscriptions are not supported\",\"extensions\":{\"code\":\"SUBSCRIPTIONS_NOT_SUPPORT\"}}]}\r\n```\r\n\r\n# What didn't change?\r\n\r\n## No HTTP Callback Protocol\r\n\r\nIntentionally excluded to keep the PR focused. This would require webhook infrastructure in the router, which adds significant complexity. The SSE and multipart protocols cover the vast majority of use \r\n\r\n## Silent Retries & Upstream Connection Repair\r\n\r\nMost GraphQL subgraph implementations are stateless for subscriptions and have no concept of \"continuing\" from where they left off after a connection loss. Implementing retry logic on the router side would create false expectations - users would assume all events are delivered, but some would be lost when the subgraph creates a fresh subscription.\r\n\r\nThis is fundamentally why the EDFS (Event-Driven Federated Subscriptions) and callback protocols exist. To avoid misleading behavior and keep the PR focused on the core functionality, connection repair is not implemented.\r\n\r\n# TODO\r\n\r\n- [x] [documentation](https://github.com/graphql-hive/console/pull/7472)\r\n- [ ] changesets\r\n- [x] e2e tests for error handling\r\n- [x] e2e tests for header propagation\r\n- [x] use details from the actual client request for entity resolution towards subgraphs of events\r\n- [x] test subgraph disconnect propagation\r\n- [ ] use apollo's multipart http payload for transport erros\r\n- [ ] ~~test client disconnect propagation~~\r\n - hard to test, would need changing the source code because async being state machines allows it just to stop execution in any suspended state\r\n- [ ] usage reporting (bin/router/src/pipeline/mod.rs#305)\r\n- [ ] http callback protocol\r\n- [ ] performance considerations\r\n- [x] entity resolution failures dont end the stream, should they? a subgraph might recover, usually you dont end the whole stream. **they should not end the stream**\r\n- [ ] subscriptions deduplication (same subs same selection sets same headers)\r\n- [ ] reloading the supergraph notifies the subscribers\r\n- [ ] limits, things like client connection or event queue\r\n- [x] configure enable/disable subscriptions\r\n- [ ] ~~configure accepted protocols~~\r\n - no need, all these here fall under \"http\" protocols and can be negotiated over http. read description\r\n- [ ] stream errors when accepting only streaming content types",
"changed_files": 80,
"closed_at": null,
"comments": 3,
"comments_url": "https://api.github.com/repos/graphql-hive/router/issues/620/comments",
"commits": 146,
"commits_url": "https://api.github.com/repos/graphql-hive/router/pulls/620/commits",
"created_at": "2025-12-13T05:59:56Z",
"deletions": 3614,
"diff_url": "https://github.com/graphql-hive/router/pull/620.diff",
"draft": true,
"head": {
"label": "graphql-hive:not-kamil-subs",
"ref": "not-kamil-subs",
"repo": {
"allow_auto_merge": false,
"allow_forking": true,
"allow_merge_commit": false,
"allow_rebase_merge": false,
"allow_squash_merge": true,
"allow_update_branch": true,
"archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
"blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
"clone_url": "https://github.com/graphql-hive/router.git",
"collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
"commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
"compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
"contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
"created_at": "2024-11-20T16:16:12Z",
"default_branch": "main",
"delete_branch_on_merge": true,
"deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
"description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"disabled": false,
"downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
"events_url": "https://api.github.com/repos/graphql-hive/router/events",
"fork": false,
"forks": 8,
"forks_count": 8,
"forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
"full_name": "graphql-hive/router",
"git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
"git_url": "git://github.com/graphql-hive/router.git",
"has_discussions": false,
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": false,
"has_pull_requests": true,
"has_wiki": false,
"homepage": "https://the-guild.dev/graphql/hive/docs/router",
"hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
"html_url": "https://github.com/graphql-hive/router",
"id": 891604244,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
"issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
"keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
"labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
"language": "Rust",
"languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"merge_commit_message": "PR_TITLE",
"merge_commit_title": "MERGE_MESSAGE",
"merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
"milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
"mirror_url": null,
"name": "router",
"node_id": "R_kgDONSTNFA",
"notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
"open_issues": 58,
"open_issues_count": 58,
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
},
"private": false,
"pull_request_creation_policy": "all",
"pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
"pushed_at": "2026-02-24T17:59:06Z",
"releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
"size": 5056,
"squash_merge_commit_message": "PR_BODY",
"squash_merge_commit_title": "PR_TITLE",
"ssh_url": "git@github.com:graphql-hive/router.git",
"stargazers_count": 72,
"stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
"subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
"svn_url": "https://github.com/graphql-hive/router",
"tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
"teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
"topics": [
"apollo-federation",
"federation",
"federation-gateway",
"graphql",
"graphql-federation",
"router"
],
"trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
"updated_at": "2026-02-24T08:52:58Z",
"url": "https://api.github.com/repos/graphql-hive/router",
"use_squash_pr_title_as_default": true,
"visibility": "public",
"watchers": 72,
"watchers_count": 72,
"web_commit_signoff_required": false
},
"sha": "06567a85fbeaf763e771b12ccb159b324101fee2",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
}
},
"html_url": "https://github.com/graphql-hive/router/pull/620",
"id": 3098392050,
"issue_url": "https://api.github.com/repos/graphql-hive/router/issues/620",
"labels": [],
"locked": false,
"maintainer_can_modify": false,
"merge_commit_sha": "6f41a601180d99e299f1a7e894e91594d5aa2dc2",
"mergeable": null,
"mergeable_state": "unknown",
"merged": false,
"merged_at": null,
"merged_by": null,
"milestone": null,
"node_id": "PR_kwDONSTNFM64rbXy",
"number": 620,
"patch_url": "https://github.com/graphql-hive/router/pull/620.patch",
"rebaseable": null,
"requested_reviewers": [
{
"avatar_url": "https://avatars.githubusercontent.com/u/3680083?v=4",
"events_url": "https://api.github.com/users/dotansimha/events{/privacy}",
"followers_url": "https://api.github.com/users/dotansimha/followers",
"following_url": "https://api.github.com/users/dotansimha/following{/other_user}",
"gists_url": "https://api.github.com/users/dotansimha/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/dotansimha",
"id": 3680083,
"login": "dotansimha",
"node_id": "MDQ6VXNlcjM2ODAwODM=",
"organizations_url": "https://api.github.com/users/dotansimha/orgs",
"received_events_url": "https://api.github.com/users/dotansimha/received_events",
"repos_url": "https://api.github.com/users/dotansimha/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/dotansimha/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/dotansimha/subscriptions",
"type": "User",
"url": "https://api.github.com/users/dotansimha",
"user_view_type": "public"
},
{
"avatar_url": "https://avatars.githubusercontent.com/u/8167190?v=4",
"events_url": "https://api.github.com/users/kamilkisiela/events{/privacy}",
"followers_url": "https://api.github.com/users/kamilkisiela/followers",
"following_url": "https://api.github.com/users/kamilkisiela/following{/other_user}",
"gists_url": "https://api.github.com/users/kamilkisiela/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/kamilkisiela",
"id": 8167190,
"login": "kamilkisiela",
"node_id": "MDQ6VXNlcjgxNjcxOTA=",
"organizations_url": "https://api.github.com/users/kamilkisiela/orgs",
"received_events_url": "https://api.github.com/users/kamilkisiela/received_events",
"repos_url": "https://api.github.com/users/kamilkisiela/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/kamilkisiela/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/kamilkisiela/subscriptions",
"type": "User",
"url": "https://api.github.com/users/kamilkisiela",
"user_view_type": "public"
}
],
"requested_teams": [],
"review_comment_url": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}",
"review_comments": 19,
"review_comments_url": "https://api.github.com/repos/graphql-hive/router/pulls/620/comments",
"state": "open",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/06567a85fbeaf763e771b12ccb159b324101fee2",
"title": "feat: Subscriptions",
"updated_at": "2026-02-24T17:59:07Z",
"url": "https://api.github.com/repos/graphql-hive/router/pulls/620",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/11807600?v=4",
"events_url": "https://api.github.com/users/enisdenjo/events{/privacy}",
"followers_url": "https://api.github.com/users/enisdenjo/followers",
"following_url": "https://api.github.com/users/enisdenjo/following{/other_user}",
"gists_url": "https://api.github.com/users/enisdenjo/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/enisdenjo",
"id": 11807600,
"login": "enisdenjo",
"node_id": "MDQ6VXNlcjExODA3NjAw",
"organizations_url": "https://api.github.com/users/enisdenjo/orgs",
"received_events_url": "https://api.github.com/users/enisdenjo/received_events",
"repos_url": "https://api.github.com/users/enisdenjo/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/enisdenjo/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/enisdenjo/subscriptions",
"type": "User",
"url": "https://api.github.com/users/enisdenjo",
"user_view_type": "public"
}
},
"repository": {
"allow_forking": true,
"archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
"archived": false,
"assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
"blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
"branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
"clone_url": "https://github.com/graphql-hive/router.git",
"collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
"comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
"commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
"compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
"contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
"contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
"created_at": "2024-11-20T16:16:12Z",
"custom_properties": {
"vanta_production_branch_name": "main"
},
"default_branch": "main",
"deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
"description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
"disabled": false,
"downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
"events_url": "https://api.github.com/repos/graphql-hive/router/events",
"fork": false,
"forks": 8,
"forks_count": 8,
"forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
"full_name": "graphql-hive/router",
"git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
"git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
"git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
"git_url": "git://github.com/graphql-hive/router.git",
"has_discussions": false,
"has_downloads": true,
"has_issues": true,
"has_pages": false,
"has_projects": false,
"has_pull_requests": true,
"has_wiki": false,
"homepage": "https://the-guild.dev/graphql/hive/docs/router",
"hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
"html_url": "https://github.com/graphql-hive/router",
"id": 891604244,
"is_template": false,
"issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
"issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
"issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
"keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
"labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
"language": "Rust",
"languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
"license": {
"key": "mit",
"name": "MIT License",
"node_id": "MDc6TGljZW5zZTEz",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
"merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
"milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
"mirror_url": null,
"name": "router",
"node_id": "R_kgDONSTNFA",
"notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
"open_issues": 58,
"open_issues_count": 58,
"owner": {
"avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
"events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
"followers_url": "https://api.github.com/users/graphql-hive/followers",
"following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
"gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/graphql-hive",
"id": 182742256,
"login": "graphql-hive",
"node_id": "O_kgDOCuRs8A",
"organizations_url": "https://api.github.com/users/graphql-hive/orgs",
"received_events_url": "https://api.github.com/users/graphql-hive/received_events",
"repos_url": "https://api.github.com/users/graphql-hive/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
"type": "Organization",
"url": "https://api.github.com/users/graphql-hive",
"user_view_type": "public"
},
"private": false,
"pull_request_creation_policy": "all",
"pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
"pushed_at": "2026-02-24T17:59:06Z",
"releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
"size": 5056,
"ssh_url": "git@github.com:graphql-hive/router.git",
"stargazers_count": 72,
"stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
"statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
"subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
"subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
"svn_url": "https://github.com/graphql-hive/router",
"tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
"teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
"topics": [
"apollo-federation",
"federation",
"federation-gateway",
"graphql",
"graphql-federation",
"router"
],
"trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
"updated_at": "2026-02-24T08:52:58Z",
"url": "https://api.github.com/repos/graphql-hive/router",
"visibility": "public",
"watchers": 72,
"watchers_count": 72,
"web_commit_signoff_required": false
},
"sender": {
"avatar_url": "https://avatars.githubusercontent.com/u/11807600?v=4",
"events_url": "https://api.github.com/users/enisdenjo/events{/privacy}",
"followers_url": "https://api.github.com/users/enisdenjo/followers",
"following_url": "https://api.github.com/users/enisdenjo/following{/other_user}",
"gists_url": "https://api.github.com/users/enisdenjo/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/enisdenjo",
"id": 11807600,
"login": "enisdenjo",
"node_id": "MDQ6VXNlcjExODA3NjAw",
"organizations_url": "https://api.github.com/users/enisdenjo/orgs",
"received_events_url": "https://api.github.com/users/enisdenjo/received_events",
"repos_url": "https://api.github.com/users/enisdenjo/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/enisdenjo/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/enisdenjo/subscriptions",
"type": "User",
"url": "https://api.github.com/users/enisdenjo",
"user_view_type": "public"
}
},
"github_job": "docker",
"github_ref": "refs/pull/620/merge",
"github_ref_name": "620/merge",
"github_ref_protected": "false",
"github_ref_type": "branch",
"github_repository": "graphql-hive/router",
"github_repository_id": "891604244",
"github_repository_owner": "graphql-hive",
"github_repository_owner_id": "182742256",
"github_run_attempt": "1",
"github_run_id": "22363476760",
"github_run_number": "1572",
"github_runner_arch": "X64",
"github_runner_environment": "github-hosted",
"github_runner_image_os": "ubuntu24",
"github_runner_image_version": "20260201.15.1",
"github_runner_name": "GitHub Actions 1000588607",
"github_runner_os": "Linux",
"github_runner_tracking_id": "github_22149c10-8abe-4f7b-8046-1de7c95e28bd",
"github_server_url": "https://github.com",
"github_triggering_actor": "enisdenjo",
"github_workflow": "build-router",
"github_workflow_ref": "graphql-hive/router/.github/workflows/build-router.yaml@refs/pull/620/merge",
"github_workflow_sha": "084431481b49364cde3f5439b936821ccadaf639",
"platform": "linux/amd64"
}
}
},
"buildx.build.ref": "builder-b462be6d-975a-46c6-b6b2-4a33579d2cb3/builder-b462be6d-975a-46c6-b6b2-4a33579d2cb30/qgmtmqoco7bq24z8707zht5rs",
"containerimage.descriptor": {
"mediaType": "application/vnd.oci.image.index.v1+json",
"digest": "sha256:4d7cce381a0858908b200f3e4aae32e533c8c24eca9123039090c6849ce283a3",
"size": 1609
},
"containerimage.digest": "sha256:4d7cce381a0858908b200f3e4aae32e533c8c24eca9123039090c6849ce283a3",
"image.name": "ghcr.io/graphql-hive/router:pr-620,ghcr.io/graphql-hive/router:sha-0844314"
} |
This reverts commit 493b0e6.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Ref router-165
Implement SSE, Incremental Delivery over HTTP, Apollo's Multipart HTTP and WebSocket specs for subscriptions when communicating with subgraphs or clients with entity resolution capabilities.
What changed?
Streaming execution result
The execution pipeline now returns
QueryPlanExecutionResult- an enum that can be either a single response or a stream:This separation allows the query planner and executor to operate independently from transport concerns, making future improvements (like connection repair and silent retries) easier to implement.
Owned query plan execution context values for streaming
Subscriptions require long-lived contexts that outlive request lifetimes, so we clone all of the necessary context values:
Arc-wrapped shared data (executors, schema metadata, projection/header plans)Subscription handlers
The router respects the client's
Acceptheader to determine response format:text/event-stream→ SSE responsesmultipart/mixed→ Incremental Delivery over HTTPmultipart/mixed;subscriptionSpec="1.0"→ Apollo multipart HTTP406 Not Acceptableif subscription is requested over unsupported transportSame behavior is expected when communicating with subgraphs.
SSE
Implements the GraphQL over SSE spec distinct connection mode.
Multipart protocol
Implements Apollo's multipart HTTP spec and GraphQL's Incremental Delivery over HTTP RFC.
WebSockets
Read stacked PR #738
Entity resolution
When a subscription emits data that references entities from other subgraphs, the router:
This is handled in the async stream generator in
execute_query_plan():Subscription node in query plan
SubscriptionNodeis used and now wraps subscription fetch operations:The query planner detects subscription operations and wraps them appropriately, enabling plans with entity resolution like:
Subgraph executor subscribe method
The HTTP executor gains a
subscribe()method that:BoxStream<HttpExecutionResponse>for downstream processingConfigure to only enable/disable subscriptions
The supported subscription protocols in this PR are inherintly HTTP and do not need a "protocol" configuration option. Hive Router will send an accept header listing all supported protocols for subscriptions over HTTP and the subgraph is free to choose whichever one it supports.
Whether we really want to limit specific protocols is up to discussion but objectively there is no benefit since they're all streamed HTTP.
Hence, you can only:
P.S. if the subscriptions are disabled, the router will respond with when receiving a subscription request:
What didn't change?
No HTTP Callback Protocol
Intentionally excluded to keep the PR focused. This would require webhook infrastructure in the router, which adds significant complexity. The SSE and multipart protocols cover the vast majority of use
Silent Retries & Upstream Connection Repair
Most GraphQL subgraph implementations are stateless for subscriptions and have no concept of "continuing" from where they left off after a connection loss. Implementing retry logic on the router side would create false expectations - users would assume all events are delivered, but some would be lost when the subgraph creates a fresh subscription.
This is fundamentally why the EDFS (Event-Driven Federated Subscriptions) and callback protocols exist. To avoid misleading behavior and keep the PR focused on the core functionality, connection repair is not implemented.
TODO
test client disconnect propagationconfigure accepted protocols