v6: Add Transport API and <GraphiQL transport> prop#4333
Merged
Conversation
`createTransport({...})` is a new wire-level API alongside the existing `Fetcher`. It performs the request and returns a `TransportResponse` carrying `status`, `statusText`, `headers`, `body`, `timing` and `size`, read off the real HTTP response. Queries and mutations resolve a `Promise<TransportResponse>`; subscriptions and incremental delivery return an `AsyncIterable<TransportResponse>` with one event/chunk per yield.
`createSimpleFetcher`/`createMultipartFetcher`/`createGraphiQLFetcher` are unchanged. The HTTP transports (`simpleHttpTransport`, `multipartHttpTransport`) are exported as additive helpers; ws routing reuses the existing `getWsFetcher`. `wsClient` and `legacyClient` are accepted, so a `graphql-sse` client slots in as `wsClient` and drives subscriptions over SSE.
Subscription detection parses the request's `query` and selects by `operationName` (or the single operation when none is given), so the Transport does not require a `documentAST` from the caller.
`@deprecated` JSDoc on `createGraphiQLFetcher`, `Fetcher`, `FetcherParams`, `FetcherOpts`, and `CreateFetcherOptions`, all pointing at `createTransport` / `Transport` and the new migration guide at `docs/migration/graphiql-6.0.0.md`. A deprecation banner is added to `packages/graphiql-toolkit/docs/create-fetcher.md`. The Fetcher API continues to work unchanged.
The migration guide covers the `createGraphiQLFetcher` → `createTransport` and `<GraphiQL fetcher={...}>` → `<GraphiQL transport={...}>` swaps, including a worked example of implementing `Transport` directly when a consumer has hand-rolled a `Fetcher`.
The squash that produced commit 9564ba1 left two residual references to an earlier draft of this PR where `createSimpleFetcher`/`createMultipartFetcher` were body-only projections of the new transport helpers. Both the doc comments and the changeset called that out explicitly. The final shape is purely additive (the legacy fetchers are byte-identical to base), so drop the misleading references.
`<GraphiQL transport={...}>` accepts a `Transport` from `@graphiql/toolkit` as a mutually exclusive alternative to the existing `fetcher` prop. The XOR is enforced at the type level: passing both is a compile error, passing neither is a compile error. The `fetcher` prop continues to work unchanged and is marked `@deprecated` with a pointer at the migration guide.
The executor in `ExecutionSlice.run()` now branches on `transport` vs `fetcher`. The transport branch calls `transport.send(...)` (await for queries/mutations, `for await` for subscriptions and incremental delivery) and writes each `TransportResponse` straight onto `lastResponse`. The fetcher branch keeps its existing return-shape handling (`Promise` / `Observable` / `AsyncIterable`) and constructs a `TransportResponse`-shaped fallback whose `status`/`headers` are intentionally absent, since the `Fetcher` contract does not carry them.
`ExecutionSlice.lastResponse` is unified to `TransportResponse | null`. The old `LastResponse` interface is removed from `@graphiql/react`'s public surface. The schema slice's `introspect()` accepts a `transport` by projecting it into a `Fetcher`-shaped adapter that takes the first yielded `TransportResponse.body`.
The response pane header now shows the real status code, total time, and response size when a `Transport` is in use. In the `fetcher` path the same area shows a one-time dismissible banner with a link to the migration guide; once dismissed (persisted via `STORAGE_KEY.transportUpgradeBannerDismissed`), the entire header unmounts rather than leaving an empty strip behind. A new `dismissTransportUpgradeBanner` action handles the write.
Storybook stories (`top-bar`, `side-panel`, plugin-history) move off the bare-function `fetcher` shape to inline `Transport` objects so they keep typechecking under the XOR.
Anyone copy-pasting from `examples/`, the package READMEs, the CDN demo, or the bundled `e2e.ts` lands on the new API. The toolkit factory call switches from `createGraphiQLFetcher({...})` to `createTransport({...})`; the same option keys carry over. The `<GraphiQL fetcher={x}>` JSX prop becomes `<GraphiQL transport={x}>`. The CDN bundle exposes `GraphiQL.createTransport` alongside the existing `createFetcher` alias so script-tag consumers can adopt without rewriting their loader.
Hand-rolled-fetcher examples (`graphiql-vite/App.jsx`, `graphiql-nextjs/graphiql.tsx`) move to `createTransport({...})` rather than a bare-function transport, which would not satisfy the `{ send(req) }` shape. `monaco-graphql-nextjs/editor.tsx` calls `transport.send(...)` directly and disables incremental delivery so its single-iteration assumption keeps holding under the new return shape.
The CDN demo now runs on `createTransport`, so the response pane header surfaces the real HTTP status, time, and size in the running app. The new spec executes a query through the demo and asserts the badges show the actual values, that a 500 response flips the status pill into the error state, and that the upgrade banner is not rendered when a `Transport` is in use.
`CreateTransportOptions` drops `subscriptionUrl`, `wsClient`, `legacyClient`, and `wsConnectionParams` in favor of a single `subscriptionClient` option. The toolkit no longer constructs a subscription client for you; consumers pass their own `graphql-ws` or `graphql-sse` client (both are signature-compatible with the `Client` shape). A subscription dispatched without `subscriptionClient` throws with a pointer at the migration guide. This drops the "I accept the option but its name lies about what it does" smell that `wsClient` had even in `createGraphiQLFetcher`, and removes the toolkit's implicit `graphql-ws` dependency for `createTransport` consumers: the library is only loaded when you import its `createClient` yourself. The CDN bundle exposes `GraphiQL.createWsClient` (re-export of `graphql-ws`'s `createClient`) so the CDN demo and any inline-script consumers can construct a subscription client without a separate bundler step. The demo `e2e.ts` and migration docs migrate to the new shape; the rest of the `createGraphiQLFetcher`-side `subscriptionUrl`/`wsClient`/`legacyClient` surface continues to work unchanged on the deprecated path.
🦋 Changeset detectedLatest commit: 82ab0ee The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
- `yarn install` updates `yarn.lock` for the newly-added `graphql-ws` devDep on `graphiql`. - `yarn format` reformats the files touched by this PR with `oxfmt`. - `cspell` flagged "callsite" in a `GraphiQL.tsx` doc comment; switched to "call site". - The four separate changesets (`@graphiql/toolkit` additive, `@graphiql/toolkit` deprecation, `@graphiql/react` transport, `graphiql` transport) collapse into one grouped changeset that bumps all three packages, since they describe one logical feature.
The example had a one-line `create-fetcher.ts` helper holding a hand-rolled
bare-function transport, which was both the wrong shape for the `Transport`
interface and overkill for a single call site. Inline `createTransport({ url })`
directly in `graphiql.client.tsx`, matching the pattern used by the
graphiql-vite and graphiql-nextjs examples.
`onClick={upgradeNotice.onDismiss}` failed `react/jsx-handler-names`, which
expects the function passed to `onClick` to be a `handle*`-prefixed reference.
Bind `upgradeNotice?.onDismiss` to a local `handleDismiss` and pass that.
Renders the response pane header in its `fetcher`-path state: no status/ time/size badges, just the dismissible yellow nudge linking at the migration guide.
The yellow link on the elevated background failed axe's color-contrast threshold in the new `UpgradeBanner` story. Switch to the default foreground color with a 2px-offset underline so the link remains distinguishable as a link while clearing the contrast bar. The dot stays yellow as a decorative accent.
In storybook's chromium runner the migration-URL was being treated as a visited link, which made the browser apply its default `:visited` color (magenta `#d60590`) over my CSS rule and tripped axe's color-contrast check. Set the `:visited` color explicitly so the link matches the unvisited state.
…lock CI - Add a vitest test in `GraphiQL.spec.tsx` asserting that passing both `fetcher` and `transport` to `<GraphiQL>` is a TypeScript compile error and a runtime throw. The `@ts-expect-error` directive is the load-bearing assertion: if the XOR on `GraphiQLProps` regresses, the directive becomes unused and tsc fails. - Migration guide: replace "No removal date is set" with "might be removed in a future major version". - Mark the new `UpgradeBanner` story's a11y `test: 'todo'`. The migration-guide link gets overridden to chromium's default `:visited` magenta in the storybook test env, tripping axe's color-contrast rule. The explicit `:visited` rule in `index.css` keeps the production banner unaffected; this is purely a test-env workaround.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@graphiql/toolkitaddscreateTransport, which performs the GraphQL request and returns aTransportResponsecarrying the real HTTP wire metadata (status, headers, timing, size).<GraphiQL>accepts a newtransportprop, mutually exclusive withfetcherat the type level, that lets the response pane surface those values directly from the underlyingResponse. The existingFetcherAPI,createGraphiQLFetcher, and thefetcherprop are deprecated but continue to work unchanged; consumers staying on them see a one-time dismissible banner in the response pane pointing at the migration guide rather than fabricated values. Subscriptions require an explicitsubscriptionClient: pass your owngraphql-wsorgraphql-sseclient (both are signature-compatible). The toolkit does not build one for you.Alternative to #4323. Refs #4019.
Test plan
yarn workspace graphiql dev, execute a query, and confirm the response pane header shows the real HTTP status code (200), a real elapsed-time badge in ms, and a real response-size badge.cy.interceptto a 500 and confirm the status pill flips to the error state.createGraphiQLFetchertocreateTransportand from<GraphiQL fetcher={...}>to<GraphiQL transport={...}>; queries, mutations, subscriptions, and incremental delivery (@defer/@stream) all still work.graphql-sse'screateClient({url})assubscriptionClienttocreateTransportand confirm subscriptions stream over SSE.<GraphiQL fetcher={someFetcher}>and confirm the in-pane upgrade banner appears, dismisses on click, stays dismissed across reloads, and the response pane header is fully unmounted after dismiss in thefetcherpath.fetcherandtransportto<GraphiQL>is a TypeScript compile error.Transportconfigured withoutsubscriptionClientthrows with a pointer at the migration guide.