diff --git a/.changeset/react-query-operation-dispatch.md b/.changeset/react-query-operation-dispatch.md new file mode 100644 index 000000000..64808e337 --- /dev/null +++ b/.changeset/react-query-operation-dispatch.md @@ -0,0 +1,5 @@ +--- +"@kubb/plugin-react-query": patch +--- + +Classify each operation as query or mutation once and dispatch only to the matching hook generators, instead of running all five (query, suspenseQuery, infiniteQuery, suspenseInfiniteQuery, mutation) for every operation with four returning early. Fixes kubb-labs/kubb#3816. Generated output is unchanged. diff --git a/internals/tanstack-query/src/index.ts b/internals/tanstack-query/src/index.ts index ab12887f7..2c42ce2e2 100644 --- a/internals/tanstack-query/src/index.ts +++ b/internals/tanstack-query/src/index.ts @@ -1,5 +1,6 @@ export { InfiniteQueryOptions } from './components/InfiniteQueryOptions.tsx' export { MutationKey, mutationKeyTransformer } from './components/MutationKey.tsx' +export { createOperationDispatcher } from './operationDispatcher.ts' export { createMutationResolver, createQueryResolver, type MutationResolver, type QueryResolver, type QueryVariant } from './resolver.ts' export { QueryKey, queryKeyTransformer } from './components/QueryKey.tsx' export type { Infinite, Mutation, ParamsCasing, ParamsType, PathParamsType, Query, Transformer } from './types.ts' diff --git a/internals/tanstack-query/src/operationDispatcher.ts b/internals/tanstack-query/src/operationDispatcher.ts new file mode 100644 index 000000000..bb409cda2 --- /dev/null +++ b/internals/tanstack-query/src/operationDispatcher.ts @@ -0,0 +1,51 @@ +import { ast, defineGenerator } from 'kubb/kit' +import { jsxRenderer } from 'kubb/jsx' +import { classifyOperation } from './utils.ts' +import type { Generator, PluginFactoryOptions } from 'kubb/kit' +import type { KubbReactElement } from 'kubb/jsx' +import type { Mutation, Query } from './types.ts' + +type OperationDispatcherResolvedOptions = { + query: Required | false + mutation: Required | false +} + +/** + * Builds a single operation generator that classifies each operation as query or mutation once, + * then renders only the generators for that family. Replaces registering every hook generator + * (query, suspenseQuery, infiniteQuery, suspenseInfiniteQuery, mutation, ...) directly with the + * core engine, which would otherwise run all of them per operation with the non-matching ones + * returning early. No `renderer` is set on the returned generator: each matching generator's + * JSX result is rendered and upserted directly, so callers stay plain TypeScript. + */ +export function createOperationDispatcher>({ + name, + queryGenerators, + mutationGenerators, +}: { + name: string + queryGenerators: Array> + mutationGenerators: Array> +}): Generator { + return defineGenerator({ + name, + async operation(node, ctx) { + if (!ast.isHttpOperationNode(node)) return null + const { query, mutation } = ctx.options + + const { isQuery, isMutation } = classifyOperation(node, { query, mutation }) + const family = isMutation ? mutationGenerators : isQuery ? queryGenerators : [] + + for (const generator of family) { + const element = await generator.operation?.(node, ctx) + if (!element) continue + + using instance = jsxRenderer() + await instance.render(element as KubbReactElement) + await ctx.upsertFile(...instance.files) + } + + return null + }, + }) +} diff --git a/packages/plugin-react-query/src/plugin.ts b/packages/plugin-react-query/src/plugin.ts index 29198fe5a..52e35a770 100644 --- a/packages/plugin-react-query/src/plugin.ts +++ b/packages/plugin-react-query/src/plugin.ts @@ -2,7 +2,14 @@ import { createGroupConfig } from '@internals/shared' import { definePlugin, Resolver } from 'kubb/kit' import { resolveContractClient } from '@internals/client' import { pluginTsName } from '@kubb/plugin-ts' -import { mutationKeyTransformer, queryKeyTransformer, resolveInfiniteConfig, resolveMutationConfig, resolveQueryConfig } from '@internals/tanstack-query' +import { + createOperationDispatcher, + mutationKeyTransformer, + queryKeyTransformer, + resolveInfiniteConfig, + resolveMutationConfig, + resolveQueryConfig, +} from '@internals/tanstack-query' import { customHookOptionsFileGenerator, hookOptionsGenerator, @@ -15,6 +22,12 @@ import { import { resolverReactQuery } from './resolvers/resolverReactQuery.ts' import type { PluginReactQuery, ResolverReactQuery } from './types.ts' +const operationGenerator = createOperationDispatcher({ + name: 'react-query-operation', + queryGenerators: [queryGenerator, suspenseQueryGenerator, infiniteQueryGenerator, suspenseInfiniteQueryGenerator], + mutationGenerators: [mutationGenerator], +}) + /** * Canonical plugin name for `@kubb/plugin-react-query`. Used for driver lookups * and cross-plugin dependency references. @@ -66,15 +79,7 @@ export const pluginReactQuery = definePlugin((options) => { macros: userMacros, } = options - const selectedGenerators = [ - queryGenerator, - suspenseQueryGenerator, - infiniteQueryGenerator, - suspenseInfiniteQueryGenerator, - mutationGenerator, - hookOptionsGenerator, - customHookOptionsFileGenerator, - ] + const selectedGenerators = [operationGenerator, hookOptionsGenerator, customHookOptionsFileGenerator] const groupConfig = createGroupConfig(group)