feat(@tanstack/query): generate mutation keys for mutation options#3858
Conversation
|
|
🦋 Changeset detectedLatest commit: 5ccc2bc The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
@slmnsh is attempting to deploy a commit to the Hey API Team on Vercel. A member of the Team first needs to authorize it. |
|
@mrlubos Can you check? |
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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.tsparallelsqueryKey.tswithcreateMutationKeyType,createMutationKeyFunction, andmutationKeyStatement. - Wire mutation key into
mutationOptions— emit a<op>MutationKeyfactory and addmutationKey: <op>MutationKey(options)to the returned options object. - Add
mutationKeysuser config — six frameworktypes.ts/config.tsfiles gainmutationKeys: 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: main ← 1285e02
mutationKeys.enabled is silently ignored
Before: users could opt out of
queryKeys/infiniteQueryKeysonly by knowing the existing flags don't actually gate emission either.
After:mutationKeysadds a third user-visibleenabledflag with the same problem —mutationKeys: falseand{ enabled: false }parse correctly through themappersand resolver, but no consumer ever readsplugin.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.tscarry the old output.
After: they still do — agrepforMutationKey|mutationKey:underexamples/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, andconfig.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 ownMutationKeytype for any user who re-exports both from the same module. This already happens forQueryKey, 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
createMutationKeyemission is guarded byplugin.querySymbol({ category: 'utility', resource: 'createMutationKey', tool: plugin.name })but theMutationKeytype is created in the same conditional. If anything else ever emits aMutationKeytype symbol first, the guard would skip the helper while leaving a foreign type referenced viareferenceSymbolonmutationOptions.ts:39. No current emitter does this; flagging only as a latent fragility shared withqueryKey. - Positional-arg passing in
createMutationKeyLiteralis correct —createMutationKeyhas signature(id, options?, tags?), distinct fromcreateQueryKey's(id, options?, infinite?, tags?), so passingtagsArrayas the third arg is right. Worth a comment if it ever causes confusion during review.
Claude Opus | 𝕏
| return data; | ||
| } | ||
| }, | ||
| mutationKey: patchApiVbyApiVersionNoTagMutationKey(options) |
There was a problem hiding this comment.
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
fix(@tanstack/query): removed body from mutation key gen
@hey-api/codegen-core
@hey-api/json-schema-ref-parser
@hey-api/nuxt
@hey-api/openapi-ts
@hey-api/shared
@hey-api/spec-types
@hey-api/types
@hey-api/vite-plugin
commit: |
|
@pullfrog review the final version please |
There was a problem hiding this comment.
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/MutationKeycodegen — newmutationKey.tsinquery-coreproduces a per-operation…MutationKeyexported helper plus a sharedcreateMutationKeybuilder andMutationKey<TOptions>type. - Wire
mutationKeyintomutationOptions—mutationOptions.tsnow emits the per-operation key statement and setsmutationKey: …MutationKey(options)on the returned options object. - Add
mutationKeysplugin 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 Keyssection indocs/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.tsmutationKeys?— left as bare property declarations while every other plugin (angular,preact,react,solid,vue) gives the inner fields the same JSDoc treatment as theirqueryKeys?blocks. Inline comment below with a suggested fix.
Sanity-checked
MutationKey<TOptions extends Partial<Options>>constraint matches the call site — generated helpers acceptPartial<Options<…Data>>, which is correct for mutation options (no requiredpath/queryenforced at the key boundary).queryKeyuses the stricterextends Optionsbecause its callers pass full options.createMutationKeycorrectly omits the_infinitediscriminator thatcreateQueryKeycarries.- The guard
if (!plugin.querySymbol({ resource: 'createMutationKey', … }))ensures the shared type + factory are emitted only once per file, matching thequeryKeypattern. applyNaming(operation.id, plugin.config.mutationKeys)(without ametaargument) is consistent with howqueryKeysymbols are created inqueryOptions.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)
Claude Opus | 𝕏
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|

Fixes #3845