Skip to content

feat: add Apollo 4.2 modern signatures to useQuery/useMutation - #6

Merged
johanrd merged 13 commits into
mainfrom
feat/apollo-modern-signatures
Sep 24, 2026
Merged

johanrd merged 13 commits into
mainfrom
feat/apollo-modern-signatures

Conversation

@johanrd

@johanrd johanrd commented May 27, 2026 •

Copy link
Copy Markdown
Owner

Adds opt-in Apollo Client 4.2 "modern signatures" to useQuery/useMutation, mirroring Apollo Client 4.2.0 (PR #13132). Classic remains the default — no source migration required for existing users (only a peer-dep bump to @apollo/client@^4.2.0; call sites compile unchanged).

Why

Per Apollo's 4.2 release notes (CHANGELOG):

Apollo Client 4.2 introduces two signature styles for methods and hooks. All signatures previously present are now "classic" signatures, and a new set of "modern" signatures are added alongside them.

The switch is global, via a TypeOverrides augmentation:

declare module '@apollo/client' {
  export interface TypeOverrides { signatureStyle: 'modern' }
}

Until this PR, glimmer-apollo only exposed the classic shape, so consumers who opted in globally still got classic ergonomics out of the wrapper.

Before / After

A query with a TypedDocumentNode<UserInfoQuery, UserInfoQueryVariables>:

// Before — generics are mandatory, the document type is duplicated
const user = useQuery<UserInfoQuery, UserInfoQueryVariables>(this, () => [
  USER_INFO,
  { variables: { id: '1' } },
]);

// After (modern) — TData/TVariables inferred from the document
const user = useQuery(this, () => [
  USER_INFO,
  { variables: { id: '1' } },
]);

A mutation with required variables:

// Before — must pass <TData, TVariables>
const login = useMutation<LoginMutation, LoginMutationVariables>(this, () => [LOGIN]);
await login.mutate({ username: 'a', password: 'b' });

// After (modern) — same call site, no explicit generics
const login = useMutation(this, () => [LOGIN]);
await login.mutate({ username: 'a', password: 'b' });

Real type-safety wins (not just less typing)

Under modern, errors that previously slipped through when call sites used wide defaults now surface at the call site:

  • variables at the options site is checked against the document. A schema-renamed or wrong-typed argument becomes a compile error. Verified by test-app/tests/unit/types/query-types-test.ts (a @ts-expect-error row for { id: 123 } against an id: string document).
  • .mutate(vars) checks vars against the document's TVariables. This check exists under classic too, but only when consumers explicitly pass generics; under modern the same check holds for inferred call sites without the boilerplate.

(The plan originally proposed a third item — narrowing .data to a deep-partial shape when errorPolicy: 'all' — but that requires a TErrorPolicy generic on QueryResource that this PR does not add. Out of scope.)

Opting in

// In a global types file
declare module '@apollo/client' {
  export interface TypeOverrides { signatureStyle: 'modern' }
}

That's it. After the augmentation, drop explicit generics anywhere a TypedDocumentNode is available.

Migration notes for modern opt-in

For consumers that opt in and use a single-generic call style with narrowly-typed documents, the modern signature is stricter about a few patterns that classic silently accepted. These are deliberate — the point of opting in is catching them — but listed here as a heads-up:

  • Single-generic useQuery<UserInfoQuery> should drop the generic and let TypedDocumentNode infer. The single-generic shape no longer compiles under modern because TVariables defaults to OperationVariables, and TypedDocumentNode<UserInfoQuery, UserInfoQueryVariables> does not satisfy TypedDocumentNode<UserInfoQuery, OperationVariables> (TVariables is contravariant). Empirically verified against proapi-webapp/UI/console during the trial migration.
  • Plain DocumentNode (no TypedDocumentNode) still works under modern, just with unknown for TData/TVariables — no migration needed.

Notes

  • The @apollo/client/utilities/internal import path is listed as a public subpath in Apollo's package.json exports, but the internal naming and lack of stability commitments suggest it's intended for advanced consumers. Apollo's own first-party hooks import NoInfer and SignatureStyle from this path (verified at @apollo/client/react/hooks/useQuery.d.ts:5).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates glimmer-apollo’s Apollo Client integration types to align with Apollo Client 4.2 signature-style support and improve TypedDocumentNode inference for query, mutation, and subscription APIs.

Changes:

  • Adds Classic/Modern signature namespaces for useQuery and useMutation.
  • Extends positional args to accept TypedDocumentNode and loosens mutation variables to optional partials.
  • Bumps Apollo Client to ^4.2.0 and adds type-level tests using expect-type.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
glimmer-apollo/src/-private/usables.ts Adds signature-style namespaces and typed Modern/Classic call signatures.
glimmer-apollo/src/-private/query.ts Allows TypedDocumentNode in query positional args.
glimmer-apollo/src/-private/mutation.ts Allows TypedDocumentNode, optional partial variables, and partial mutate args.
glimmer-apollo/src/-private/subscription.ts Allows TypedDocumentNode in subscription positional args.
glimmer-apollo/package.json Raises Apollo Client peer/dev dependency floor to 4.2.
test-app/package.json Updates Apollo Client and adds expect-type for type tests.
test-app/tests/unit/types/query-types-test.ts Adds type assertions for query signature inference and compatibility.
test-app/tests/unit/types/mutation-types-test.ts Adds type assertions for mutation inference and optional variables.
test-app/tests/unit/types/subscription-types-test.ts Adds type assertions for subscription TypedDocumentNode inference.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comment thread docs/modern-signatures.md Outdated
johanrd and others added 13 commits July 26, 2026 22:03
ember-try release/beta/canary now resolve to ember-source 7.x, which the
previous toolchain could not build: ember-cli 6.9 fails in
_initVendorFiles on ember 7's package layout, and the hardcoded
ember-template-compiler specifier resolves to a nonexistent path through
ember 7's exports glob.

Bump ember-cli, embroider packages and
babel-plugin-ember-template-compilation to ember-7-aware versions, and
drop compilerPath so the babel plugin auto-detects the compiler location
per scenario (ember 7 layout first, ember <= 6 fallback).
fix(test-app): support ember-source 7 in ember-try scenarios
Mirrors Apollo Client 4.2's `Signatures.Classic`/`Signatures.Modern`
pattern (gated by `TypeOverrides.signatureStyle`) so consumers can drop
explicit `<TData, TVariables>` generics and let TypeScript infer from a
`TypedDocumentNode`. Classic remains the default and is unchanged.

`useSubscription` gets the same `TypedDocumentNode` inference improvement
but no Classic/Modern split, matching Apollo 4.2's own approach for subs.

`MutationOptions.variables` is now `Partial<TVariables>` and optional,
subsuming the pnpm patch shipped in the consuming app.

Peer dep bumped to `@apollo/client@^4.2.0` (`SignatureStyle` and
`NoInfer` live at `@apollo/client/utilities/internal`, only available in
4.2+).
… test

- Remove stale eslint-disable directives (`qunit/no-identical-names` is not
  in the CI plugin set; `@typescript-eslint/no-unused-vars` directive was
  reported unused) from the three new type-test files.
- Apply prettier formatting to the type-test files.
- Extend the file-level eslint-disable in the existing mutation-test.ts to
  also silence `@typescript-eslint/no-unsafe-member-access`: Apollo 4.2's
  stricter `MutateOptions` type doesn't unwrap cleanly through sinon's
  `spy.args` typing, causing member access on captured call args to flag
  as unsafe. Behavior assertions in tests don't need stricter typing here.
Revert the Partial<TVariables> widening on MutationOptions.variables and
mutate(vars?). Modern-default in d6884cc already lets call sites omit
generics, so partials at the options site were no longer needed and just
hid genuine schema-renamed-arg errors.
Add docs/modern-signatures.md explaining the TypeOverrides augmentation,
before/after examples for each hook, the concrete type-safety wins
(variables checked at the options site, .mutate(vars) checked without
explicit generics), and migration gotchas for opted-in consumers
(single-generic call sites, plain DocumentNode behavior).

Add a one-line callout near the top of queries/mutations/subscriptions
pages linking to the new page. Existing examples stay classic so they
remain accurate for the default (no TypeOverrides) consumer.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@josemarluedke
josemarluedke force-pushed the feat/apollo-modern-signatures branch from fcb1006 to b910851 Compare August 10, 2026 18:44
@johanrd
johanrd merged commit 5525224 into main Sep 24, 2026
7 checks passed
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.

3 participants