createGraphiQLFetcher: support subscriptions over HTTP multipart/mixed (Apollo subscriptionSpec=1.0), not just WebSockets
- Dominant language
- TypeScript
- Stars
- 16.9k
- Forks
- 1.9k
- Avg merge
- 22h 45m
- Merged PRs (30d)
- 70
Description
## Is your feature request related to a problem? Please describe.
`createGraphiQLFetcher` hardwires *every* subscription operation to a WebSocket transport. In `create-fetcher/createFetcher.ts`, once an operation is detected as a subscription it goes straight to `getWsFetcher`:
```ts
if (isSubscription) {
const wsFetcher = await getWsFetcher(options, fetcherOpts);
if (!wsFetcher) {
throw new Error(
`Your GraphiQL createFetcher is not properly configured for websocket subscriptions yet. …`,
);
}
return wsFetcher(graphQLParams);
}
```
and `getWsFetcher` only ever resolves from `wsClient` / `subscriptionUrl` / `legacyClient` — all WebSocket. There is **no** code path that lets a subscription be delivered over HTTP.
This blocks a mainstream setup: servers that deliver subscriptions over HTTP `multipart/mixed` using the Apollo multipart subscription protocol (`subscriptionSpec=1.0`), which is what Apollo Client + Apollo Router/GraphOS use by default. For these APIs there is **no WebSocket endpoint at all** — subscriptions ride the same HTTP endpoint as queries/mutations. Pointing GraphiQL at such an API makes queries/mutations work, but any subscription throws the "not properly configured for websocket subscriptions" error, even though the transport is plain HTTP that the fetcher is otherwise already talking to.
Note this is **distinct** from the existing multipart support: `createMultipartFetcher` handles `@defer`/`@stream` *incremental delivery* (`deferSpec`, `{ hasNext, incremental }` frames) and is only wired to queries/mutations. Apollo multipart *subscriptions* are a different framing (`{ payload }` envelopes + heartbeats), so the existing multipart reader can't be reused as-is, and subscription ops never reach it anyway.
## Describe the solution you'd like
Let `createGraphiQLFetcher` deliver subscriptions over HTTP multipart when configured/negotiated — e.g. an opt-in option such as `subscriptionSpec: '1.0'` (or auto-detection), routing subscription operations to a multipart reader instead of `getWsFetcher` when no WS client is configured. The reader would:
- POST to `options.url` with `Accept: multipart/mixed;subscriptionSpec="1.0", application/json` (boundary `graphql`)
- yield `part.payload` for each message part (containing `data` / `errors` / `extensions`)
- silently skip heartbeat parts (`{}`)
- terminate on the fatal-error frame (`{ "payload": null, "errors": [...] }`) and on the closing boundary
`meros` (already a dependency) can parse the stream; only the per-part envelope handling differs from the `@defer` path.
## Describe alternatives you've considered
- **Consumer-side custom fetcher** — works, but every GraphiQL-based tool sitting in front of an Apollo Router/Client stack has to reimplement the same multipart subscription reader. First-class support in the toolkit is the natural home.
- **graphql-sse / SSE** — a different protocol; GraphiQL's current `EventSource` handling isn't `graphql-sse`-compliant either (enisdenjo/graphql-sse#8), so it's not an out-of-the-box path.
## Additional context
- Apollo multipart subscription protocol (Accept header, `{ payload }` envelope, `{}` heartbeats, fatal-error frame): https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol
- Affects latest `@graphiql/toolkit@0.12.1` and current `main` (subscription routing is identical on both).
- Prior art: Apollo Client's `HttpLink`, Apollo Sandbox/Explorer, and `@apollo/client` all consume this protocol today.
Contributor guide
Research direction
Read create-fetcher/createFetcher.ts, especially the subscription branch and getWsFetcher, then compare its existing createMultipartFetcher path. Trace how meros is used for multipart responses. Done means an explicitly configured HTTP subscription can yield each payload, skip heartbeat parts, and stop on fatal errors or the closing boundary without affecting WebSocket subscriptions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100