Skip to content

feat(@tanstack/query): generate mutation keys for mutation options#3858

Merged
mrlubos merged 10 commits into
hey-api:mainfrom
slmnsh:main
May 14, 2026
Merged

feat(@tanstack/query): generate mutation keys for mutation options#3858
mrlubos merged 10 commits into
hey-api:mainfrom
slmnsh:main

Conversation

@slmnsh
Copy link
Copy Markdown
Contributor

@slmnsh slmnsh commented May 9, 2026

Fixes #3845

@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 9, 2026

🦋 Changeset detected

Latest commit: 5ccc2bc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hey-api/openapi-ts Patch

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

@vercel
Copy link
Copy Markdown

vercel Bot commented May 9, 2026

@slmnsh is attempting to deploy a commit to the Hey API Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. feature 🚀 Feature request. labels May 9, 2026
@slmnsh
Copy link
Copy Markdown
Contributor Author

slmnsh commented May 9, 2026

@mrlubos Can you check?

@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

❌ Patch coverage is 1.63934% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.45%. Comparing base (6353658) to head (5ccc2bc).

Files with missing lines Patch % Lines
...ts/src/plugins/@tanstack/query-core/mutationKey.ts 2.85% 33 Missing and 1 partial ⚠️
...plugins/@tanstack/query-core/v5/mutationOptions.ts 0.00% 8 Missing and 2 partials ⚠️
...pi-ts/src/plugins/@tanstack/query-core/queryKey.ts 0.00% 4 Missing ⚠️
...ins/@tanstack/angular-query-experimental/config.ts 0.00% 2 Missing ⚠️
...pi-ts/src/plugins/@tanstack/preact-query/config.ts 0.00% 2 Missing ⚠️
...api-ts/src/plugins/@tanstack/react-query/config.ts 0.00% 2 Missing ⚠️
...api-ts/src/plugins/@tanstack/solid-query/config.ts 0.00% 2 Missing ⚠️
...pi-ts/src/plugins/@tanstack/svelte-query/config.ts 0.00% 2 Missing ⚠️
...enapi-ts/src/plugins/@tanstack/vue-query/config.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3858      +/-   ##
==========================================
- Coverage   39.58%   39.45%   -0.13%     
==========================================
  Files         532      533       +1     
  Lines       19581    19636      +55     
  Branches     5835     5846      +11     
==========================================
- Hits         7751     7748       -3     
- Misses       9582     9637      +55     
- Partials     2248     2251       +3     
Flag Coverage Δ
unittests 39.45% <1.63%> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Important

Consider wiring up mutationKeys.enabled, regenerating examples, adding a changeset, and documenting the new option before merging. The implementation faithfully mirrors the existing queryKey machinery and the snapshots are consistent across all six framework plugins, but the new public config has a dead flag and no test coverage beyond the default cell.

TL;DR — Adds a mutationKeys plugin config and emits a per-operation <name>MutationKey() factory plus a shared MutationKey<TOptions> type and createMutationKey helper for every Tanstack Query plugin (angular/preact/react/solid/svelte/vue). Every mutationOptions(...) result now carries a mutationKey tuple, mirroring how queryOptions already carries a queryKey. Resolves #3845.

Key changes

  • Add shared mutation-key emitters — new mutationKey.ts parallels queryKey.ts with createMutationKeyType, createMutationKeyFunction, and mutationKeyStatement.
  • Wire mutation key into mutationOptions — emit a <op>MutationKey factory and add mutationKey: <op>MutationKey(options) to the returned options object.
  • Add mutationKeys user config — six framework types.ts/config.ts files gain mutationKeys: boolean | NameTransformer | { case, enabled, name, tags } with defaults { enabled: true, name: '{{name}}MutationKey', tags: false }.
  • Regenerate 86 tanstack-query snapshots — every existing fixture under packages/openapi-ts-tests/tanstack-query/v5/__snapshots__/** now contains the new type, helper, and per-operation key factory.

Summary | 92 files | 1 commit | base: main1285e02


mutationKeys.enabled is silently ignored

Before: users could opt out of queryKeys/infiniteQueryKeys only by knowing the existing flags don't actually gate emission either.
After: mutationKeys adds a third user-visible enabled flag with the same problem — mutationKeys: false and { enabled: false } parse correctly through the mappers and resolver, but no consumer ever reads plugin.config.mutationKeys.enabled.

createMutationOptions in packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts unconditionally calls mutationKeyStatement(...) and assigns .prop('mutationKey', $(symbolMutationKey).call('options')) regardless of the flag. mutationKey.ts only reads config.tags. A user setting mutationKeys: false still gets the full MutationKey type, the createMutationKey helper, every <op>MutationKey() export, and the mutationKey field on every mutation. Either gate the emissions on plugin.config.mutationKeys.enabled (matching the precedent in packages/openapi-ts/src/plugins/@pinia/colada/queryOptions.ts:48) or drop the flag from the type and resolver. The same sin exists for queryKeys.enabled in this plugin today, but this PR adds a fresh instance.

mutationOptions.ts · mutationKey.ts · react-query/types.ts


Test matrix only covers the default cell

Before: a four-axis config (enabled, name, tags, case) shipped with no behavioural verification.
After: the same is still true — 86 snapshot diffs all use defaults, so three of the four axes are unproven.

A grep for a 3-arg createMutationKey('id', options, [ literal across every snapshot returns zero hits, so the tags: true branch in mutationKey.ts (which emits $.array().elements(...operation.tags) as the third arg) is never exercised. The name-builder scenario in packages/openapi-ts-tests/tanstack-query/v5/test/plugins.test.ts overrides name for infiniteQueryKeys, queryKeys, queryOptions, mutationOptions, etc. but conspicuously skips mutationKeys — the snapshot still emits fooPostMutationKey instead of a custom name. No scenario sets mutationKeys: false (which would have surfaced the dead-flag bug above), and no scenario overrides case. Adding three small scenarios — mutationKeys: false, mutationKeys: { tags: true }, and mutationKeys: { name: '{{name}}F' } — would lock in the contract documented in the JSDoc.

plugins.test.ts · mutationKey.ts


Examples not regenerated, so examples:check will fail in CI

Before: examples/openapi-ts-tanstack-{react,vue,svelte,angular-query-experimental}-query/src/client/@tanstack/*.gen.ts carry the old output.
After: they still do — a grep for MutationKey|mutationKey: under examples/ returns nothing.

Default mutationKeys: { enabled: true } means every regenerated tanstack example will diff. scripts/examples-check.sh calls git diff --quiet after running pnpm examples:generate and exits non-zero on any drift. Run pnpm examples:generate and commit the four updated @tanstack/*.gen.ts files before this PR can land green.

scripts/examples-check.sh · examples/openapi-ts-tanstack-react-query


Missing changeset and docs

Before: .changeset/ only contains the README, changelog.js, and config.json.
After: still no entry for this PR.

This repo uses Changesets — without a .md entry the next release won't bump or surface the new config in CHANGELOG. Add one at minor for @hey-api/openapi-ts. Separately, docs/openapi-ts/plugins/tanstack-query.md has no mention of mutationKey anywhere; the symmetrical Queries section documents queryKeys, the per-operation getXQueryKey() accessor, the tags sub-option, and case/name customization (around lines 282–446). Mirror that structure for mutations, and consider a note in docs/openapi-ts/migrating.md since every existing useMutation({ ...fooMutation() }) consumer now silently inherits a default mutationKey tuple where there used to be undefined.

docs/openapi-ts/plugins/tanstack-query.md · docs/openapi-ts/migrating.md


Smaller notes

  • The exported type MutationKey<TOptions extends Options> shadows @tanstack/react-query's own MutationKey type for any user who re-exports both from the same module. This already happens for QueryKey, so it's consistent rather than novel — but worth a doc callout. (Observable in every regenerated snapshot, e.g. packages/openapi-ts-tests/tanstack-query/v5/__snapshots__/2.0.x/plugins/asClass/@tanstack/react-query.gen.ts:57.)
  • The createMutationKey emission is guarded by plugin.querySymbol({ category: 'utility', resource: 'createMutationKey', tool: plugin.name }) but the MutationKey type is created in the same conditional. If anything else ever emits a MutationKey type symbol first, the guard would skip the helper while leaving a foreign type referenced via referenceSymbol on mutationOptions.ts:39. No current emitter does this; flagging only as a latent fragility shared with queryKey.
  • Positional-arg passing in createMutationKeyLiteral is correct — createMutationKey has signature (id, options?, tags?), distinct from createQueryKey's (id, options?, infinite?, tags?), so passing tagsArray as the third arg is right. Worth a comment if it ever causes confusion during review.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

return data;
}
},
mutationKey: patchApiVbyApiVersionNoTagMutationKey(options)
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.

This mutationKey line is emitted unconditionally in createMutationOptions (see packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts:117). Setting mutationKeys: false or { enabled: false } does not suppress it because nothing reads plugin.config.mutationKeys.enabled. Gate this assignment (and the upstream mutationKeyStatement(...) + createMutationKeyType/createMutationKeyFunction emissions) on the enabled flag, or remove the flag from the public type.

fix(@tanstack/query): mutation option types when it has required
operation data
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 9, 2026

Open in StackBlitz

@hey-api/codegen-core

npm i https://pkg.pr.new/@hey-api/codegen-core@3858

@hey-api/json-schema-ref-parser

npm i https://pkg.pr.new/@hey-api/json-schema-ref-parser@3858

@hey-api/nuxt

npm i https://pkg.pr.new/@hey-api/nuxt@3858

@hey-api/openapi-ts

npm i https://pkg.pr.new/@hey-api/openapi-ts@3858

@hey-api/shared

npm i https://pkg.pr.new/@hey-api/shared@3858

@hey-api/spec-types

npm i https://pkg.pr.new/@hey-api/spec-types@3858

@hey-api/types

npm i https://pkg.pr.new/@hey-api/types@3858

@hey-api/vite-plugin

npm i https://pkg.pr.new/@hey-api/vite-plugin@3858

commit: 5ccc2bc

@mrlubos
Copy link
Copy Markdown
Member

mrlubos commented May 14, 2026

@pullfrog review the final version please

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

TL;DR — Adds per-operation mutationKey helpers and a mutationKey property on every generated mutationOptions, mirroring the existing queryKey pipeline so users can target mutations from the cache (cancel, set defaults, etc.). The shape ([Pick<TOptions, 'baseUrl' | 'body' | 'headers' | 'path' | 'query'> & { _id; tags? }]) is symmetric with queryKey minus the query-only _infinite discriminator, and the implementation is cleanly factored along the same lines as queryKey.ts.

Key changes

  • Add createMutationKey/MutationKey codegen — new mutationKey.ts in query-core produces a per-operation …MutationKey exported helper plus a shared createMutationKey builder and MutationKey<TOptions> type.
  • Wire mutationKey into mutationOptionsmutationOptions.ts now emits the per-operation key statement and sets mutationKey: …MutationKey(options) on the returned options object.
  • Add mutationKeys plugin config option — all six TanStack plugin variants (angular-query-experimental, preact-query, react-query, solid-query, svelte-query, vue-query) get the new resolved/user config with sensible defaults (enabled: true, tags: false, name: '{{name}}MutationKey').
  • Docs — new ## Mutation Keys section in docs/openapi-ts/plugins/tanstack-query.md.
  • Regenerate fixtures — examples and __snapshots__ updated everywhere mutations are emitted.

Summary | 97 files | 6 commits | base: main

Notes before merge

  • No changeset.changeset/ is untouched. Past TanStack feature PRs in this repo (e.g. feat(@tanstack/query): generate type-safe setQueryData helpers) ship with a changeset; please add one so the next release picks this up under @hey-api/openapi-ts.
  • svelte-query/types.ts mutationKeys? — left as bare property declarations while every other plugin (angular, preact, react, solid, vue) gives the inner fields the same JSDoc treatment as their queryKeys? blocks. Inline comment below with a suggested fix.

Sanity-checked

  • MutationKey<TOptions extends Partial<Options>> constraint matches the call site — generated helpers accept Partial<Options<…Data>>, which is correct for mutation options (no required path/query enforced at the key boundary). queryKey uses the stricter extends Options because its callers pass full options.
  • createMutationKey correctly omits the _infinite discriminator that createQueryKey carries.
  • The guard if (!plugin.querySymbol({ resource: 'createMutationKey', … })) ensures the shared type + factory are emitted only once per file, matching the queryKey pattern.
  • applyNaming(operation.id, plugin.config.mutationKeys) (without a meta argument) is consistent with how queryKey symbols are created in queryOptions.ts:45.

Note: 1 inline comment(s) dropped because they did not anchor to lines inside the PR diff:

  • packages/openapi-ts/src/plugins/@tanstack/svelte-query/types.ts:138-142 (RIGHT) — file has no textual diff (binary, pure rename, or mode change)

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus𝕏

@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels May 14, 2026
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels May 14, 2026
Copy link
Copy Markdown
Member

@mrlubos mrlubos left a comment

Choose a reason for hiding this comment

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

@slmnsh well done!

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label May 14, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web Ready Ready Preview, Comment May 14, 2026 10:37am

Request Review

@mrlubos mrlubos merged commit 0d63bb1 into hey-api:main May 14, 2026
9 of 11 checks passed
@hey-api hey-api Bot mentioned this pull request May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 🚀 Feature request. lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate mutationKey when generating MutationOptions

2 participants