feat(typescript): GraphQL SDK generation#16730
Conversation
Layer 1 - IR Changes: - Add Transport.graphql variant to http.yml union type - Create graphql.yml defining GraphqlTransport + GraphqlOperationType - Bump IR version 67.6.0 → 67.7.0 - Update CHANGELOG.md Layer 2 - GraphQL → IR Converter: - GraphQLToIRConverter class (main entry point, parallel to ProtobufIRGenerator) - ir-conversion/convertGraphQLTypes.ts (schema types → IR TypeDeclarations) - ir-conversion/convertRootFieldToEndpoint.ts (root fields → HttpEndpoints) Layer 5 - Query Auto-Generation: - query-generation/generateSelectionQuery.ts (depth-limited full selection) - SelectionSetBuilder class with cycle detection + max depth cap Layer 3 - Pipeline Integration: - GraphQLIRGenerator in lazy-fern-workspace (calls GraphQLToIRConverter) - INTEGRATION_NOTES.md documenting OSSWorkspace + SdkChecker changes All files are stubs with TODO markers — no implementation logic yet. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phases 0-2 of GraphQL SDK support: - Regenerate IR SDK with Transport.graphql variant + GraphqlTransport/GraphqlOperationType - Implement GraphQL schema→IR converter (types, endpoints, services, query generation) - Wire generateAllGraphQLIRs into OSSWorkspace.getIntermediateRepresentation (parallel + merge) - Remove 'GraphQL SDKs not supported' checker warning - Map graphql transport to JSON encoding in convertHttpService Co-Authored-By: Claude <noreply@anthropic.com>
Phase 3 - TS SDK generator consumes Transport.graphql (gated, non-graphql output unchanged):
- Request: wrap body as { query, variables } and POST to /graphql
- Response: unwrap responseBody.data[operationName]
- Converter: emit wrapper sdkRequest so field args flow as GraphQL variables
- Converter: set real platformHeaders (X-Fern-*) to avoid empty header name at runtime
Verified end-to-end: generated SDK issues a real { query, variables } POST and returns unwrapped data.
Co-Authored-By: Claude <noreply@anthropic.com>
GraphQL servers return HTTP 200 even on failure with { data: null, errors: [...] }.
- Unwrap data via optional chaining so null data yields undefined (no TypeError)
- In the ok branch, if errors[] is non-empty, route through the SDK error channel
(throw FernCustomError in throwing mode; failure result in non-throwing mode)
carrying the full GraphQL error body. Gated on graphql transport.
Co-Authored-By: Claude <noreply@anthropic.com>
The OSSWorkspace/SdkChecker wiring it described is now implemented, so the notes-to-self scaffolding file is no longer needed. Co-Authored-By: Claude <noreply@anthropic.com>
| union: | ||
| http: {} | ||
| grpc: GrpcTransport | ||
| graphql: graphql.GraphqlTransport |
There was a problem hiding this comment.
🔴 Backward migration omits new transport variant, causing older generators to receive unrecognized data
The new GraphQL transport variant is passed through unchanged during backward IR migration (migrateHttpEndpoint at packages/cli/generation/ir-migrations/src/migrations/v67-to-v66/migrateFromV67ToV66.ts), so generators on v66 IR receive an unrecognized transport type and may crash.
Impact: SDK generation for GraphQL specs fails for all generators except TypeScript and Python (which are on v67 IR), including Go, Java, C#, Ruby, and PHP.
Migration does not strip or downcast the new graphql Transport variant for v66 generators
The PR adds a graphql variant to the Transport discriminated union in packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml:37. The existing v67-to-v66 migration at packages/cli/generation/ir-migrations/src/migrations/v67-to-v66/migrateFromV67ToV66.ts was written before this variant existed.
The migrateHttpEndpoint function uses spread (...endpoint) without explicitly handling the transport field. When the migration serializes to V67 JSON and parses with the V66 parser, the graphql transport discriminant is not a known variant in V66's Transport union. Generators on v66 that use assertNever or exhaustive switches on Transport.type will throw at runtime.
Per REVIEW.md: "Changes to packages/ir-sdk/fern/apis/ir-types-latest/definition/ affect all generators. Verify that corresponding IR migration entries exist in packages/cli/generation/ir-migrations/ for backward compatibility."
The migration's migrateHttpEndpoint (around line 210 of the migration file) should convert Transport.graphql to Transport.http (since GraphQL-over-HTTP uses a JSON envelope) when migrating backward to v66, similar to how downcastAvailabilityStatus maps new availability statuses to old ones.
Prompt for agents
The v67-to-v66 migration in packages/cli/generation/ir-migrations/src/migrations/v67-to-v66/migrateFromV67ToV66.ts needs to be updated to handle the new graphql Transport variant. The migrateHttpEndpoint function currently passes the transport field through unchanged via the spread operator. Add a migrateTransport function that converts Transport.graphql to Transport.http (since GraphQL-over-HTTP uses JSON encoding, this is the semantically closest v66 equivalent). Then call this function in migrateHttpEndpoint to rewrite the transport field. Also update migrateHttpService if it passes service-level transport through. The pattern should be similar to the existing downcastAvailabilityStatus function that maps new enum values to older equivalents.
Was this helpful? React with 👍 or 👎 to provide feedback.
| for (const spec of graphqlSpecs) { | ||
| try { | ||
| const graphqlIRGenerator = new GraphQLIRGenerator({ context }); | ||
| const ir = await graphqlIRGenerator.generate({ | ||
| absoluteFilepath: spec.absoluteFilepath, | ||
| namespace: spec.namespace | ||
| }); | ||
| results.push(ir); | ||
| } catch (error) { | ||
| context.logger.log("warn", "Failed to generate GraphQL IR: " + error); | ||
| } | ||
| } |
There was a problem hiding this comment.
🚩 GraphQL IR generation runs in parallel with OpenAPI processing but errors are only logged as warnings
In packages/cli/workspace/lazy-fern-workspace/src/OSSWorkspace.ts:583-594, generateAllGraphQLIRs catches errors from individual GraphQL specs and logs them as warnings (context.logger.log("warn", ...)). This means a malformed GraphQL schema silently produces no IR contribution rather than failing the generation. This matches the pattern used for protobuf specs (packages/cli/workspace/lazy-fern-workspace/src/OSSWorkspace.ts:552-554) so it's consistent, but users might not notice their GraphQL endpoints are missing from the generated SDK if their schema has a syntax error.
Was this helpful? React with 👍 or 👎 to provide feedback.
Docs Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on
Docs generation runs |
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
GraphQL specs convert directly to IR (not to a Fern definition), so they only merged on the --from-openapi (direct-to-IR) path. Carry the generated GraphQL IR on FernWorkspace (populated in OSSWorkspace.toFernWorkspace) and merge it inside generateIntermediateRepresentation — the chokepoint every generate path uses (remote, local, and the ir command). GraphQL SDKs now generate from plain `fern generate` without the flag. Co-Authored-By: Claude <noreply@anthropic.com>
A service whose endpoints have no examples (e.g. GraphQL services) produced an
empty test file with an empty describe block, which vitest fails on ("No test
found in suite"). Skip emitting the wire test file when the service has no test
cases, and fix the empty-check in buildFile (tests is Code[][], so flatten before
checking length).
Co-Authored-By: Claude <noreply@anthropic.com>
…hase3-ts-generator
GraphQL endpoints send a { query, variables } envelope, but autogenerated examples
model only the bare variables, so a generated wire test's mocked request body would
not match what the SDK sends. Skip wire tests for graphql transport (the README/
reference snippets, which use the method-call args, are unaffected).
Co-Authored-By: Claude <noreply@anthropic.com>
Inject autogenerated examples into the GraphQL IR before merging it into the fern-definition IR (the fern-def example injection runs earlier, so merged GraphQL operations would otherwise have none). This populates README usage snippets, reference.md, and code samples for GraphQL SDKs. Respects exampleGeneration.disabled. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
DateTime/Timestamp->datetime, Date->date, UUID->uuid, Long->long, BigInt->bigInteger. Unknown and precision-sensitive scalars (BigDecimal, JSON, URL, Email, ...) stay string. Co-Authored-By: Claude <noreply@anthropic.com>
…hase3-ts-generator
…selection Add variableDefinitions + arguments to GraphqlTransport (IR 67.8.0) and populate them in the converter, so SDKs can rebuild the GraphQL query envelope around a caller-provided field selection instead of always sending the pre-built query. Co-Authored-By: Claude <noreply@anthropic.com>
…ction Core utility (emitted on-demand) that builds a GraphQL operation string from a caller-provided field selection, reusing the operation's variable definitions and argument passthrough. Supports nested selections and $on inline fragments for interface/union types. Not yet wired into endpoints (snapshot-safe). Co-Authored-By: Claude <noreply@anthropic.com>
GraphQL operations now accept an optional `select`; when provided, the SDK builds
the query from it via buildGraphqlQuery (reusing the operation's variableDefinitions
+ arguments) instead of the baked full query, and `select` is never sent as a
variable. Adds a graphql CoreUtility (on-demand), a `select?` property on graphql
request wrappers, and a `{ select? }` param for no-arg ops. Gated on graphql
transport; non-graphql output unchanged. v1 types select as core GraphqlSelection
(per-type Select types are a follow-up).
Co-Authored-By: Claude <noreply@anthropic.com>
Generate a <Type>Select interface per object/union type (scalar->boolean, object->boolean|<Child>Select, undiscriminated unions->__typename + $on map) and type each graphql operation's select? as its response type's Select instead of the loose GraphqlSelection. Gives autocomplete + compile-time field checking. Gated on graphql endpoints (no change to non-graphql SDKs); recursion handled. Co-Authored-By: Claude <noreply@anthropic.com>
…errors When a GraphQL response carries a non-empty errors[] (HTTP 200), throwing-mode SDKs now throw a dedicated core GraphqlError with a typed errors array (+ data, rawResponse) instead of the generic statusCode-200 error, so callers can catch it and read the GraphQL errors directly. Gated on graphql transport; non-graphql output unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
Adds an optional `max-depth` field to the GraphQL spec in generators.yml, threaded from the public schema through the workspace loader to the IR converter's query generation (default 4 when unset). Lets users tune the depth of the auto-generated default selection query per GraphQL API. Co-Authored-By: Claude <noreply@anthropic.com>
Generates real streaming subscription methods for GraphQL SDKs. Subscription root fields now produce an AsyncIterableIterator<T> consumed with for-await, backed by a new subscribeGraphql runtime helper that speaks the graphql-transport-ws subprotocol over a WebSocket (built on the existing WS primitive, no new dependency). Subscriptions support the same args and field selection as queries; queries/mutations are unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
…mitted The subscribeGraphql helper imports 'ws' for a WebSocket implementation on node/bun/deno, but GraphqlUtils never registered it, so a freshly generated GraphQL SDK failed 'npm install && tsc' with TS2307 (cannot find module 'ws'). Register ws/@types/ws via the manifest's addDependencies, mirroring the existing websocket core utility. Co-Authored-By: Claude <noreply@anthropic.com>
The dedicated GraphqlError (and GraphqlResponseError/GraphqlSelection types) had no exports.ts under core/graphql, so PublicExportsManager never surfaced them — consumers could not import GraphqlError to instanceof-check caught errors, defeating the purpose of the dedicated type. Add core/graphql/exports.ts so the symbols flow through core/exports -> src/exports -> the SDK root. Co-Authored-By: Claude <noreply@anthropic.com>
Apply biome formatting, drop an unused import in SelectTypeGenerator, and replace non-null assertions in the generateSelectionQuery test with explicit guards (noNonNullAssertion). No behavior change. Co-Authored-By: Claude <noreply@anthropic.com>
Adds a GraphQL schema test-definition (queries, mutations, subscriptions, union, enum, cyclic types, custom scalar) and the ts-sdk snapshot, giving GraphQL SDK generation end-to-end CI coverage. The ts- prefix scopes the fixture to the TypeScript generator (only language with GraphQL support). Co-Authored-By: Claude <noreply@anthropic.com>
…efault) GraphQL operations now require a `select` instead of defaulting to a baked "select everything" query (which over-fetched and, for deep schemas like BigCommerce, shipped ~29KB query strings). The query is always built at runtime from the caller's typed selection. Each <Type>Select field now carries its GraphQL description as JSDoc so the required selection is discoverable via editor hover/autocomplete. Gated on the graphql transport — non-GraphQL SDKs are byte-identical (547+164 generator tests pass, no snapshot drift). Co-Authored-By: Claude <noreply@anthropic.com>
With `select` required, the SDK never sends a baked default query, so the `max-depth` generators.yml option (which only tuned that baked query) no longer affects output. Remove it from the public config and the workspace threading to keep the surface clean. The converter keeps an internal default depth for the IR-level transport.query fallback. Co-Authored-By: Claude <noreply@anthropic.com>
Adds GraphqlFieldArgument / GraphqlObjectFieldArguments types and an optional graphqlFieldArguments map on IntermediateRepresentation (keyed by TypeId). The GraphQL importer populates it from each object/interface field's arguments (name, value type, GraphQL SDL type); the merge carries it across additionalIrs. Foundation for typed $args on nested selections (pagination/filter/sort). IR 67.9.0. Additive and GraphQL-only. Co-Authored-By: Claude <noreply@anthropic.com>
Adds a reserved `$args` key (alongside `$on`) so callers can pass nested-field
arguments — pagination, filtering, sorting — on any field in a `select`:
posts: { $args: { first: 10, sortBy: "NEWEST" }, edges: { node: { title: true } } }
- Generates typed <Type><Field>Args interfaces and widens select field types to
`boolean | (<Child>Select & { $args?: <Args> })`.
- Emits a per-SDK arg-type registry (GraphqlArgTypes) so the runtime can resolve
each arg's GraphQL SDL type and descend nested types.
- buildGraphqlQuery now returns { query, variables } and emits nested args as
declared GraphQL variables (gqlArg<N>) — enums/inputs/lists serialize correctly.
- Gated on graphql; non-GraphQL SDKs byte-identical (547 tests, 0 snapshot drift).
Co-Authored-By: Claude <noreply@anthropic.com>
Rename the reserved selection keys $on -> __on and $args -> __args to match the PRD convention (GraphQL reserves __; $ reads as GraphQL variable syntax). Gated on graphql; non-GraphQL SDKs byte-identical (547 tests, 0 snapshot drift). Co-Authored-By: Claude <noreply@anthropic.com>
GraphQL operations now take the operation arguments and the field selection as separate parameters: field(args, selection) — or field(selection) for no-arg operations — instead of a single request object bundling select. Selection is no longer part of the request wrapper. Adds a no-arg fixture op (viewer) for coverage. Gated on graphql; non-GraphQL SDKs byte-identical (547 tests, 0 drift). Co-Authored-By: Claude <noreply@anthropic.com>
GraphQL methods now capture the caller's selection literal as a generic S and
return Result<Model, S> — the result type is narrowed to exactly the selected
fields (unselected fields are absent from the type, not present-but-undefined).
Adds the genql/Zeus-style Result<T,S> core type (handles our optional-nullability
convention, arrays, branded scalars, and __on union/interface distribution).
Subscriptions return AsyncIterableIterator<Result<Model,S>>. Gated on graphql;
non-GraphQL SDKs byte-identical (547 tests, 0 snapshot drift). Verified: selecting
{id} narrows the result and accessing an unselected field is a compile error.
Co-Authored-By: Claude <noreply@anthropic.com>
…ions
GraphQL query/mutation methods now return a GraphqlResponse<T> envelope
({ data, errors }) instead of throwing on GraphQL errors[] — GraphQL is a
partial-success protocol (a 200 can carry both). Transport failures still throw;
`throwOnError` is an opt-in request option that throws GraphqlError when errors
are present. Subscriptions still yield Result<Model,S> values. Gated on graphql;
non-GraphQL SDKs byte-identical (547 tests, 0 snapshot drift).
Co-Authored-By: Claude <noreply@anthropic.com>
… select)
The selection parameter is now optional: omitting it returns a default selection
of the return type's safe scalar fields (excludes deprecated + relations; falls
back to { __typename } for scalar-less/union types), precisely typed via a
generated <Type>DefaultSelection as-const + generic/param default. A no-selection
call compiles and narrows to the default (PRD G7 'index toward working states'),
while an explicit selection still narrows to exactly what was asked. Gated on
graphql; non-GraphQL SDKs byte-identical (547 tests, 0 snapshot drift).
Co-Authored-By: Claude <noreply@anthropic.com>
Result<T,S> only added __typename in the union (__on) path, so selecting
__typename on a plain object (and the safe-scalar default { __typename } for
scalar-less types like connection wrappers) produced an empty result type.
Intersect the non-polymorphic Result with GraphqlTypenameResult<S> so
{ __typename: true } yields { __typename: string } on any object.
Co-Authored-By: Claude <noreply@anthropic.com>
A GraphQL schema cannot express authentication, so the GraphQL->IR converter marks every operation auth:false. When the surrounding API declares auth (inherited from a combined OpenAPI spec, or via generators.yml api.auth / auth-schemes) those schemes reach the merged IR but never reached the GraphQL operations. Add applyAuthToGraphqlEndpoints (ir-utils): a post-merge pass that, when the IR has auth schemes, flips GraphQL query/mutation endpoints to auth:true so Fern's existing auth machinery generates the typed param and injects the header on the GraphQL POST, exactly as for REST. Subscriptions are intentionally excluded - their synchronous AsyncIterableIterator method cannot await the auth provider inline. The converter's auth:false default is untouched, so the change is fully reversible by removing the helper and its two call sites. Verified end-to-end: ts-graphql fixture now declares auth: bearer; regenerated SDK injects Authorization: Bearer on query/mutation POSTs. Co-Authored-By: Claude <noreply@anthropic.com>
Subscriptions were excluded from GraphQL auth because the generated method returns an AsyncIterableIterator synchronously and cannot await the auth provider inline. Resolve auth lazily inside the WebSocket connect instead. - subscribeGraphql: connectionParams/headers may now be (possibly async) suppliers, resolved on connect alongside the url. The same supplier is passed to both fields and resolved once. Resolved headers are also sent on the WebSocket upgrade request (node ws), not just the connection_init payload. - Subscription codegen: when the endpoint is authed, wrap header construction (which awaits the auth provider) in an async supplier passed to subscribeGraphql; the method itself stays synchronous. No-auth subscriptions are unchanged. - applyAuthToGraphqlEndpoints: drop the subscription exclusion so subscriptions are authed alongside query/mutation. Verified: ts-graphql fixture regenerated and tsc-clean; subscribeGraphql runtime tests (incl. new async-supplier case) and 547 client-class-generator tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
…ections
PRD §10.5: hoisting a selection object widens its literal types
({ id: true } -> { id: boolean }), which breaks selection-result inference. Emit
a `fragmentOn` helper alongside the <Type>Select types — one const-generic
identity function per type (`Type: <const S extends TypeSelect>(selection: S): S`)
— so a reused selection keeps its precise literal type and still drives result
narrowing when passed to an operation.
Co-Authored-By: Claude <noreply@anthropic.com>
PRD §6.7/§7: the GraphQL->IR converter now reads @deprecated(reason:) (graphql-js deprecationReason) on object/interface/input fields, enum values, arguments, and root operations into the IR availability. - Deprecated fields get a @deprecated JSDoc tag in their <Type>Select entry, so editors flag them where the consumer opts into fetching them. - Deprecated fields stay excluded from the default selection (already enforced), so regeneration never silently re-fetches a newly-deprecated field. - Deprecated operations carry @deprecated on their method via the existing availability-docs path. Model-type property/enum @deprecated emission is a shared TS-generator gap (affects REST too) and is left as a follow-up. Fixture schema gains a deprecated field + enum value for coverage. Co-Authored-By: Claude <noreply@anthropic.com>
PRD §7: normalize both GraphQL input formats to one GraphQLSchema. The converter
now detects introspection-query JSON (a .json file, or content parsing to an
introspection result; both the bare { __schema } and { data: { __schema } }
envelope are handled) and builds via graphql-js buildClientSchema, falling back
to buildSchema for SDL. Extracted into a testable buildGraphQLSchemaFromString
helper with unit coverage for SDL and both introspection shapes.
Co-Authored-By: Claude <noreply@anthropic.com>
PRD §6.5: power users who maintain their own GraphQL documents can call
client.raw<TResult, TVariables>(query, variables, requestOptions) and get back
the same { data, errors } envelope as the typed operations (with throwOnError
opt-in). Reuses the root client's passthrough fetch, so auth, retries, base-url
resolution, headers, and logging all apply. Emitted on the root client whenever
the IR exposes any GraphQL query/mutation operation.
Co-Authored-By: Claude <noreply@anthropic.com>
PRD §10.11 server-acceptance guardrails. The bulk of §10.11 is already met by the existing design: documents are static/deterministic per call shape (arguments are positional $gqlArgN variables, never inlined), so they are APQ/allowlist-friendly, and selection depth is already bounded at generation time. This adds the remaining piece: buildGraphqlQuery now fails fast with a clear error when the built document exceeds maxDocumentLength (GraphqlArgContext option; default 1,000,000 chars, set 0 to disable), rather than letting an oversized runtime-built selection become one request the server rejects. Co-Authored-By: Claude <noreply@anthropic.com>
PRD §10.6 (built-in defaults). JSON/JSONObject custom scalars now map to an untyped value (unknown) instead of a misleading string; well-known scalars keep their native-ish primitive mappings (DateTime/Date/UUID/Long/BigInt). Any unrecognized custom scalar falls back to string and is reported with a generation-time warning naming the scalar, so the publisher sees exactly which scalars need attention rather than getting a silent fallback. No new config surface (defaults only). Fixture gains JSON + an unrecognized scalar for coverage. Co-Authored-By: Claude <noreply@anthropic.com>
PRD §6.3: `{ __all: true }` selects every scalar leaf field of a type, useful at
nested levels the default selection does not reach. Each <Type>Select gains an
optional __all; the Result engine pulls in all scalar leaves (object relations
must still be selected explicitly), detecting scalars by primitive-assignability
so branded custom scalars (DateTime = string & {...}), enums, scalar arrays, and
unknown/JSON are classified correctly. Runtime: the arg-type registry now emits
every field (scalar leaves as {}), so buildGraphqlQuery expands __all to the
type's scalar fields (deduped against explicit selections) with no separate
registry. Verified with a standalone type-level test of the Result engine plus
runtime unit tests.
Co-Authored-By: Claude <noreply@anthropic.com>
GraphQL SDK generation
Adds end-to-end GraphQL → SDK generation to Fern (TypeScript), so GraphQL APIs produce typed SDKs alongside OpenAPI/gRPC. This is the single consolidated PR (the earlier stacked generator PR #16731 is folded in here).
How it works
Each GraphQL root field is modeled as a synthetic HTTP operation carrying a new
Transport.graphql. The existing SDK machinery (types, clients, auth, request/response models, errors) is reused; only the transport envelope is GraphQL-specific. At runtime each operation issues a singlePOST /graphqlwith{ query, variables }and returns the unwrappeddata[operationName].What's included
IR / pipeline
Transport.graphqlvariant +GraphqlTransport(query,operationType,operationName, andvariableDefinitions/argumentsfor future field selection). IR 67.8.0.@fern-api/graphql-to-fdrconverter: GraphQL schema → IR (objects/enums/unions/interfaces/inputs; custom scalars mapped to typed primitives —DateTime,UUID,Long, …; depth-limited auto query generation; wrappersdkRequestso field args flow as GraphQLvariables).OSSWorkspace.toFernWorkspace()attaches the GraphQL IR andgenerateIntermediateRepresentation()merges it — so GraphQL flows through the standardfern generatepipeline (no--from-openapiflag needed).README.md+reference.md+ snippets (matching the OpenAPI SDK experience).TypeScript generator (all gated on
transport === "graphql"; non-GraphQL output byte-identical, no generator snapshot changes){ query, variables }; response unwrapdata[operationName](optional-chained).errors[]surfaced through the SDK error channel.Release metadata: CLI changelog entry, generator
versions.yml→3.74.0, IR changelog67.8.0.Testing
BigcommerceStorefrontError(HTTP 401). A simulated GraphQL-200-with-errors[]surfaces cleanly instead of aTypeError.Known follow-ups (not in this PR)
selectparam): IR/converter foundation is in this PR; the generator-sideselectparam + runtime query builder is the next step.Generated with Claude Code