Skip to content

feat(typescript): GraphQL SDK generation#16730

Open
cadesark wants to merge 48 commits into
mainfrom
graphql-sdk-pipeline
Open

feat(typescript): GraphQL SDK generation#16730
cadesark wants to merge 48 commits into
mainfrom
graphql-sdk-pipeline

Conversation

@cadesark

@cadesark cadesark commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

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 single POST /graphql with { query, variables } and returns the unwrapped data[operationName].

What's included

IR / pipeline

  • New Transport.graphql variant + GraphqlTransport (query, operationType, operationName, and variableDefinitions/arguments for future field selection). IR 67.8.0.
  • @fern-api/graphql-to-fdr converter: GraphQL schema → IR (objects/enums/unions/interfaces/inputs; custom scalars mapped to typed primitives — DateTime, UUID, Long, …; depth-limited auto query generation; wrapper sdkRequest so field args flow as GraphQL variables).
  • OSSWorkspace.toFernWorkspace() attaches the GraphQL IR and generateIntermediateRepresentation() merges it — so GraphQL flows through the standard fern generate pipeline (no --from-openapi flag needed).
  • Autogenerated examples for GraphQL operations → rich README.md + reference.md + snippets (matching the OpenAPI SDK experience).
  • Removed the "GraphQL SDKs are not supported" checker warning.

TypeScript generator (all gated on transport === "graphql"; non-GraphQL output byte-identical, no generator snapshot changes)

  • Request envelope { query, variables }; response unwrap data[operationName] (optional-chained).
  • GraphQL errors[] surfaced through the SDK error channel.
  • Empty wire-test files skipped for GraphQL services.

Release metadata: CLI changelog entry, generator versions.yml3.74.0, IR changelog 67.8.0.

Testing

  • Full repo compiles (160/160). Non-GraphQL generator snapshots unchanged.
  • Generated the BigCommerce Storefront SDK from an 11.5k-line schema (692 types, 64 ops) → 805 files, strict-compiles.
  • A real network call to a live BigCommerce store (no token) surfaces a clean typed BigcommerceStorefrontError (HTTP 401). A simulated GraphQL-200-with-errors[] surfaces cleanly instead of a TypeError.

Known follow-ups (not in this PR)

  • Field selection (runtime select param): IR/converter foundation is in this PR; the generator-side select param + runtime query builder is the next step.
  • Configurable query depth; dedicated GraphQL error type; interface inline-fragment/polymorphism; subscriptions; other languages (Python/Go/Java/C#).

Generated with Claude Code

fern-support and others added 7 commits June 25, 2026 19:50
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>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Open in Devin Review

union:
http: {}
grpc: GrpcTransport
graphql: graphql.GraphqlTransport

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +583 to +594
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);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Docs Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-06-25T05:19:47Z).

Fixture main PR Delta
docs 220.4s (n=5) 268.8s (35 versions) +48.4s (+22.0%)

Docs generation runs fern generate --docs --preview end-to-end against the benchmark fixture with 35 API versions (each version: markdown processing + OpenAPI-to-IR + FDR upload).
Delta is computed against the nightly baseline on main.
Baseline from nightly run(s) on main (latest: 2026-06-25T05:19:47Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-06-26 05:11 UTC

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-06-25T05:19:47Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
csharp-sdk square 71s (n=5) 109s (n=5) 65s -6s (-8.5%)
go-sdk square 134s (n=5) 284s (n=5) 129s -5s (-3.7%)
java-sdk square 213s (n=5) 282s (n=5) 206s -7s (-3.3%)
php-sdk square 59s (n=5) 84s (n=5) 56s -3s (-5.1%)
python-sdk square 136s (n=5) 241s (n=5) 210s +74s (+54.4%)
ruby-sdk-v2 square 91s (n=5) 129s (n=5) 86s -5s (-5.5%)
rust-sdk square 183s (n=5) 173s (n=5) 157s -26s (-14.2%)
swift-sdk square 57s (n=5) 431s (n=5) 52s -5s (-8.8%)
ts-sdk square 236s (n=5) 239s (n=5) 224s -12s (-5.1%)

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 fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-06-25T05:19:47Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-06-26 05:12 UTC

cadesark and others added 10 commits June 25, 2026 19:21
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>
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>
…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>
@fern-support fern-support requested a review from Swimburger as a code owner June 26, 2026 01:03
@cadesark cadesark changed the title feat(graphql): GraphQL → IR conversion pipeline + workspace wiring feat(graphql): GraphQL SDK generation (TypeScript) Jun 26, 2026
cadesark and others added 6 commits June 25, 2026 22:02
…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>
cadesark and others added 2 commits June 26, 2026 00:21
…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>
@cadesark cadesark changed the title feat(graphql): GraphQL SDK generation (TypeScript) feat(typescript): GraphQL SDK generation Jun 26, 2026
cadesark and others added 22 commits June 26, 2026 00:49
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants