diff --git a/.changeset/twelve-mugs-reflect.md b/.changeset/twelve-mugs-reflect.md new file mode 100644 index 000000000..13d39a15d --- /dev/null +++ b/.changeset/twelve-mugs-reflect.md @@ -0,0 +1,5 @@ +--- +'@graphql-codegen/typescript-react-query': major +--- + +Support react-query v5 diff --git a/.vscode/settings.json b/.vscode/settings.json index 9c22884fe..2148080f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,5 +19,8 @@ }, "typescript.tsdk": "node_modules/typescript/lib", "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/dev-test/githunt/types.react-query.ts b/dev-test/githunt/types.react-query.ts index 38315319a..f5368df76 100644 --- a/dev-test/githunt/types.react-query.ts +++ b/dev-test/githunt/types.react-query.ts @@ -460,12 +460,13 @@ export const CommentDocument = ` } } ${CommentsPageCommentFragmentDoc}`; + export const useCommentQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, variables: CommentQueryVariables, options?: UseQueryOptions, -) => - useQuery( +) => { + return useQuery( ['Comment', variables], fetcher( dataSource.endpoint, @@ -475,12 +476,14 @@ export const useCommentQuery = ( ), options, ); +}; + export const useInfiniteCommentQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, variables: CommentQueryVariables, options?: UseInfiniteQueryOptions, -) => - useInfiniteQuery( +) => { + return useInfiniteQuery( ['Comment.infinite', variables], metaData => fetcher( @@ -491,6 +494,7 @@ export const useInfiniteCommentQuery = ( )(), options, ); +}; export const CurrentUserForProfileDocument = ` query CurrentUserForProfile { @@ -500,12 +504,13 @@ export const CurrentUserForProfileDocument = ` } } `; + export const useCurrentUserForProfileQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, variables?: CurrentUserForProfileQueryVariables, options?: UseQueryOptions, -) => - useQuery( +) => { + return useQuery( variables === undefined ? ['CurrentUserForProfile'] : ['CurrentUserForProfile', variables], fetcher( dataSource.endpoint, @@ -515,6 +520,8 @@ export const useCurrentUserForProfileQuery = , -) => - useInfiniteQuery( +) => { + return useInfiniteQuery( variables === undefined ? ['CurrentUserForProfile.infinite'] : ['CurrentUserForProfile.infinite', variables], @@ -536,6 +543,7 @@ export const useInfiniteCurrentUserForProfileQuery = < )(), options, ); +}; export const FeedDocument = ` query Feed($type: FeedType!, $offset: Int, $limit: Int) { @@ -547,12 +555,13 @@ export const FeedDocument = ` } } ${FeedEntryFragmentDoc}`; + export const useFeedQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, variables: FeedQueryVariables, options?: UseQueryOptions, -) => - useQuery( +) => { + return useQuery( ['Feed', variables], fetcher( dataSource.endpoint, @@ -562,12 +571,14 @@ export const useFeedQuery = ( ), options, ); +}; + export const useInfiniteFeedQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, variables: FeedQueryVariables, options?: UseInfiniteQueryOptions, -) => - useInfiniteQuery( +) => { + return useInfiniteQuery( ['Feed.infinite', variables], metaData => fetcher( @@ -578,6 +589,7 @@ export const useInfiniteFeedQuery = ( )(), options, ); +}; export const SubmitRepositoryDocument = ` mutation submitRepository($repoFullName: String!) { @@ -586,6 +598,7 @@ export const SubmitRepositoryDocument = ` } } `; + export const useSubmitRepositoryMutation = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, options?: UseMutationOptions< @@ -594,8 +607,8 @@ export const useSubmitRepositoryMutation = , -) => - useMutation( +) => { + return useMutation( ['submitRepository'], (variables?: SubmitRepositoryMutationVariables) => fetcher( @@ -606,6 +619,8 @@ export const useSubmitRepositoryMutation = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, options?: UseMutationOptions< @@ -621,8 +637,8 @@ export const useSubmitCommentMutation = ( SubmitCommentMutationVariables, TContext >, -) => - useMutation( +) => { + return useMutation( ['submitComment'], (variables?: SubmitCommentMutationVariables) => fetcher( @@ -633,6 +649,8 @@ export const useSubmitCommentMutation = ( )(), options, ); +}; + export const VoteDocument = ` mutation vote($repoFullName: String!, $type: VoteType!) { vote(repoFullName: $repoFullName, type: $type) { @@ -644,11 +662,12 @@ export const VoteDocument = ` } } `; + export const useVoteMutation = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, options?: UseMutationOptions, -) => - useMutation( +) => { + return useMutation( ['vote'], (variables?: VoteMutationVariables) => fetcher( @@ -659,3 +678,4 @@ export const useVoteMutation = ( )(), options, ); +}; diff --git a/packages/plugins/typescript/react-query/src/config.ts b/packages/plugins/typescript/react-query/src/config.ts index 63fe5f768..90e059c1d 100644 --- a/packages/plugins/typescript/react-query/src/config.ts +++ b/packages/plugins/typescript/react-query/src/config.ts @@ -4,38 +4,12 @@ export type HardcodedFetch = { endpoint: string; fetchParams?: string | Record **If you are using the `react-query` package instead of the `@tanstack/react-query` package in your project, please set the `legacyMode` option to `true`.** - * - */ -export interface ReactQueryRawPluginConfig - extends Omit< - RawClientSideBasePluginConfig, - | 'documentMode' - | 'noGraphQLTag' - | 'gqlImport' - | 'documentNodeImport' - | 'noExport' - | 'importOperationTypesFrom' - | 'importDocumentNodeExternallyFrom' - | 'useTypeImports' - | 'legacyMode' - > { +export interface BaseReactQueryPluginConfig { /** - * @description Customize the fetcher you wish to use in the generated file. React-Query is agnostic to the data-fetching layer, so you should provide it, or use a custom one. - * - * The following options are available to use: - * - * - 'fetch' - requires you to specify endpoint and headers on each call, and uses `fetch` to do the actual http call. - * - `{ endpoint: string, fetchParams: RequestInit }`: hardcode your endpoint and fetch options into the generated output, using the environment `fetch` method. You can also use `process.env.MY_VAR` as endpoint or header value. - * - `file#identifier` - You can use custom fetcher method that should implement the exported `ReactQueryFetcher` interface. Example: `./my-fetcher#myCustomFetcher`. - * - `graphql-request`: Will generate each hook with `client` argument, where you should pass your own `GraphQLClient` (created from `graphql-request`). + * @default unknown + * @description Changes the default "TError" generic type. */ - fetcher?: 'fetch' | HardcodedFetch | GraphQlRequest | CustomFetch; + errorType?: string; /** * @default false @@ -98,23 +72,30 @@ export interface ReactQueryRawPluginConfig exposeFetcher?: boolean; /** - * @default unknown - * @description Changes the default "TError" generic type. + * @default false + * @description Adds an Infinite Query along side the standard one */ - errorType?: string; + addInfiniteQuery?: boolean; /** * @default false - * @description Adds an Infinite Query along side the standard one + * @description Adds a Suspense Query along side the standard one */ - addInfiniteQuery?: boolean; + addSuspenseQuery?: boolean; /** * @default false * @description If true, it imports `react-query` not `@tanstack/react-query`, default is false. + * @deprecated Please use `reactQueryVersion` instead. */ legacyMode?: boolean; + /** + * @default 4 + * @description The version of react-query to use. Will override the legacyMode option. + */ + reactQueryVersion?: 3 | 4 | 5; + /** * @default empty * @description Add custom import for react-query. @@ -126,3 +107,38 @@ export interface ReactQueryRawPluginConfig */ reactQueryImportFrom?: string; } + +/** + * @description This plugin generates `React-Query` Hooks with TypeScript typings. + * + * It extends the basic TypeScript plugins: `@graphql-codegen/typescript`, `@graphql-codegen/typescript-operations` - and thus shares a similar configuration. + * + * > **If you are using the `react-query` package instead of the `@tanstack/react-query` package in your project, please set the `legacyMode` option to `true`.** + * + */ +export interface ReactQueryRawPluginConfig + extends Omit< + RawClientSideBasePluginConfig, + | 'documentMode' + | 'noGraphQLTag' + | 'gqlImport' + | 'documentNodeImport' + | 'noExport' + | 'importOperationTypesFrom' + | 'importDocumentNodeExternallyFrom' + | 'useTypeImports' + | 'legacyMode' + >, + BaseReactQueryPluginConfig { + /** + * @description Customize the fetcher you wish to use in the generated file. React-Query is agnostic to the data-fetching layer, so you should provide it, or use a custom one. + * + * The following options are available to use: + * + * - 'fetch' - requires you to specify endpoint and headers on each call, and uses `fetch` to do the actual http call. + * - `{ endpoint: string, fetchParams: RequestInit }`: hardcode your endpoint and fetch options into the generated output, using the environment `fetch` method. You can also use `process.env.MY_VAR` as endpoint or header value. + * - `file#identifier` - You can use custom fetcher method that should implement the exported `ReactQueryFetcher` interface. Example: `./my-fetcher#myCustomFetcher`. + * - `graphql-request`: Will generate each hook with `client` argument, where you should pass your own `GraphQLClient` (created from `graphql-request`). + */ + fetcher?: 'fetch' | HardcodedFetch | GraphQlRequest | CustomFetch; +} diff --git a/packages/plugins/typescript/react-query/src/fetcher-custom-mapper.ts b/packages/plugins/typescript/react-query/src/fetcher-custom-mapper.ts index 20767012a..65b3c918d 100644 --- a/packages/plugins/typescript/react-query/src/fetcher-custom-mapper.ts +++ b/packages/plugins/typescript/react-query/src/fetcher-custom-mapper.ts @@ -1,35 +1,32 @@ -import { OperationDefinitionNode } from 'graphql'; +import autoBind from 'auto-bind'; import { buildMapperImport, ParsedMapper, parseMapper, } from '@graphql-codegen/visitor-plugin-common'; import { CustomFetch } from './config.js'; -import { FetcherRenderer } from './fetcher.js'; -import { - generateInfiniteQueryKey, - generateMutationKey, - generateQueryKey, -} from './variables-generator.js'; +import { FetcherRenderer, type GenerateConfig } from './fetcher.js'; import { ReactQueryVisitor } from './visitor.js'; -export class CustomMapperFetcher implements FetcherRenderer { +export class CustomMapperFetcher extends FetcherRenderer { private _mapper: ParsedMapper; private _isReactHook: boolean; - constructor(private visitor: ReactQueryVisitor, customFetcher: CustomFetch) { + constructor(protected visitor: ReactQueryVisitor, customFetcher: CustomFetch) { + super(visitor); if (typeof customFetcher === 'string') { customFetcher = { func: customFetcher }; } this._mapper = parseMapper(customFetcher.func); this._isReactHook = customFetcher.isReactHook; + autoBind(this); } private getFetcherFnName(operationResultType: string, operationVariablesTypes: string): string { return `${this._mapper.type}<${operationResultType}, ${operationVariablesTypes}>`; } - generateFetcherImplementaion(): string { + generateFetcherImplementation(): string { if (this._mapper.isExternal) { return buildMapperImport( this._mapper.source, @@ -46,120 +43,64 @@ export class CustomMapperFetcher implements FetcherRenderer { return null; } - generateInfiniteQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = `variables${hasRequiredVariables ? '' : '?'}: ${operationVariablesTypes}`; - - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.infiniteQuery.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.infiniteQuery.options); - - const options = `options?: ${hookConfig.infiniteQuery.options}<${operationResultType}, TError, TData>`; + generateInfiniteQueryHook(config: GenerateConfig, isSuspense = false): string { + const { documentVariableName, operationResultType, operationVariablesTypes } = config; const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes); const implHookOuter = this._isReactHook ? `const query = ${typedFetcher}(${documentVariableName})` : ''; - const impl = this._isReactHook + const implFetcher = this._isReactHook ? `(metaData) => query({...variables, ...(metaData.pageParam ?? {})})` : `(metaData) => ${typedFetcher}(${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})})()`; - return `export const useInfinite${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( - ${variables}, - ${options} - ) =>{ - ${implHookOuter} - return ${hookConfig.infiniteQuery.hook}<${operationResultType}, TError, TData>( - ${generateInfiniteQueryKey(node, hasRequiredVariables)}, - ${impl}, - options - )};`; - } + const { generateBaseInfiniteQueryHook } = this.generateInfiniteQueryHelper(config, isSuspense); - generateQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = `variables${hasRequiredVariables ? '' : '?'}: ${operationVariablesTypes}`; + return generateBaseInfiniteQueryHook({ + implHookOuter, + implFetcher, + }); + } - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.query.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.query.options); + generateQueryHook(config: GenerateConfig, isSuspense = false): string { + const { generateBaseQueryHook } = this.generateQueryHelper(config, isSuspense); - const options = `options?: ${hookConfig.query.options}<${operationResultType}, TError, TData>`; + const { documentVariableName, operationResultType, operationVariablesTypes } = config; const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes); - const impl = this._isReactHook + const implFetcher = this._isReactHook ? `${typedFetcher}(${documentVariableName}).bind(null, variables)` : `${typedFetcher}(${documentVariableName}, variables)`; - return `export const use${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( - ${variables}, - ${options} - ) => - ${hookConfig.query.hook}<${operationResultType}, TError, TData>( - ${generateQueryKey(node, hasRequiredVariables)}, - ${impl}, - options - );`; + return generateBaseQueryHook({ + implFetcher, + }); } - generateMutationHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = `variables?: ${operationVariablesTypes}`; - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.mutation.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.mutation.options); - - const options = `options?: ${hookConfig.mutation.options}<${operationResultType}, TError, ${operationVariablesTypes}, TContext>`; + generateMutationHook(config: GenerateConfig): string { + const { documentVariableName, operationResultType, operationVariablesTypes } = config; + + const { generateBaseMutationHook, variables } = this.generateMutationHelper(config); + const typedFetcher = this.getFetcherFnName(operationResultType, operationVariablesTypes); - const impl = this._isReactHook + const implFetcher = this._isReactHook ? `${typedFetcher}(${documentVariableName})` : `(${variables}) => ${typedFetcher}(${documentVariableName}, variables)()`; - return `export const use${operationName} = < - TError = ${this.visitor.config.errorType}, - TContext = unknown - >(${options}) => - ${ - hookConfig.mutation.hook - }<${operationResultType}, TError, ${operationVariablesTypes}, TContext>( - ${generateMutationKey(node)}, - ${impl}, - options - );`; + return generateBaseMutationHook({ + implFetcher, + }); } - generateFetcherFetch( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { + generateFetcherFetch(config: GenerateConfig): string { + const { + documentVariableName, + operationResultType, + operationVariablesTypes, + hasRequiredVariables, + operationName, + } = config; + // We can't generate a fetcher field since we can't call react hooks outside of a React Fucntion Component // Related: https://reactjs.org/docs/hooks-rules.html if (this._isReactHook) return ''; diff --git a/packages/plugins/typescript/react-query/src/fetcher-fetch-hardcoded.ts b/packages/plugins/typescript/react-query/src/fetcher-fetch-hardcoded.ts index af9948f00..cf1b25f49 100644 --- a/packages/plugins/typescript/react-query/src/fetcher-fetch-hardcoded.ts +++ b/packages/plugins/typescript/react-query/src/fetcher-fetch-hardcoded.ts @@ -1,16 +1,13 @@ -import { OperationDefinitionNode } from 'graphql'; +import autoBind from 'auto-bind'; import { HardcodedFetch } from './config.js'; -import { FetcherRenderer } from './fetcher.js'; -import { - generateInfiniteQueryKey, - generateMutationKey, - generateQueryKey, - generateQueryVariablesSignature, -} from './variables-generator.js'; +import { FetcherRenderer, type GenerateConfig } from './fetcher.js'; import { ReactQueryVisitor } from './visitor.js'; -export class HardcodedFetchFetcher implements FetcherRenderer { - constructor(private visitor: ReactQueryVisitor, private config: HardcodedFetch) {} +export class HardcodedFetchFetcher extends FetcherRenderer { + constructor(protected visitor: ReactQueryVisitor, private config: HardcodedFetch) { + super(visitor); + autoBind(this); + } private getEndpoint(): string { try { @@ -37,7 +34,7 @@ export class HardcodedFetchFetcher implements FetcherRenderer { return ` method: "POST",${fetchParamsPartial}`; } - generateFetcherImplementaion(): string { + generateFetcherImplementation(): string { return ` function fetcher(query: string, variables?: TVariables) { return async (): Promise => { @@ -59,110 +56,41 @@ ${this.getFetchParams()} }`; } - generateInfiniteQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.infiniteQuery.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.infiniteQuery.options); - - const options = `options?: ${hookConfig.infiniteQuery.options}<${operationResultType}, TError, TData>`; - - return `export const useInfinite${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( - ${variables}, - ${options} - ) => - ${hookConfig.infiniteQuery.hook}<${operationResultType}, TError, TData>( - ${generateInfiniteQueryKey(node, hasRequiredVariables)}, - (metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})})(), - options - );`; + generateInfiniteQueryHook(config: GenerateConfig, isSuspense = false): string { + const { generateBaseInfiniteQueryHook } = this.generateInfiniteQueryHelper(config, isSuspense); + + const { documentVariableName, operationResultType, operationVariablesTypes } = config; + + return generateBaseInfiniteQueryHook({ + implFetcher: `(metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})})()`, + }); } - generateQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.query.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.query.options); - - const options = `options?: ${hookConfig.query.options}<${operationResultType}, TError, TData>`; - - return `export const use${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( - ${variables}, - ${options} - ) => - ${hookConfig.query.hook}<${operationResultType}, TError, TData>( - ${generateQueryKey(node, hasRequiredVariables)}, - fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables), - options - );`; + generateQueryHook(config: GenerateConfig, isSuspense = false): string { + const { generateBaseQueryHook } = this.generateQueryHelper(config, isSuspense); + + const { documentVariableName, operationResultType, operationVariablesTypes } = config; + + return generateBaseQueryHook({ + implFetcher: `fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables)`, + }); } - generateMutationHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = `variables?: ${operationVariablesTypes}`; - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.mutation.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.mutation.options); - - const options = `options?: ${hookConfig.mutation.options}<${operationResultType}, TError, ${operationVariablesTypes}, TContext>`; - - return `export const use${operationName} = < - TError = ${this.visitor.config.errorType}, - TContext = unknown - >(${options}) => - ${ - hookConfig.mutation.hook - }<${operationResultType}, TError, ${operationVariablesTypes}, TContext>( - ${generateMutationKey(node)}, - (${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables)(), - options - );`; + generateMutationHook(config: GenerateConfig): string { + const { generateBaseMutationHook, variables } = this.generateMutationHelper(config); + + const { documentVariableName, operationResultType, operationVariablesTypes } = config; + + return generateBaseMutationHook({ + implFetcher: `(${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables)()`, + }); } - generateFetcherFetch( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); + generateFetcherFetch(config: GenerateConfig): string { + const { documentVariableName, operationResultType, operationVariablesTypes, operationName } = + config; + + const variables = this.generateQueryVariablesSignature(config); return `\nuse${operationName}.fetcher = (${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables);`; } diff --git a/packages/plugins/typescript/react-query/src/fetcher-fetch.ts b/packages/plugins/typescript/react-query/src/fetcher-fetch.ts index ca1d49afc..ee9363913 100644 --- a/packages/plugins/typescript/react-query/src/fetcher-fetch.ts +++ b/packages/plugins/typescript/react-query/src/fetcher-fetch.ts @@ -1,17 +1,14 @@ -import { OperationDefinitionNode } from 'graphql'; -import { FetcherRenderer } from './fetcher.js'; -import { - generateInfiniteQueryKey, - generateMutationKey, - generateQueryKey, - generateQueryVariablesSignature, -} from './variables-generator.js'; +import autoBind from 'auto-bind'; +import { FetcherRenderer, type GenerateConfig } from './fetcher.js'; import { ReactQueryVisitor } from './visitor.js'; -export class FetchFetcher implements FetcherRenderer { - constructor(private visitor: ReactQueryVisitor) {} +export class FetchFetcher extends FetcherRenderer { + constructor(protected visitor: ReactQueryVisitor) { + super(visitor); + autoBind(this); + } - generateFetcherImplementaion(): string { + generateFetcherImplementation(): string { return ` function fetcher(endpoint: string, requestInit: RequestInit, query: string, variables?: TVariables) { return async (): Promise => { @@ -34,115 +31,61 @@ function fetcher(endpoint: string, requestInit: RequestInit, }`; } - generateInfiniteQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, + generateInfiniteQueryHook(config: GenerateConfig, isSuspense = false): string { + const { generateBaseInfiniteQueryHook, variables, options } = this.generateInfiniteQueryHelper( + config, + isSuspense, ); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.infiniteQuery.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.infiniteQuery.options); - const options = `options?: ${hookConfig.infiniteQuery.options}<${operationResultType}, TError, TData>`; + const { documentVariableName, operationResultType, operationVariablesTypes } = config; - return `export const useInfinite${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( + return generateBaseInfiniteQueryHook({ + implArguments: ` dataSource: { endpoint: string, fetchParams?: RequestInit }, ${variables}, ${options} - ) => - ${hookConfig.infiniteQuery.hook}<${operationResultType}, TError, TData>( - ${generateInfiniteQueryKey(node, hasRequiredVariables)}, - (metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})})(), - options - );`; + `, + implFetcher: `(metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})})()`, + }); } - generateQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, + generateQueryHook(config: GenerateConfig, isSuspense = false): string { + const { generateBaseQueryHook, variables, options } = this.generateQueryHelper( + config, + isSuspense, ); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.query.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.query.options); - const options = `options?: ${hookConfig.query.options}<${operationResultType}, TError, TData>`; + const { documentVariableName, operationResultType, operationVariablesTypes } = config; - return `export const use${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( + return generateBaseQueryHook({ + implArguments: ` dataSource: { endpoint: string, fetchParams?: RequestInit }, ${variables}, ${options} - ) => - ${hookConfig.query.hook}<${operationResultType}, TError, TData>( - ${generateQueryKey(node, hasRequiredVariables)}, - fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, variables), - options - );`; + `, + implFetcher: `fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, variables)`, + }); } - generateMutationHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = `variables?: ${operationVariablesTypes}`; - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.mutation.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.mutation.options); - - const options = `options?: ${hookConfig.mutation.options}<${operationResultType}, TError, ${operationVariablesTypes}, TContext>`; - - return `export const use${operationName} = < - TError = ${this.visitor.config.errorType}, - TContext = unknown - >( + generateMutationHook(config: GenerateConfig): string { + const { generateBaseMutationHook, variables, options } = this.generateMutationHelper(config); + + const { documentVariableName, operationResultType, operationVariablesTypes } = config; + + return generateBaseMutationHook({ + implArguments: ` dataSource: { endpoint: string, fetchParams?: RequestInit }, ${options} - ) => - ${ - hookConfig.mutation.hook - }<${operationResultType}, TError, ${operationVariablesTypes}, TContext>( - ${generateMutationKey(node)}, - (${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, variables)(), - options - );`; + `, + implFetcher: `(${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, variables)()`, + }); } - generateFetcherFetch( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); + generateFetcherFetch(config: GenerateConfig): string { + const { documentVariableName, operationResultType, operationVariablesTypes, operationName } = + config; + + const variables = this.generateQueryVariablesSignature(config); return `\nuse${operationName}.fetcher = (dataSource: { endpoint: string, fetchParams?: RequestInit }, ${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(dataSource.endpoint, dataSource.fetchParams || {}, ${documentVariableName}, variables);`; } diff --git a/packages/plugins/typescript/react-query/src/fetcher-graphql-request.ts b/packages/plugins/typescript/react-query/src/fetcher-graphql-request.ts index 0e4c8e1db..9a61e8fb0 100644 --- a/packages/plugins/typescript/react-query/src/fetcher-graphql-request.ts +++ b/packages/plugins/typescript/react-query/src/fetcher-graphql-request.ts @@ -1,22 +1,18 @@ -import { OperationDefinitionNode } from 'graphql'; +import autoBind from 'auto-bind'; import { GraphQlRequest } from './config.js'; -import { FetcherRenderer } from './fetcher.js'; -import { - generateInfiniteQueryKey, - generateMutationKey, - generateQueryKey, - generateQueryVariablesSignature, -} from './variables-generator.js'; +import { FetcherRenderer, type GenerateConfig } from './fetcher.js'; import { ReactQueryVisitor } from './visitor.js'; -export class GraphQLRequestClientFetcher implements FetcherRenderer { +export class GraphQLRequestClientFetcher extends FetcherRenderer { private clientPath: string | null; - constructor(private visitor: ReactQueryVisitor, config: GraphQlRequest) { + constructor(protected visitor: ReactQueryVisitor, config: GraphQlRequest) { + super(visitor); this.clientPath = typeof config === 'object' ? config.clientImportPath : null; + autoBind(this); } - generateFetcherImplementaion(): string { + generateFetcherImplementation(): string { return this.clientPath ? ` function fetcher(query: string, variables?: TVariables, requestHeaders?: RequestInit['headers']) { @@ -36,73 +32,40 @@ function fetcher(client: Graph }`; } - generateInfiniteQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); - + generateInfiniteQueryHook(config: GenerateConfig, isSuspense = false): string { const typeImport = this.visitor.config.useTypeImports ? 'import type' : 'import'; if (this.clientPath) this.visitor.imports.add(this.clientPath); this.visitor.imports.add(`${typeImport} { GraphQLClient } from 'graphql-request';`); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.infiniteQuery.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.infiniteQuery.options); + const { generateBaseInfiniteQueryHook, variables, options } = this.generateInfiniteQueryHelper( + config, + isSuspense, + ); - const options = `options?: ${hookConfig.infiniteQuery.options}<${operationResultType}, TError, TData>`; + const { documentVariableName, operationResultType, operationVariablesTypes } = config; return this.clientPath - ? `export const useInfinite${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( + ? generateBaseInfiniteQueryHook({ + implArguments: ` pageParamKey: keyof ${operationVariablesTypes}, ${variables}, ${options}, headers?: RequestInit['headers'] - ) => - ${hookConfig.infiniteQuery.hook}<${operationResultType}, TError, TData>( - ${generateInfiniteQueryKey(node, hasRequiredVariables)}, - (metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})}, headers)(), - options - );` - : `export const useInfinite${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( + `, + implFetcher: `(metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, {...variables, [pageParamKey]: metaData.pageParam}, headers)()`, + }) + : generateBaseInfiniteQueryHook({ + implArguments: ` client: GraphQLClient, ${variables}, ${options}, headers?: RequestInit['headers'] - ) => - ${hookConfig.infiniteQuery.hook}<${operationResultType}, TError, TData>( - ${generateInfiniteQueryKey(node, hasRequiredVariables)}, - (metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(client, ${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})}, headers)(), - options - );`; + `, + implFetcher: `(metaData) => fetcher<${operationResultType}, ${operationVariablesTypes}>(client, ${documentVariableName}, {...variables, ...(metaData.pageParam ?? {})}, headers)()`, + }); } - generateQueryHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); - + generateQueryHook(config: GenerateConfig, isSuspense = false): string { const typeImport = this.visitor.config.useTypeImports ? 'import type' : 'import'; if (this.clientPath) this.visitor.imports.add(this.clientPath); this.visitor.imports.add(`${typeImport} { GraphQLClient } from 'graphql-request';`); @@ -110,105 +73,65 @@ function fetcher(client: Graph `${typeImport} { RequestInit } from 'graphql-request/dist/types.dom';`, ); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.query.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.query.options); + const { generateBaseQueryHook, variables, options } = this.generateQueryHelper( + config, + isSuspense, + ); - const options = `options?: ${hookConfig.query.options}<${operationResultType}, TError, TData>`; + const { documentVariableName, operationResultType, operationVariablesTypes } = config; return this.clientPath - ? `export const use${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( + ? generateBaseQueryHook({ + implArguments: ` ${variables}, ${options}, headers?: RequestInit['headers'] - ) => - ${hookConfig.query.hook}<${operationResultType}, TError, TData>( - ${generateQueryKey(node, hasRequiredVariables)}, - fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables, headers), - options - );` - : `export const use${operationName} = < - TData = ${operationResultType}, - TError = ${this.visitor.config.errorType} - >( + `, + implFetcher: `fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables, headers)`, + }) + : generateBaseQueryHook({ + implArguments: ` client: GraphQLClient, ${variables}, ${options}, headers?: RequestInit['headers'] - ) => - ${hookConfig.query.hook}<${operationResultType}, TError, TData>( - ${generateQueryKey(node, hasRequiredVariables)}, - fetcher<${operationResultType}, ${operationVariablesTypes}>(client, ${documentVariableName}, variables, headers), - options - );`; + `, + implFetcher: `fetcher<${operationResultType}, ${operationVariablesTypes}>(client, ${documentVariableName}, variables, headers)`, + }); } - generateMutationHook( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = `variables?: ${operationVariablesTypes}`; + generateMutationHook(config: GenerateConfig): string { const typeImport = this.visitor.config.useTypeImports ? 'import type' : 'import'; if (this.clientPath) this.visitor.imports.add(this.clientPath); this.visitor.imports.add(`${typeImport} { GraphQLClient } from 'graphql-request';`); - const hookConfig = this.visitor.queryMethodMap; - this.visitor.reactQueryHookIdentifiersInUse.add(hookConfig.mutation.hook); - this.visitor.reactQueryOptionsIdentifiersInUse.add(hookConfig.mutation.options); + const { generateBaseMutationHook, variables, options } = this.generateMutationHelper(config); - const options = `options?: ${hookConfig.mutation.options}<${operationResultType}, TError, ${operationVariablesTypes}, TContext>`; + const { documentVariableName, operationResultType, operationVariablesTypes } = config; return this.clientPath - ? `export const use${operationName} = < - TError = ${this.visitor.config.errorType}, - TContext = unknown - >( + ? generateBaseMutationHook({ + implArguments: ` ${options}, headers?: RequestInit['headers'] - ) => - ${ - hookConfig.mutation.hook - }<${operationResultType}, TError, ${operationVariablesTypes}, TContext>( - ${generateMutationKey(node)}, - (${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables, headers)(), - options - );` - : `export const use${operationName} = < - TError = ${this.visitor.config.errorType}, - TContext = unknown - >( + `, + implFetcher: `(${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(${documentVariableName}, variables, headers)()`, + }) + : generateBaseMutationHook({ + implArguments: ` client: GraphQLClient, ${options}, headers?: RequestInit['headers'] - ) => - ${ - hookConfig.mutation.hook - }<${operationResultType}, TError, ${operationVariablesTypes}, TContext>( - ${generateMutationKey(node)}, - (${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(client, ${documentVariableName}, variables, headers)(), - options - );`; + `, + implFetcher: `(${variables}) => fetcher<${operationResultType}, ${operationVariablesTypes}>(client, ${documentVariableName}, variables, headers)()`, + }); } - generateFetcherFetch( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ): string { - const variables = generateQueryVariablesSignature( - hasRequiredVariables, - operationVariablesTypes, - ); + generateFetcherFetch(config: GenerateConfig): string { + const { documentVariableName, operationResultType, operationVariablesTypes, operationName } = + config; + + const variables = this.generateQueryVariablesSignature(config); const typeImport = this.visitor.config.useTypeImports ? 'import type' : 'import'; if (this.clientPath) this.visitor.imports.add(this.clientPath); this.visitor.imports.add( diff --git a/packages/plugins/typescript/react-query/src/fetcher.ts b/packages/plugins/typescript/react-query/src/fetcher.ts index e49eff7fc..9d7991ad7 100644 --- a/packages/plugins/typescript/react-query/src/fetcher.ts +++ b/packages/plugins/typescript/react-query/src/fetcher.ts @@ -1,37 +1,322 @@ +import autoBind from 'auto-bind'; import { OperationDefinitionNode } from 'graphql'; +import { ReactQueryVisitor } from './visitor.js'; -export interface FetcherRenderer { - generateFetcherImplementaion: () => string; - generateQueryHook: ( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ) => string; - generateInfiniteQueryHook: ( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ) => string; - generateMutationHook: ( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ) => string; - generateFetcherFetch: ( - node: OperationDefinitionNode, - documentVariableName: string, - operationName: string, - operationResultType: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, - ) => string; +export interface GenerateConfig { + node: OperationDefinitionNode; + documentVariableName: string; + operationName: string; + operationResultType: string; + operationVariablesTypes: string; + hasRequiredVariables: boolean; +} + +interface GenerateBaseHookConfig { + implArguments?: string; + implHookOuter?: string; + implFetcher: string; +} + +type ReactQueryMethodMap = { + [key: string]: { + getHook: (operationName?: string) => string; + getOptions: () => string; + getOtherTypes?: () => { [key: string]: string }; + }; +}; + +export abstract class FetcherRenderer { + constructor(protected visitor: ReactQueryVisitor) { + autoBind(this); + } + + public abstract generateFetcherImplementation(): string; + public abstract generateFetcherFetch(config: GenerateConfig): string; + protected abstract generateQueryHook(config: GenerateConfig, isSuspense?: boolean): string; + protected abstract generateInfiniteQueryHook( + config: GenerateConfig, + isSuspense?: boolean, + ): string; + protected abstract generateMutationHook(config: GenerateConfig): string; + + public createQueryMethodMap(isSuspense = false) { + const suspenseText = isSuspense ? 'Suspense' : ''; + const queryMethodMap: ReactQueryMethodMap = { + infiniteQuery: { + getHook: (operationName = 'Query') => `use${suspenseText}Infinite${operationName}`, + getOptions: () => `Use${suspenseText}InfiniteQueryOptions`, + getOtherTypes: () => ({ infiniteData: 'InfiniteData' }), + }, + query: { + getHook: (operationName = 'Query') => `use${suspenseText}${operationName}`, + getOptions: () => `Use${suspenseText}QueryOptions`, + }, + mutation: { + getHook: (operationName = 'Mutation') => `use${operationName}`, + getOptions: () => `UseMutationOptions`, + }, + }; + + return queryMethodMap; + } + + protected generateInfiniteQueryHelper(config: GenerateConfig, isSuspense: boolean) { + const { operationResultType, operationName } = config; + + const { infiniteQuery } = this.createQueryMethodMap(isSuspense); + + const isNextVersion = this.visitor.config.reactQueryVersion >= 5; + + this.visitor.reactQueryHookIdentifiersInUse.add(infiniteQuery.getHook()); + this.visitor.reactQueryOptionsIdentifiersInUse.add(infiniteQuery.getOptions()); + if (isNextVersion) { + this.visitor.reactQueryOptionsIdentifiersInUse.add( + infiniteQuery.getOtherTypes().infiniteData, + ); + } + + const variables = this.generateInfiniteQueryVariablesSignature(config); + const options = this.generateInfiniteQueryOptionsSignature(config, isSuspense); + + const generateBaseInfiniteQueryHook = (hookConfig: GenerateBaseHookConfig) => { + const { implArguments, implHookOuter = '', implFetcher } = hookConfig; + + const argumentsResult = + implArguments ?? + ` + ${variables}, + ${options} + `; + + return `export const ${infiniteQuery.getHook(operationName)} = < + TData = ${ + isNextVersion + ? `${infiniteQuery.getOtherTypes().infiniteData}<${operationResultType}>` + : operationResultType + }, + TError = ${this.visitor.config.errorType} + >(${argumentsResult}) => { + ${implHookOuter} + return ${infiniteQuery.getHook()}<${operationResultType}, TError, TData>( + ${this.generateInfiniteQueryFormattedParameters( + this.generateInfiniteQueryKey(config, isSuspense), + implFetcher, + )} + )};`; + }; + + return { generateBaseInfiniteQueryHook, variables, options }; + } + + protected generateQueryHelper(config: GenerateConfig, isSuspense: boolean) { + const { operationName, operationResultType } = config; + + const { query } = this.createQueryMethodMap(isSuspense); + + this.visitor.reactQueryHookIdentifiersInUse.add(query.getHook()); + this.visitor.reactQueryOptionsIdentifiersInUse.add(query.getOptions()); + + const variables = this.generateQueryVariablesSignature(config); + const options = this.generateQueryOptionsSignature(config, isSuspense); + + const generateBaseQueryHook = (hookConfig: GenerateBaseHookConfig) => { + const { implArguments, implHookOuter = '', implFetcher } = hookConfig; + + const argumentsResult = + implArguments ?? + ` + ${variables}, + ${options} + `; + + return `export const ${query.getHook(operationName)} = < + TData = ${operationResultType}, + TError = ${this.visitor.config.errorType} + >(${argumentsResult}) => { + ${implHookOuter} + return ${query.getHook()}<${operationResultType}, TError, TData>( + ${this.generateQueryFormattedParameters( + this.generateQueryKey(config, isSuspense), + implFetcher, + )} + )};`; + }; + + return { + generateBaseQueryHook, + variables, + options, + }; + } + + protected generateMutationHelper(config: GenerateConfig) { + const { operationResultType, operationVariablesTypes, operationName } = config; + + const { mutation } = this.createQueryMethodMap(); + + this.visitor.reactQueryHookIdentifiersInUse.add(mutation.getHook()); + this.visitor.reactQueryOptionsIdentifiersInUse.add(mutation.getOptions()); + + const variables = `variables?: ${operationVariablesTypes}`; + const options = `options?: ${mutation.getOptions()}<${operationResultType}, TError, ${operationVariablesTypes}, TContext>`; + + const generateBaseMutationHook = (hookConfig: GenerateBaseHookConfig) => { + const { implArguments, implHookOuter = '', implFetcher } = hookConfig; + + const argumentsResult = implArguments ?? `${options}`; + + return `export const ${mutation.getHook(operationName)} = < + TError = ${this.visitor.config.errorType}, + TContext = unknown + >(${argumentsResult}) => { + ${implHookOuter} + return ${mutation.getHook()}<${operationResultType}, TError, ${operationVariablesTypes}, TContext>( + ${this.generateMutationFormattedParameters(this.generateMutationKey(config), implFetcher)} + )};`; + }; + + return { + generateBaseMutationHook, + variables, + options, + }; + } + + protected generateQueryVariablesSignature({ + hasRequiredVariables, + operationVariablesTypes, + }: GenerateConfig): string { + return `variables${hasRequiredVariables ? '' : '?'}: ${operationVariablesTypes}`; + } + + private generateQueryOptionsSignature( + { operationResultType }: GenerateConfig, + isSuspense: boolean, + ): string { + const { query } = this.createQueryMethodMap(isSuspense); + + if (this.visitor.config.reactQueryVersion <= 4) { + return `options?: ${query.getOptions()}<${operationResultType}, TError, TData>`; + } + return `options?: Omit<${query.getOptions()}<${operationResultType}, TError, TData>, 'queryKey'> & { queryKey?: ${query.getOptions()}<${operationResultType}, TError, TData>['queryKey'] }`; + } + + private generateInfiniteQueryVariablesSignature(config: GenerateConfig): string { + if (this.visitor.config.reactQueryVersion <= 4) { + return `variables${config.hasRequiredVariables ? '' : '?'}: ${ + config.operationVariablesTypes + }`; + } + return `variables: ${config.operationVariablesTypes}`; + } + + private generateInfiniteQueryOptionsSignature( + { operationResultType }: GenerateConfig, + isSuspense: boolean, + ): string { + const { infiniteQuery } = this.createQueryMethodMap(isSuspense); + + if (this.visitor.config.reactQueryVersion <= 4) { + return `options?: ${infiniteQuery.getOptions()}<${operationResultType}, TError, TData>`; + } + return `options: Omit<${infiniteQuery.getOptions()}<${operationResultType}, TError, TData>, 'queryKey'> & { queryKey?: ${infiniteQuery.getOptions()}<${operationResultType}, TError, TData>['queryKey'] }`; + } + + public generateInfiniteQueryKey(config: GenerateConfig, isSuspense: boolean): string { + const identifier = isSuspense ? 'infiniteSuspense' : 'infinite'; + if (config.hasRequiredVariables) + return `['${config.node.name.value}.${identifier}', variables]`; + return `variables === undefined ? ['${config.node.name.value}.${identifier}'] : ['${config.node.name.value}.${identifier}', variables]`; + } + + public generateInfiniteQueryOutput(config: GenerateConfig, isSuspense = false) { + const { infiniteQuery } = this.createQueryMethodMap(isSuspense); + const signature = this.generateQueryVariablesSignature(config); + const { operationName, node } = config; + return { + hook: this.generateInfiniteQueryHook(config, isSuspense), + getKey: `${infiniteQuery.getHook( + operationName, + )}.getKey = (${signature}) => ${this.generateInfiniteQueryKey(config, isSuspense)};`, + rootKey: `${infiniteQuery.getHook(operationName)}.rootKey = '${node.name.value}.infinite';`, + }; + } + + public generateQueryKey(config: GenerateConfig, isSuspense: boolean): string { + const identifier = isSuspense ? `${config.node.name.value}Suspense` : config.node.name.value; + if (config.hasRequiredVariables) return `['${identifier}', variables]`; + return `variables === undefined ? ['${identifier}'] : ['${identifier}', variables]`; + } + + public generateQueryOutput(config: GenerateConfig, isSuspense = false) { + const { query } = this.createQueryMethodMap(isSuspense); + const signature = this.generateQueryVariablesSignature(config); + const { operationName, node, documentVariableName } = config; + return { + hook: this.generateQueryHook(config, isSuspense), + document: `${query.getHook(operationName)}.document = ${documentVariableName};`, + getKey: `${query.getHook(operationName)}.getKey = (${signature}) => ${this.generateQueryKey( + config, + isSuspense, + )};`, + rootKey: `${query.getHook(operationName)}.rootKey = '${node.name.value}';`, + }; + } + + public generateMutationKey({ node }: GenerateConfig): string { + return `['${node.name.value}']`; + } + + public generateMutationOutput(config: GenerateConfig) { + const { mutation } = this.createQueryMethodMap(); + const { operationName } = config; + return { + hook: this.generateMutationHook(config), + getKey: `${mutation.getHook(operationName)}.getKey = () => ${this.generateMutationKey( + config, + )};`, + }; + } + + private generateInfiniteQueryFormattedParameters(queryKey: string, queryFn: string) { + if (this.visitor.config.reactQueryVersion <= 4) { + return `${queryKey}, + ${queryFn}, + options`; + } + return `(() => { + const { queryKey: optionsQueryKey, ...restOptions } = options; + return { + queryKey: optionsQueryKey ?? ${queryKey}, + queryFn: ${queryFn}, + ...restOptions + } + })()`; + } + + private generateQueryFormattedParameters(queryKey: string, queryFn: string): string { + if (this.visitor.config.reactQueryVersion <= 4) { + return `${queryKey}, + ${queryFn}, + options`; + } + return `{ + queryKey: ${queryKey}, + queryFn: ${queryFn}, + ...options + }`; + } + + private generateMutationFormattedParameters(mutationKey: string, mutationFn: string): string { + if (this.visitor.config.reactQueryVersion <= 4) { + return `${mutationKey}, + ${mutationFn}, + options`; + } + return `{ + mutationKey: ${mutationKey}, + mutationFn: ${mutationFn}, + ...options + }`; + } } diff --git a/packages/plugins/typescript/react-query/src/index.ts b/packages/plugins/typescript/react-query/src/index.ts index c24805baa..b9e62dc2a 100644 --- a/packages/plugins/typescript/react-query/src/index.ts +++ b/packages/plugins/typescript/react-query/src/index.ts @@ -33,6 +33,7 @@ export const plugin: PluginFunction typeof t === 'string'), ].join('\n'), @@ -42,6 +43,7 @@ export const plugin: PluginFunction typeof t === 'string'), ].join('\n'), @@ -51,12 +53,18 @@ export const plugin: PluginFunction = async ( schema: GraphQLSchema, documents: Types.DocumentFile[], - config: ReactQueryVisitor, + config: ReactQueryRawPluginConfig, outputFile: string, ) => { if (extname(outputFile) !== '.ts' && extname(outputFile) !== '.tsx') { throw new Error(`Plugin "typescript-react-query" requires extension to be ".ts" or ".tsx"!`); } + + if (config.reactQueryVersion !== 5 && config.addSuspenseQuery) { + throw new Error( + `Suspense queries are only supported in react-query@5. Please upgrade your react-query version.`, + ); + } }; export { ReactQueryVisitor }; diff --git a/packages/plugins/typescript/react-query/src/variables-generator.ts b/packages/plugins/typescript/react-query/src/variables-generator.ts deleted file mode 100644 index bb4b0f6f4..000000000 --- a/packages/plugins/typescript/react-query/src/variables-generator.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { OperationDefinitionNode } from 'graphql'; - -export function generateQueryVariablesSignature( - hasRequiredVariables: boolean, - operationVariablesTypes: string, -): string { - return `variables${hasRequiredVariables ? '' : '?'}: ${operationVariablesTypes}`; -} -export function generateInfiniteQueryKey( - node: OperationDefinitionNode, - hasRequiredVariables: boolean, -): string { - if (hasRequiredVariables) return `['${node.name.value}.infinite', variables]`; - return `variables === undefined ? ['${node.name.value}.infinite'] : ['${node.name.value}.infinite', variables]`; -} - -export function generateInfiniteQueryKeyMaker( - node: OperationDefinitionNode, - operationName: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, -) { - const signature = generateQueryVariablesSignature(hasRequiredVariables, operationVariablesTypes); - return `\nuseInfinite${operationName}.getKey = (${signature}) => ${generateInfiniteQueryKey( - node, - hasRequiredVariables, - )};\n`; -} - -export function generateInfiniteQueryRootKeyMaker( - node: OperationDefinitionNode, - operationName: string, -) { - return `\nuseInfinite${operationName}.rootKey = '${node.name.value}.infinite';\n`; -} - -export function generateQueryKey( - node: OperationDefinitionNode, - hasRequiredVariables: boolean, -): string { - if (hasRequiredVariables) return `['${node.name.value}', variables]`; - return `variables === undefined ? ['${node.name.value}'] : ['${node.name.value}', variables]`; -} - -export function generateQueryKeyMaker( - node: OperationDefinitionNode, - operationName: string, - operationVariablesTypes: string, - hasRequiredVariables: boolean, -) { - const signature = generateQueryVariablesSignature(hasRequiredVariables, operationVariablesTypes); - return `\nuse${operationName}.getKey = (${signature}) => ${generateQueryKey( - node, - hasRequiredVariables, - )};\n`; -} - -export function generateQueryRootKeyMaker(node: OperationDefinitionNode, operationName: string) { - return `\nuse${operationName}.rootKey = '${node.name.value}';\n`; -} - -export function generateMutationKey(node: OperationDefinitionNode): string { - return `['${node.name.value}']`; -} - -export function generateMutationKeyMaker(node: OperationDefinitionNode, operationName: string) { - return `\nuse${operationName}.getKey = () => ${generateMutationKey(node)};\n`; -} diff --git a/packages/plugins/typescript/react-query/src/visitor.ts b/packages/plugins/typescript/react-query/src/visitor.ts index 544aa5872..89c5f75d4 100644 --- a/packages/plugins/typescript/react-query/src/visitor.ts +++ b/packages/plugins/typescript/react-query/src/visitor.ts @@ -9,46 +9,14 @@ import { getConfigValue, LoadedFragment, } from '@graphql-codegen/visitor-plugin-common'; -import { ReactQueryRawPluginConfig } from './config.js'; +import { BaseReactQueryPluginConfig, ReactQueryRawPluginConfig } from './config.js'; import { CustomMapperFetcher } from './fetcher-custom-mapper.js'; import { HardcodedFetchFetcher } from './fetcher-fetch-hardcoded.js'; import { FetchFetcher } from './fetcher-fetch.js'; import { GraphQLRequestClientFetcher } from './fetcher-graphql-request.js'; -import { FetcherRenderer } from './fetcher.js'; -import { - generateInfiniteQueryKeyMaker, - generateInfiniteQueryRootKeyMaker, - generateMutationKeyMaker, - generateQueryKeyMaker, - generateQueryRootKeyMaker, -} from './variables-generator.js'; - -export interface ReactQueryPluginConfig extends ClientSideBasePluginConfig { - errorType: string; - exposeDocument: boolean; - exposeQueryKeys: boolean; - exposeQueryRootKeys: boolean; - exposeMutationKeys: boolean; - exposeFetcher: boolean; - addInfiniteQuery: boolean; - legacyMode: boolean; - reactQueryImportFrom?: string; -} +import { FetcherRenderer, type GenerateConfig } from './fetcher.js'; -export interface ReactQueryMethodMap { - infiniteQuery: { - hook: string; - options: string; - }; - query: { - hook: string; - options: string; - }; - mutation: { - hook: string; - options: string; - }; -} +export type ReactQueryPluginConfig = BaseReactQueryPluginConfig & ClientSideBasePluginConfig; export class ReactQueryVisitor extends ClientSideBaseVisitor< ReactQueryRawPluginConfig, @@ -59,27 +27,14 @@ export class ReactQueryVisitor extends ClientSideBaseVisitor< public reactQueryHookIdentifiersInUse = new Set(); public reactQueryOptionsIdentifiersInUse = new Set(); - public queryMethodMap: ReactQueryMethodMap = { - infiniteQuery: { - hook: 'useInfiniteQuery', - options: 'UseInfiniteQueryOptions', - }, - query: { - hook: 'useQuery', - options: 'UseQueryOptions', - }, - mutation: { - hook: 'useMutation', - options: 'UseMutationOptions', - }, - }; - constructor( schema: GraphQLSchema, fragments: LoadedFragment[], protected rawConfig: ReactQueryRawPluginConfig, documents: Types.DocumentFile[], ) { + const defaultReactQueryVersion = !rawConfig.reactQueryVersion && rawConfig.legacyMode ? 3 : 4; + super(schema, fragments, rawConfig, { documentMode: DocumentMode.string, errorType: getConfigValue(rawConfig.errorType, 'unknown'), @@ -89,9 +44,11 @@ export class ReactQueryVisitor extends ClientSideBaseVisitor< exposeMutationKeys: getConfigValue(rawConfig.exposeMutationKeys, false), exposeFetcher: getConfigValue(rawConfig.exposeFetcher, false), addInfiniteQuery: getConfigValue(rawConfig.addInfiniteQuery, false), - legacyMode: getConfigValue(rawConfig.legacyMode, false), + addSuspenseQuery: getConfigValue(rawConfig.addSuspenseQuery, false), + reactQueryVersion: getConfigValue(rawConfig.reactQueryVersion, defaultReactQueryVersion), reactQueryImportFrom: getConfigValue(rawConfig.reactQueryImportFrom, ''), }); + this._externalImportPrefix = this.config.importOperationTypesFrom ? `${this.config.importOperationTypesFrom}.` : ''; @@ -139,7 +96,7 @@ export class ReactQueryVisitor extends ClientSideBaseVisitor< const moduleName = this.config.reactQueryImportFrom ? this.config.reactQueryImportFrom - : this.config.legacyMode + : this.config.reactQueryVersion <= 3 ? 'react-query' : '@tanstack/react-query'; @@ -147,7 +104,7 @@ export class ReactQueryVisitor extends ClientSideBaseVisitor< } public getFetcherImplementation(): string { - return this.fetcher.generateFetcherImplementaion(); + return this.fetcher.generateFetcherImplementation(); } private _getHookSuffix(name: string, operationType: string) { @@ -179,92 +136,70 @@ export class ReactQueryVisitor extends ClientSideBaseVisitor< useTypesSuffix: false, }); + const generateConfig: GenerateConfig = { + node, + documentVariableName, + operationResultType, + operationVariablesTypes, + hasRequiredVariables, + operationName, + }; + operationResultType = this._externalImportPrefix + operationResultType; operationVariablesTypes = this._externalImportPrefix + operationVariablesTypes; + const queries: string[] = []; + const getOutputFromQueries = () => `\n${queries.join('\n\n')}\n`; + if (operationType === 'Query') { - let query = this.fetcher.generateQueryHook( - node, - documentVariableName, - operationName, - operationResultType, - operationVariablesTypes, - hasRequiredVariables, - ); - if (this.config.exposeDocument) { - query += `\nuse${operationName}.document = ${documentVariableName};\n`; - } - if (this.config.exposeQueryKeys) { - query += `\n${generateQueryKeyMaker( - node, - operationName, - operationVariablesTypes, - hasRequiredVariables, - )};\n`; - } - if (this.config.exposeQueryRootKeys) { - query += `\n${generateQueryRootKeyMaker(node, operationName)}`; - } + const addQuery = (generateConfig: GenerateConfig, isSuspense = false) => { + const { hook, getKey, rootKey, document } = this.fetcher.generateQueryOutput( + generateConfig, + isSuspense, + ); + queries.push(hook); + if (this.config.exposeDocument) queries.push(document); + if (this.config.exposeQueryKeys) queries.push(getKey); + if (this.config.exposeQueryRootKeys) queries.push(rootKey); + }; + + addQuery(generateConfig); + + if (this.config.addSuspenseQuery) addQuery(generateConfig, true); + if (this.config.addInfiniteQuery) { - query += `\n${this.fetcher.generateInfiniteQueryHook( - node, - documentVariableName, - operationName, - operationResultType, - operationVariablesTypes, - hasRequiredVariables, - )}\n`; - if (this.config.exposeQueryKeys) { - query += `\n${generateInfiniteQueryKeyMaker( - node, - operationName, - operationVariablesTypes, - hasRequiredVariables, - )};\n`; - } - if (this.config.exposeQueryRootKeys) { - query += `\n${generateInfiniteQueryRootKeyMaker(node, operationName)}`; + const addInfiniteQuery = (generateConfig: GenerateConfig, isSuspense = false) => { + const { hook, getKey, rootKey } = this.fetcher.generateInfiniteQueryOutput( + generateConfig, + isSuspense, + ); + queries.push(hook); + if (this.config.exposeQueryKeys) queries.push(getKey); + if (this.config.exposeQueryRootKeys) queries.push(rootKey); + }; + + addInfiniteQuery(generateConfig); + + if (this.config.addSuspenseQuery) { + addInfiniteQuery(generateConfig, true); } } - // The reason we're looking at the private field of the CustomMapperFetcher to see if it's a react hook // is to prevent calling generateFetcherFetch for each query since all the queries won't be able to generate // a fetcher field anyways. if (this.config.exposeFetcher && !(this.fetcher as any)._isReactHook) { - query += this.fetcher.generateFetcherFetch( - node, - documentVariableName, - operationName, - operationResultType, - operationVariablesTypes, - hasRequiredVariables, - ); + queries.push(this.fetcher.generateFetcherFetch(generateConfig)); } - return query; + return getOutputFromQueries(); } if (operationType === 'Mutation') { - let query = this.fetcher.generateMutationHook( - node, - documentVariableName, - operationName, - operationResultType, - operationVariablesTypes, - hasRequiredVariables, - ); - if (this.config.exposeMutationKeys) { - query += generateMutationKeyMaker(node, operationName); - } + const { hook, getKey } = this.fetcher.generateMutationOutput(generateConfig); + queries.push(hook); + if (this.config.exposeMutationKeys) queries.push(getKey); if (this.config.exposeFetcher && !(this.fetcher as any)._isReactHook) { - query += this.fetcher.generateFetcherFetch( - node, - documentVariableName, - operationName, - operationResultType, - operationVariablesTypes, - hasRequiredVariables, - ); + queries.push(this.fetcher.generateFetcherFetch(generateConfig)); } - return query; + return getOutputFromQueries(); } if (operationType === 'Subscription') { // eslint-disable-next-line no-console diff --git a/packages/plugins/typescript/react-query/tests/__snapshots__/react-query.spec.ts.snap b/packages/plugins/typescript/react-query/tests/__snapshots__/react-query.spec.ts.snap index 88e2aed62..ccfed1a09 100644 --- a/packages/plugins/typescript/react-query/tests/__snapshots__/react-query.spec.ts.snap +++ b/packages/plugins/typescript/react-query/tests/__snapshots__/react-query.spec.ts.snap @@ -2,6 +2,7 @@ exports[`React-Query exposeQueryKeys: true Should generate getKey for each query 1`] = ` " + export const TestDocument = \` query test { feed { @@ -17,6 +18,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TestQuery, TError = unknown @@ -24,15 +26,15 @@ export const useTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), options - ); + )}; useTestQuery.getKey = (variables?: TestQueryVariables) => variables === undefined ? ['test'] : ['test', variables]; -; export const TestDocument = \` mutation test($name: String) { @@ -41,22 +43,26 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown >( dataSource: { endpoint: string, fetchParams?: RequestInit }, options?: UseMutationOptions - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), options - );" + )}; +" `; exports[`React-Query exposeQueryKeys: true, addInfiniteQuery: true Should generate getKey for each query - also infinite queries 1`] = ` " + export const TestDocument = \` query test { feed { @@ -72,6 +78,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TestQuery, TError = unknown @@ -79,15 +86,15 @@ export const useTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), options - ); + )}; useTestQuery.getKey = (variables?: TestQueryVariables) => variables === undefined ? ['test'] : ['test', variables]; -; export const useInfiniteTestQuery = < TData = TestQuery, @@ -96,16 +103,15 @@ export const useInfiniteTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TestQueryVariables, options?: UseInfiniteQueryOptions - ) => - useInfiniteQuery( + ) => { + + return useInfiniteQuery( variables === undefined ? ['test.infinite'] : ['test.infinite', variables], (metaData) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), options - ); - + )}; useInfiniteTestQuery.getKey = (variables?: TestQueryVariables) => variables === undefined ? ['test.infinite'] : ['test.infinite', variables]; -; export const TestDocument = \` mutation test($name: String) { @@ -114,22 +120,26 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown >( dataSource: { endpoint: string, fetchParams?: RequestInit }, options?: UseMutationOptions - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), options - );" + )}; +" `; exports[`React-Query exposeQueryRootKeys: true Should generate rootKey for each query 1`] = ` " + export const TestDocument = \` query test { feed { @@ -145,6 +155,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TestQuery, TError = unknown @@ -152,12 +163,13 @@ export const useTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), options - ); + )}; useTestQuery.rootKey = 'test'; @@ -168,22 +180,26 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown >( dataSource: { endpoint: string, fetchParams?: RequestInit }, options?: UseMutationOptions - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), options - );" + )}; +" `; exports[`React-Query exposeQueryRootKeys: true, addInfiniteQuery: true Should generate rootKey for each query - also infinite queries 1`] = ` " + export const TestDocument = \` query test { feed { @@ -199,6 +215,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TestQuery, TError = unknown @@ -206,12 +223,13 @@ export const useTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), options - ); + )}; useTestQuery.rootKey = 'test'; @@ -222,13 +240,13 @@ export const useInfiniteTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TestQueryVariables, options?: UseInfiniteQueryOptions - ) => - useInfiniteQuery( + ) => { + + return useInfiniteQuery( variables === undefined ? ['test.infinite'] : ['test.infinite', variables], (metaData) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), options - ); - + )}; useInfiniteTestQuery.rootKey = 'test.infinite'; @@ -239,22 +257,26 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown >( dataSource: { endpoint: string, fetchParams?: RequestInit }, options?: UseMutationOptions - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), options - );" + )}; +" `; -exports[`React-Query fetcher: custom-mapper Should generate mutation correctly with lazy variables 1`] = ` +exports[`React-Query fetcher: custom-mapper Should generate mutation correctly with lazy variables: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -270,25 +292,28 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], useCustomFetcher(TestDocument).bind(null, variables), options - ); + )}; + export const useInfiniteTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseInfiniteQueryOptions - ) =>{ + ) => { const query = useCustomFetcher(TestDocument) return useInfiniteQuery( variables === undefined ? ['test.infinite'] : ['test.infinite', variables], @@ -303,19 +328,30 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], useCustomFetcher(TestDocument), options - );" + )}; +" +`; + +exports[`React-Query fetcher: custom-mapper Should generate mutation correctly with lazy variables: prepend 1`] = ` +[ + "import { useQuery, useInfiniteQuery, useMutation, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions } from 'react-query';", + "import { useCustomFetcher } from './my-file';", +] `; -exports[`React-Query fetcher: custom-mapper Should generate query correctly with external mapper 1`] = ` +exports[`React-Query fetcher: custom-mapper Should generate query correctly with external mapper: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -331,25 +367,28 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], myCustomFetcher(TestDocument, variables), options - ); + )}; + export const useInfiniteTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseInfiniteQueryOptions - ) =>{ + ) => { return useInfiniteQuery( variables === undefined ? ['test.infinite'] : ['test.infinite', variables], @@ -364,19 +403,30 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => myCustomFetcher(TestDocument, variables)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: custom-mapper Should generate query correctly with external mapper: prepend 1`] = ` +[ + "import { useQuery, useInfiniteQuery, useMutation, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions } from 'react-query';", + "import { myCustomFetcher } from './my-file';", +] `; -exports[`React-Query fetcher: custom-mapper Should generate query correctly with internal mapper 1`] = ` +exports[`React-Query fetcher: custom-mapper Should generate query correctly with internal mapper: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -392,18 +442,21 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], myCustomFetcher(TestDocument, variables), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -411,19 +464,30 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => myCustomFetcher(TestDocument, variables)(), options - );" + )}; +" `; -exports[`React-Query fetcher: fetch Should generate query and mutation correctly 1`] = ` +exports[`React-Query fetcher: custom-mapper Should generate query correctly with internal mapper: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';", + null, +] +`; + +exports[`React-Query fetcher: fetch Should generate query and mutation correctly: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -439,6 +503,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown @@ -446,12 +511,14 @@ export const useTestQuery = < dataSource: { endpoint: string, fetchParams?: RequestInit }, variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -459,22 +526,52 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown >( dataSource: { endpoint: string, fetchParams?: RequestInit }, options?: UseMutationOptions - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: fetch Should generate query and mutation correctly: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';", + " +function fetcher(endpoint: string, requestInit: RequestInit, query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch(endpoint, { + method: 'POST', + ...requestInit, + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] `; -exports[`React-Query fetcher: graphql-request Should generate query correctly with client 1`] = ` +exports[`React-Query fetcher: graphql-request Should generate query correctly with client: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -490,6 +587,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown @@ -498,12 +596,30 @@ export const useTestQuery = < variables?: TTestQueryVariables, options?: UseQueryOptions, headers?: RequestInit['headers'] - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(client, TestDocument, variables, headers), options - ); + )}; + +export const useInfiniteTestQuery = < + TData = TTestQuery, + TError = unknown + >( + client: GraphQLClient, + variables?: TTestQueryVariables, + options?: UseInfiniteQueryOptions, + headers?: RequestInit['headers'] + ) => { + + return useInfiniteQuery( + variables === undefined ? ['test.infinite'] : ['test.infinite', variables], + (metaData) => fetcher(client, TestDocument, {...variables, ...(metaData.pageParam ?? {})}, headers)(), + options + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -511,6 +627,7 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown @@ -518,16 +635,35 @@ export const useTestMutation = < client: GraphQLClient, options?: UseMutationOptions, headers?: RequestInit['headers'] - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(client, TestDocument, variables, headers)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: graphql-request Should generate query correctly with client: prepend 1`] = ` +[ + "import { GraphQLClient } from 'graphql-request';", + "import { RequestInit } from 'graphql-request/dist/types.dom';", + "import { useQuery, useInfiniteQuery, useMutation, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions } from 'react-query';", + " +function fetcher(client: GraphQLClient, query: string, variables?: TVariables, requestHeaders?: RequestInit['headers']) { + return async (): Promise => client.request({ + document: query, + variables, + requestHeaders + }); +}", +] `; -exports[`React-Query fetcher: graphql-request with clientImportPath Should generate query correctly with client 1`] = ` +exports[`React-Query fetcher: graphql-request with clientImportPath Should generate query correctly with client: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -543,6 +679,7 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown @@ -550,12 +687,14 @@ export const useTestQuery = < variables?: TTestQueryVariables, options?: UseQueryOptions, headers?: RequestInit['headers'] - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(TestDocument, variables, headers), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -563,22 +702,43 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown >( options?: UseMutationOptions, headers?: RequestInit['headers'] - ) => - useMutation( + ) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(TestDocument, variables, headers)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: graphql-request with clientImportPath Should generate query correctly with client: prepend 1`] = ` +[ + "import { client as graphqlClient } from 'client.ts';", + "import { GraphQLClient } from 'graphql-request';", + "import { RequestInit } from 'graphql-request/dist/types.dom';", + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';", + " +function fetcher(query: string, variables?: TVariables, requestHeaders?: RequestInit['headers']) { + return async (): Promise => graphqlClient.request({ + document: query, + variables, + requestHeaders + }); +}", +] `; -exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with fetch config 2`] = ` +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with fetch config and fetchParams object: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -594,18 +754,21 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(TestDocument, variables), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -613,19 +776,49 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(TestDocument, variables)(), options - );" + )}; +" `; -exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with fetch config and fetchParams object 2`] = ` +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with fetch config and fetchParams object: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';", + " +function fetcher(query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch("http://localhost:3000/graphql", { + method: "POST", + ...({"headers":{"Authorization":"Bearer XYZ"}}), + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] +`; + +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with fetch config: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -641,18 +834,21 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(TestDocument, variables), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -660,19 +856,49 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(TestDocument, variables)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with fetch config: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';", + " +function fetcher(query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch("http://localhost:3000/graphql", { + method: "POST", + ...({"headers":{"Authorization":"Bearer XYZ"}}), + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] `; -exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint 1`] = ` +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint from env var: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -688,18 +914,21 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(TestDocument, variables), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -707,19 +936,48 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(TestDocument, variables)(), options - );" + )}; +" `; -exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint from env var 1`] = ` +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint from env var: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';", + " +function fetcher(query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch(process.env.ENDPOINT_URL as string, { + method: "POST", + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] +`; + +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint from just identifier: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -735,18 +993,21 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(TestDocument, variables), options - ); + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -754,19 +1015,48 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(TestDocument, variables)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint from just identifier: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';", + " +function fetcher(query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch(myEndpoint as string, { + method: "POST", + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] `; -exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint from just identifier 1`] = ` +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint: content 1`] = ` " + export const TestDocument = \` query test { feed { @@ -782,18 +1072,35 @@ export const TestDocument = \` } } \`; + export const useTestQuery = < TData = TTestQuery, TError = unknown >( variables?: TTestQueryVariables, options?: UseQueryOptions - ) => - useQuery( + ) => { + + return useQuery( variables === undefined ? ['test'] : ['test', variables], fetcher(TestDocument, variables), options - ); + )}; + +export const useInfiniteTestQuery = < + TData = TTestQuery, + TError = unknown + >( + variables?: TTestQueryVariables, + options?: UseInfiniteQueryOptions + ) => { + + return useInfiniteQuery( + variables === undefined ? ['test.infinite'] : ['test.infinite', variables], + (metaData) => fetcher(TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), + options + )}; + export const TestDocument = \` mutation test($name: String) { submitRepository(repoFullName: $name) { @@ -801,13 +1108,311 @@ export const TestDocument = \` } } \`; + export const useTestMutation = < TError = unknown, TContext = unknown - >(options?: UseMutationOptions) => - useMutation( + >(options?: UseMutationOptions) => { + + return useMutation( ['test'], (variables?: TTestMutationVariables) => fetcher(TestDocument, variables)(), options - );" + )}; +" +`; + +exports[`React-Query fetcher: hardcoded-fetch Should generate query correctly with hardcoded endpoint: prepend 1`] = ` +[ + "import { useQuery, useInfiniteQuery, useMutation, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions } from 'react-query';", + " +function fetcher(query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch("http://localhost:3000/graphql", { + method: "POST", + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] +`; + +exports[`React-Query reactQueryImportFrom: custom-path Should import react-query from custom path: prepend 1`] = ` +[ + "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'custom-react-query';", + " +function fetcher(endpoint: string, requestInit: RequestInit, query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch(endpoint, { + method: 'POST', + ...requestInit, + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] +`; + +exports[`React-Query support v4 syntax: content 1`] = ` +" + +export const TestDocument = \` + query test { + feed { + id + commentCount + repository { + full_name + html_url + owner { + avatar_url + } + } + } +} + \`; + +export const useTestQuery = < + TData = TestQuery, + TError = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + variables?: TestQueryVariables, + options?: UseQueryOptions + ) => { + + return useQuery( + variables === undefined ? ['test'] : ['test', variables], + fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), + options + )}; + +export const useInfiniteTestQuery = < + TData = TestQuery, + TError = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + variables?: TestQueryVariables, + options?: UseInfiniteQueryOptions + ) => { + + return useInfiniteQuery( + variables === undefined ? ['test.infinite'] : ['test.infinite', variables], + (metaData) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), + options + )}; + +export const TestDocument = \` + mutation test($name: String) { + submitRepository(repoFullName: $name) { + id + } +} + \`; + +export const useTestMutation = < + TError = unknown, + TContext = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + options?: UseMutationOptions + ) => { + + return useMutation( + ['test'], + (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), + options + )}; +" +`; + +exports[`React-Query support v4 syntax: prepend 1`] = ` +[ + "import { useQuery, useInfiniteQuery, useMutation, type UseQueryOptions, type UseInfiniteQueryOptions, type UseMutationOptions } from '@tanstack/react-query';", + " +function fetcher(endpoint: string, requestInit: RequestInit, query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch(endpoint, { + method: 'POST', + ...requestInit, + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] +`; + +exports[`React-Query support v5 syntax: content 1`] = ` +" + +export const TestDocument = \` + query test { + feed { + id + commentCount + repository { + full_name + html_url + owner { + avatar_url + } + } + } +} + \`; + +export const useTestQuery = < + TData = TestQuery, + TError = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + variables?: TestQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } + ) => { + + return useQuery( + { + queryKey: variables === undefined ? ['test'] : ['test', variables], + queryFn: fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), + ...options + } + )}; + +export const useSuspenseTestQuery = < + TData = TestQuery, + TError = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + variables?: TestQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseSuspenseQueryOptions['queryKey'] } + ) => { + + return useSuspenseQuery( + { + queryKey: variables === undefined ? ['testSuspense'] : ['testSuspense', variables], + queryFn: fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), + ...options + } + )}; + +export const useInfiniteTestQuery = < + TData = InfiniteData, + TError = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + variables: TestQueryVariables, + options: Omit, 'queryKey'> & { queryKey?: UseInfiniteQueryOptions['queryKey'] } + ) => { + + return useInfiniteQuery( + (() => { + const { queryKey: optionsQueryKey, ...restOptions } = options; + return { + queryKey: optionsQueryKey ?? variables === undefined ? ['test.infinite'] : ['test.infinite', variables], + queryFn: (metaData) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), + ...restOptions + } + })() + )}; + +export const useSuspenseInfiniteTestQuery = < + TData = InfiniteData, + TError = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + variables: TestQueryVariables, + options: Omit, 'queryKey'> & { queryKey?: UseSuspenseInfiniteQueryOptions['queryKey'] } + ) => { + + return useSuspenseInfiniteQuery( + (() => { + const { queryKey: optionsQueryKey, ...restOptions } = options; + return { + queryKey: optionsQueryKey ?? variables === undefined ? ['test.infiniteSuspense'] : ['test.infiniteSuspense', variables], + queryFn: (metaData) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), + ...restOptions + } + })() + )}; + +export const TestDocument = \` + mutation test($name: String) { + submitRepository(repoFullName: $name) { + id + } +} + \`; + +export const useTestMutation = < + TError = unknown, + TContext = unknown + >( + dataSource: { endpoint: string, fetchParams?: RequestInit }, + options?: UseMutationOptions + ) => { + + return useMutation( + { + mutationKey: ['test'], + mutationFn: (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), + ...options + } + )}; +" +`; + +exports[`React-Query support v5 syntax: prepend 1`] = ` +[ + "import { useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryOptions, UseSuspenseQueryOptions, UseInfiniteQueryOptions, InfiniteData, UseSuspenseInfiniteQueryOptions, UseMutationOptions } from '@tanstack/react-query';", + " +function fetcher(endpoint: string, requestInit: RequestInit, query: string, variables?: TVariables) { + return async (): Promise => { + const res = await fetch(endpoint, { + method: 'POST', + ...requestInit, + body: JSON.stringify({ query, variables }), + }); + + const json = await res.json(); + + if (json.errors) { + const { message } = json.errors[0]; + + throw new Error(message); + } + + return json.data; + } +}", +] `; diff --git a/packages/plugins/typescript/react-query/tests/react-query.spec.ts b/packages/plugins/typescript/react-query/tests/react-query.spec.ts index 6f8eeebb1..815432f1d 100644 --- a/packages/plugins/typescript/react-query/tests/react-query.spec.ts +++ b/packages/plugins/typescript/react-query/tests/react-query.spec.ts @@ -3,6 +3,7 @@ import { mergeOutputs, Types } from '@graphql-codegen/plugin-helpers'; import { validateTs } from '@graphql-codegen/testing'; import { plugin as tsPlugin } from '@graphql-codegen/typescript'; import { plugin as tsDocumentsPlugin } from '@graphql-codegen/typescript-operations'; +import type { ReactQueryRawPluginConfig } from '../src/config'; import { plugin } from '../src/index.js'; const validateTypeScript = async ( @@ -31,92 +32,25 @@ describe('React-Query', () => { const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toEqual([ - `import { useQuery, useInfiniteQuery, useMutation, type UseQueryOptions, type UseInfiniteQueryOptions, type UseMutationOptions } from '@tanstack/react-query';`, - ` -function fetcher(endpoint: string, requestInit: RequestInit, query: string, variables?: TVariables) { - return async (): Promise => { - const res = await fetch(endpoint, { - method: 'POST', - ...requestInit, - body: JSON.stringify({ query, variables }), - }); - - const json = await res.json(); - - if (json.errors) { - const { message } = json.errors[0]; + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); + await validateTypeScript(mergeOutputs(out), schema, docs, config); + }); - throw new Error(message); - } + it('support v5 syntax', async () => { + const config: ReactQueryRawPluginConfig = { + reactQueryVersion: 5, + addInfiniteQuery: true, + addSuspenseQuery: true, + }; - return json.data; - } -}`, - ]); - expect(out.content).toEqual(` -export const TestDocument = \` - query test { - feed { - id - commentCount - repository { - full_name - html_url - owner { - avatar_url - } - } - } -} - \`; -export const useTestQuery = < - TData = TestQuery, - TError = unknown - >( - dataSource: { endpoint: string, fetchParams?: RequestInit }, - variables?: TestQueryVariables, - options?: UseQueryOptions - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), - options - ); -export const useInfiniteTestQuery = < - TData = TestQuery, - TError = unknown - >( - dataSource: { endpoint: string, fetchParams?: RequestInit }, - variables?: TestQueryVariables, - options?: UseInfiniteQueryOptions - ) => - useInfiniteQuery( - variables === undefined ? ['test.infinite'] : ['test.infinite', variables], - (metaData) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), - options - ); + const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; -export const TestDocument = \` - mutation test($name: String) { - submitRepository(repoFullName: $name) { - id - } -} - \`; -export const useTestMutation = < - TError = unknown, - TContext = unknown - >( - dataSource: { endpoint: string, fetchParams?: RequestInit }, - options?: UseMutationOptions - ) => - useMutation( - ['test'], - (variables?: TestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), - options - );`); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); + it('Duplicated nested fragments are removed', async () => { const schema = buildSchema(/* GraphQL */ ` schema { @@ -241,47 +175,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useInfiniteQuery, useMutation, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions } from 'react-query';`, - ); - - expect(out.prepend).toContain(`import { myCustomFetcher } from './my-file';`); - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseQueryOptions - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - myCustomFetcher(TestDocument, variables), - options - );`); - - expect(out.content).toBeSimilarStringTo(`export const useInfiniteTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseInfiniteQueryOptions - ) =>{ - return useInfiniteQuery( - variables === undefined ? ['test.infinite'] : ['test.infinite', variables], - (metaData) => myCustomFetcher(TestDocument, {...variables, ...(metaData.pageParam ?? {})})(), - options - )};`); - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >(options?: UseMutationOptions) => - useMutation( - ['test'], - (variables?: TTestMutationVariables) => myCustomFetcher(TestDocument, variables)(), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -294,33 +189,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';`, - ); - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseQueryOptions - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - myCustomFetcher(TestDocument, variables), - options - );`); - - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >(options?: UseMutationOptions) => - useMutation( - ['test'], - (variables?: TTestMutationVariables) => myCustomFetcher(TestDocument, variables)(), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -337,47 +207,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useInfiniteQuery, useMutation, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions } from 'react-query';`, - ); - expect(out.prepend).toContain(`import { useCustomFetcher } from './my-file';`); - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseQueryOptions - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - useCustomFetcher(TestDocument).bind(null, variables), - options - );`); - - expect(out.content).toBeSimilarStringTo(`export const useInfiniteTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseInfiniteQueryOptions - ) =>{ - const query = useCustomFetcher(TestDocument) - return useInfiniteQuery( - variables === undefined ? ['test.infinite'] : ['test.infinite', variables], - (metaData) => query({...variables, ...(metaData.pageParam ?? {})}), - options - )};`); - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >(options?: UseMutationOptions) => - useMutation( - ['test'], - useCustomFetcher(TestDocument), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -572,54 +403,14 @@ export const useTestMutation = < const config = { fetcher: 'graphql-request', typesPrefix: 'T', + addInfiniteQuery: true, legacyMode: true, }; const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';`, - ); - expect(out.prepend).toContain(`import { GraphQLClient } from 'graphql-request';`); - expect(out.prepend).toContain( - `import { RequestInit } from 'graphql-request/dist/types.dom';`, - ); - expect(out.prepend[3]) - .toBeSimilarStringTo(` function fetcher(client: GraphQLClient, query: string, variables?: TVariables, requestHeaders?: RequestInit['headers']) { - return async (): Promise => client.request({ - document: query, - variables, - requestHeaders - `); - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - client: GraphQLClient, - variables?: TTestQueryVariables, - options?: UseQueryOptions, - headers?: RequestInit['headers'] - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - fetcher(client, TestDocument, variables, headers), - options - );`); - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >( - client: GraphQLClient, - options?: UseMutationOptions, - headers?: RequestInit['headers'] - ) => - useMutation( - ['test'], - (variables?: TTestMutationVariables) => fetcher(client, TestDocument, variables, headers)(), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should support useTypeImports', async () => { @@ -768,48 +559,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';`, - ); - expect(out.prepend).toContain(clientImportPath); - expect(out.prepend).toContain(`import { GraphQLClient } from 'graphql-request';`); - expect(out.prepend).toContain( - `import { RequestInit } from 'graphql-request/dist/types.dom';`, - ); - expect(out.prepend[4]) - .toBeSimilarStringTo(` function fetcher(query: string, variables?: TVariables, requestHeaders?: RequestInit['headers']) { - return async (): Promise => graphqlClient.request({ - document: query, - variables, - requestHeaders - `); - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseQueryOptions, - headers?: RequestInit['headers'] - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - fetcher(TestDocument, variables, headers), - options - );`); - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >( - options?: UseMutationOptions, - headers?: RequestInit['headers'] - ) => - useMutation( - ['test'], - (variables?: TTestMutationVariables) => fetcher(TestDocument, variables, headers)(), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); it('Should generate fetcher field when exposeFetcher is true', async () => { @@ -835,56 +586,13 @@ export const useTestMutation = < }, typesPrefix: 'T', legacyMode: true, + addInfiniteQuery: true, }; const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';`, - ); - expect(out.prepend[1]) - .toBeSimilarStringTo(` function fetcher(query: string, variables?: TVariables) { - return async (): Promise => { - const res = await fetch("http://localhost:3000/graphql", { - method: "POST", - body: JSON.stringify({ query, variables }), - }); - - const json = await res.json(); - - if (json.errors) { - const { message } = json.errors[0]; - - throw new Error(message); - } - - return json.data; - } - }`); - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - variables?: TTestQueryVariables, - options?: UseQueryOptions - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - fetcher(TestDocument, variables), - options - );`); - - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >(options?: UseMutationOptions) => - useMutation( - ['test'], - (variables?: TTestMutationVariables) => fetcher(TestDocument, variables)(), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -903,30 +611,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend[1]).toMatchInlineSnapshot(` - " - function fetcher(query: string, variables?: TVariables) { - return async (): Promise => { - const res = await fetch("http://localhost:3000/graphql", { - method: "POST", - ...({"headers":{"Authorization":"Bearer XYZ"}}), - body: JSON.stringify({ query, variables }), - }); - - const json = await res.json(); - - if (json.errors) { - const { message } = json.errors[0]; - - throw new Error(message); - } - - return json.data; - } - }" - `); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -945,30 +631,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend[1]).toMatchInlineSnapshot(` - " - function fetcher(query: string, variables?: TVariables) { - return async (): Promise => { - const res = await fetch("http://localhost:3000/graphql", { - method: "POST", - ...({"headers":{"Authorization":"Bearer XYZ"}}), - body: JSON.stringify({ query, variables }), - }); - - const json = await res.json(); - - if (json.errors) { - const { message } = json.errors[0]; - - throw new Error(message); - } - - return json.data; - } - }" - `); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -982,27 +646,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend[1]) - .toBeSimilarStringTo(`function fetcher(query: string, variables?: TVariables) { - return async (): Promise => { - const res = await fetch(process.env.ENDPOINT_URL as string, { - method: "POST", - body: JSON.stringify({ query, variables }), - }); - - const json = await res.json(); - - if (json.errors) { - const { message } = json.errors[0]; - - throw new Error(message); - } - - return json.data; - } - }`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -1016,27 +661,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend[1]) - .toBeSimilarStringTo(` function fetcher(query: string, variables?: TVariables) { - return async (): Promise => { - const res = await fetch(myEndpoint as string, { - method: "POST", - body: JSON.stringify({ query, variables }), - }); - - const json = await res.json(); - - if (json.errors) { - const { message } = json.errors[0]; - - throw new Error(message); - } - - return json.data; - } - }`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -1173,38 +799,8 @@ export const useTestMutation = < const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - `import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';`, - ); - - expect(out.content).toBeSimilarStringTo(`export const useTestQuery = < - TData = TTestQuery, - TError = unknown - >( - dataSource: { endpoint: string, fetchParams?: RequestInit }, - variables?: TTestQueryVariables, - options?: UseQueryOptions - ) => - useQuery( - variables === undefined ? ['test'] : ['test', variables], - fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables), - options - );`); - - expect(out.content).toBeSimilarStringTo(`export const useTestMutation = < - TError = unknown, - TContext = unknown - >( - dataSource: { endpoint: string, fetchParams?: RequestInit }, - options?: UseMutationOptions - ) => - useMutation( - ['test'], - (variables?: TTestMutationVariables) => fetcher(dataSource.endpoint, dataSource.fetchParams || {}, TestDocument, variables)(), - options - );`); - - expect(out.content).toMatchSnapshot(); + expect(out.prepend).toMatchSnapshot('prepend'); + expect(out.content).toMatchSnapshot('content'); await validateTypeScript(mergeOutputs(out), schema, docs, config); }); @@ -1448,12 +1044,8 @@ export const useTestMutation = < reactQueryImportFrom: 'custom-react-query', }; const out = (await plugin(schema, docs, config)) as Types.ComplexPluginOutput; - expect(out.prepend).toContain( - "import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'custom-react-query';", - ); - expect(out.prepend).not.toContain( - `"import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from 'react-query';"`, - ); + expect(out.prepend).toMatchSnapshot('prepend'); + await validateTypeScript(mergeOutputs(out), schema, docs, config); }); }); });