diff --git a/packages/query/package.json b/packages/query/package.json index f5e868ea0..b8ad992bd 100644 --- a/packages/query/package.json +++ b/packages/query/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@orval/core": "6.31.0", + "@orval/fetch": "6.31.0", "lodash.omitby": "^4.6.0" }, "devDependencies": { diff --git a/packages/query/src/client.ts b/packages/query/src/client.ts index a22fe0afd..f6e7759e5 100644 --- a/packages/query/src/client.ts +++ b/packages/query/src/client.ts @@ -6,12 +6,22 @@ import { GeneratorMutator, GetterResponse, pascal, - GetterProps, - ContextSpecs, GeneratorDependency, + GeneratorOptions, + OutputHttpClient, + VERBS_WITH_BODY, + generateFormDataAndUrlEncodedFunction, + generateMutatorConfig, + generateMutatorRequestOptions, } from '@orval/core'; -import { vueUnRefParams } from './utils'; +import { generateRequestFunction as generateFetchRequestFunction } from '@orval/fetch'; + +import { + makeRouteSafe, + vueUnRefParams, + vueWrapTypeWithMaybeRef, +} from './utils'; export const AXIOS_DEPENDENCIES: GeneratorDependency[] = [ { @@ -30,42 +40,133 @@ export const AXIOS_DEPENDENCIES: GeneratorDependency[] = [ }, ]; -export const generateRequestOptionsArguments = ({ - isRequestOptions, - hasSignal, -}: { - isRequestOptions: boolean; - hasSignal: boolean; -}) => { - if (isRequestOptions) { - return 'options?: AxiosRequestConfig\n'; +export const generateQueryRequestFunction = ( + verbOptions: GeneratorVerbOptions, + options: GeneratorOptions, + isVue: boolean, +) => { + if (options.context.output.httpClient === OutputHttpClient.AXIOS) { + return generateAxiosRequestFunction(verbOptions, options, isVue); + } else { + return generateFetchRequestFunction(verbOptions, options); } - - return hasSignal ? 'signal?: AbortSignal\n' : ''; }; -export const generateQueryHttpRequestFunction = ( +export const generateAxiosRequestFunction = ( { headers, queryParams, operationName, response, + mutator, body, + props: _props, verb, - paramsSerializer, + formData, + formUrlEncoded, override, + paramsSerializer, }: GeneratorVerbOptions, - context: ContextSpecs, - props: GetterProps, - route: string, + { route: _route, context }: GeneratorOptions, isVue: boolean, - isRequestOptions: boolean, - isFormData: boolean, - isFormUrlEncoded: boolean, - hasSignal: boolean, - isExactOptionalPropertyTypes: boolean, - bodyForm: string, ) => { + let props = _props; + let route = _route; + + if (isVue) { + props = vueWrapTypeWithMaybeRef(_props); + } + + if (context.output?.urlEncodeParameters) { + route = makeRouteSafe(route); + } + + const isRequestOptions = override.requestOptions !== false; + const isFormData = override.formData !== false; + const isFormUrlEncoded = override.formUrlEncoded !== false; + const hasSignal = !!override.query.signal; + + const isExactOptionalPropertyTypes = + !!context.output.tsconfig?.compilerOptions?.exactOptionalPropertyTypes; + const isBodyVerb = VERBS_WITH_BODY.includes(verb); + + const bodyForm = generateFormDataAndUrlEncodedFunction({ + formData, + formUrlEncoded, + body, + isFormData, + isFormUrlEncoded, + }); + + if (mutator) { + const mutatorConfig = generateMutatorConfig({ + route, + body, + headers, + queryParams, + response, + verb, + isFormData, + isFormUrlEncoded, + isBodyVerb, + hasSignal, + isExactOptionalPropertyTypes, + isVue, + }); + + let bodyDefinition = body.definition.replace('[]', '\\[\\]'); + let propsImplementation = + mutator?.bodyTypeName && body.definition + ? toObjectString(props, 'implementation').replace( + new RegExp(`(\\w*):\\s?${bodyDefinition}`), + `$1: ${mutator.bodyTypeName}<${body.definition}>`, + ) + : toObjectString(props, 'implementation'); + + const requestOptions = isRequestOptions + ? generateMutatorRequestOptions( + override.requestOptions, + mutator.hasSecondArg, + ) + : ''; + + if (mutator.isHook) { + return `${ + override.query.shouldExportMutatorHooks ? 'export ' : '' + }const use${pascal(operationName)}Hook = () => { + const ${operationName} = ${mutator.name}<${ + response.definition.success || 'unknown' + }>(); + + return useCallback((\n ${propsImplementation}\n ${ + isRequestOptions && mutator.hasSecondArg + ? `options${context.output.optionsParamRequired ? '' : '?'}: SecondParameter>,` + : '' + }${ + !isBodyVerb && hasSignal ? 'signal?: AbortSignal\n' : '' + }) => {${bodyForm} + return ${operationName}( + ${mutatorConfig}, + ${requestOptions}); + }, [${operationName}]) + } + `; + } + + return `${override.query.shouldExportHttpClient ? 'export ' : ''}const ${operationName} = (\n ${propsImplementation}\n ${ + isRequestOptions && mutator.hasSecondArg + ? `options${context.output.optionsParamRequired ? '' : '?'}: SecondParameter,` + : '' + }${!isBodyVerb && hasSignal ? 'signal?: AbortSignal\n' : ''}) => { + ${isVue ? vueUnRefParams(props) : ''} + ${bodyForm} + return ${mutator.name}<${response.definition.success || 'unknown'}>( + ${mutatorConfig}, + ${requestOptions}); + } + `; + } + const isSyntheticDefaultImportsAllowed = isSyntheticDefaultImportsAllow( context.output.tsconfig, ); @@ -94,7 +195,7 @@ export const generateQueryHttpRequestFunction = ( const queryProps = toObjectString(props, 'implementation'); - return `export const ${operationName} = (\n ${queryProps} ${optionsArgs} ): Promise> => {${bodyForm} ${isVue ? vueUnRefParams(props) : ''} @@ -103,11 +204,32 @@ export const generateQueryHttpRequestFunction = ( }.${verb}(${options}); } `; + + return httpRequestFunctionImplementation; }; -export const getQueryArgumentsRequestType = (mutator?: GeneratorMutator) => { +export const generateRequestOptionsArguments = ({ + isRequestOptions, + hasSignal, +}: { + isRequestOptions: boolean; + hasSignal: boolean; +}) => { + if (isRequestOptions) { + return 'options?: AxiosRequestConfig\n'; + } + + return hasSignal ? 'signal?: AbortSignal\n' : ''; +}; + +export const getQueryArgumentsRequestType = ( + httpClient: OutputHttpClient, + mutator?: GeneratorMutator, +) => { if (!mutator) { - return `axios?: AxiosRequestConfig`; + return httpClient === OutputHttpClient.AXIOS + ? `axios?: AxiosRequestConfig` + : 'fetch?: RequestInit'; } if (mutator.hasSecondArg && !mutator.isHook) { @@ -126,19 +248,25 @@ export const getQueryOptions = ({ mutator, isExactOptionalPropertyTypes, hasSignal, + httpClient, }: { isRequestOptions: boolean; mutator?: GeneratorMutator; isExactOptionalPropertyTypes: boolean; hasSignal: boolean; + httpClient: OutputHttpClient; }) => { if (!mutator && isRequestOptions) { + const options = + httpClient === OutputHttpClient.AXIOS ? 'axiosOptions' : 'fetchOptions'; + if (!hasSignal) { - return 'axiosOptions'; + return options; } + return `{ ${ isExactOptionalPropertyTypes ? '...(signal ? { signal } : {})' : 'signal' - }, ...axiosOptions }`; + }, ...${options} }`; } if (mutator?.hasSecondArg && isRequestOptions) { @@ -146,7 +274,9 @@ export const getQueryOptions = ({ return 'requestOptions'; } - return 'requestOptions, signal'; + return httpClient === OutputHttpClient.AXIOS + ? 'requestOptions, signal' + : '{ signal, ...requestOptions }'; } if (hasSignal) { @@ -158,9 +288,11 @@ export const getQueryOptions = ({ export const getHookOptions = ({ isRequestOptions, + httpClient, mutator, }: { isRequestOptions: boolean; + httpClient: OutputHttpClient; mutator?: GeneratorMutator; }) => { if (!isRequestOptions) { @@ -170,7 +302,12 @@ export const getHookOptions = ({ let value = 'const {query: queryOptions'; if (!mutator) { - value += ', axios: axiosOptions'; + const options = + httpClient === OutputHttpClient.AXIOS + ? ', axios: axiosOptions' + : ', fetch: fetchOptions'; + + value += options; } if (mutator?.hasSecondArg) { @@ -185,29 +322,37 @@ export const getHookOptions = ({ export const getQueryErrorType = ( operationName: string, response: GetterResponse, + httpClient: OutputHttpClient, mutator?: GeneratorMutator, ) => { - let errorType = `AxiosError<${response.definition.errors || 'unknown'}>`; - if (mutator) { - errorType = mutator.hasErrorType + return mutator.hasErrorType ? `${mutator.default ? pascal(operationName) : ''}ErrorType<${ response.definition.errors || 'unknown' }>` : response.definition.errors || 'unknown'; - } + } else { + const errorType = + httpClient === OutputHttpClient.AXIOS ? 'AxiosError' : 'Promise'; - return errorType; + return `${errorType}<${response.definition.errors || 'unknown'}>`; + } }; export const getHooksOptionImplementation = ( isRequestOptions: boolean, + httpClient: OutputHttpClient, mutator?: GeneratorMutator, ) => { + const options = + httpClient === OutputHttpClient.AXIOS + ? ', axios: axiosOptions' + : ', fetch: fetchOptions'; + return isRequestOptions ? `const {mutation: mutationOptions${ !mutator - ? `, axios: axiosOptions` + ? options : mutator?.hasSecondArg ? ', request: requestOptions' : '' @@ -217,11 +362,15 @@ export const getHooksOptionImplementation = ( export const getMutationRequestArgs = ( isRequestOptions: boolean, + httpClient: OutputHttpClient, mutator?: GeneratorMutator, ) => { + const options = + httpClient === OutputHttpClient.AXIOS ? 'axiosOptions' : 'fetchOptions'; + return isRequestOptions ? !mutator - ? `axiosOptions` + ? options : mutator?.hasSecondArg ? 'requestOptions' : '' diff --git a/packages/query/src/index.ts b/packages/query/src/index.ts index c3bc6cc38..c6c1b46c4 100644 --- a/packages/query/src/index.ts +++ b/packages/query/src/index.ts @@ -3,10 +3,7 @@ import { ClientBuilder, ClientDependenciesBuilder, ClientHeaderBuilder, - generateFormDataAndUrlEncodedFunction, generateMutator, - generateMutatorConfig, - generateMutatorRequestOptions, generateVerbImports, GeneratorDependency, GeneratorMutator, @@ -27,18 +24,17 @@ import { stringify, toObjectString, Verbs, - VERBS_WITH_BODY, jsDoc, GetterQueryParam, compareVersions, getRouteAsArray, NormalizedOutputOptions, + OutputHttpClient, } from '@orval/core'; import omitBy from 'lodash.omitby'; import { normalizeQueryOptions, isVue, - makeRouteSafe, vueWrapTypeWithMaybeRef, vueUnRefParams, } from './utils'; @@ -50,7 +46,7 @@ import { getQueryErrorType, getHooksOptionImplementation, getMutationRequestArgs, - generateQueryHttpRequestFunction, + generateQueryRequestFunction, } from './client'; const REACT_DEPENDENCIES: GeneratorDependency[] = [ @@ -138,11 +134,14 @@ export const getSvelteQueryDependencies: ClientDependenciesBuilder = ( hasGlobalMutator, hasParamsSerializerOptions, packageJson, + httpClient?: OutputHttpClient, ) => { const hasSvelteQueryV3 = isSvelteQueryV3(packageJson); return [ - ...(!hasGlobalMutator ? AXIOS_DEPENDENCIES : []), + ...(!hasGlobalMutator && httpClient === OutputHttpClient.AXIOS + ? AXIOS_DEPENDENCIES + : []), ...(hasParamsSerializerOptions ? PARAMS_SERIALIZER_DEPENDENCIES : []), ...(hasSvelteQueryV3 ? SVELTE_QUERY_DEPENDENCIES_V3 @@ -202,6 +201,7 @@ export const getReactQueryDependencies: ClientDependenciesBuilder = ( hasGlobalMutator, hasParamsSerializerOptions, packageJson, + httpClient, ) => { const hasReactQuery = packageJson?.dependencies?.['react-query'] ?? @@ -212,7 +212,9 @@ export const getReactQueryDependencies: ClientDependenciesBuilder = ( return [ ...(hasGlobalMutator ? REACT_DEPENDENCIES : []), - ...(!hasGlobalMutator ? AXIOS_DEPENDENCIES : []), + ...(!hasGlobalMutator && httpClient === OutputHttpClient.AXIOS + ? AXIOS_DEPENDENCIES + : []), ...(hasParamsSerializerOptions ? PARAMS_SERIALIZER_DEPENDENCIES : []), ...(hasReactQuery && !hasReactQueryV4 ? REACT_QUERY_DEPENDENCIES_V3 @@ -300,11 +302,14 @@ export const getVueQueryDependencies: ClientDependenciesBuilder = ( hasGlobalMutator: boolean, hasParamsSerializerOptions: boolean, packageJson, + httpClient?: OutputHttpClient, ) => { const hasVueQueryV3 = isVueQueryV3(packageJson); return [ - ...(!hasGlobalMutator ? AXIOS_DEPENDENCIES : []), + ...(!hasGlobalMutator && httpClient === OutputHttpClient.AXIOS + ? AXIOS_DEPENDENCIES + : []), ...(hasParamsSerializerOptions ? PARAMS_SERIALIZER_DEPENDENCIES : []), ...(hasVueQueryV3 ? VUE_QUERY_DEPENDENCIES_V3 : VUE_QUERY_DEPENDENCIES), ]; @@ -351,140 +356,6 @@ const getPackageByQueryClient = ( } }; -const generateQueryRequestFunction = ( - verbOptions: GeneratorVerbOptions, - { route: _route, context }: GeneratorOptions, - isVue: boolean, - output?: NormalizedOutputOptions, -) => { - const { - headers, - queryParams, - operationName, - response, - mutator, - body, - props: _props, - verb, - formData, - formUrlEncoded, - override, - } = verbOptions; - - let props = _props; - let route = _route; - - if (isVue) { - props = vueWrapTypeWithMaybeRef(_props); - } - - if (output?.urlEncodeParameters) { - route = makeRouteSafe(route); - } - - const isRequestOptions = override.requestOptions !== false; - const isFormData = override.formData !== false; - const isFormUrlEncoded = override.formUrlEncoded !== false; - const hasSignal = !!override.query.signal; - - const isExactOptionalPropertyTypes = - !!context.output.tsconfig?.compilerOptions?.exactOptionalPropertyTypes; - const isBodyVerb = VERBS_WITH_BODY.includes(verb); - - const bodyForm = generateFormDataAndUrlEncodedFunction({ - formData, - formUrlEncoded, - body, - isFormData, - isFormUrlEncoded, - }); - - if (mutator) { - const mutatorConfig = generateMutatorConfig({ - route, - body, - headers, - queryParams, - response, - verb, - isFormData, - isFormUrlEncoded, - isBodyVerb, - hasSignal, - isExactOptionalPropertyTypes, - isVue, - }); - - let bodyDefinition = body.definition.replace('[]', '\\[\\]'); - let propsImplementation = - mutator?.bodyTypeName && body.definition - ? toObjectString(props, 'implementation').replace( - new RegExp(`(\\w*):\\s?${bodyDefinition}`), - `$1: ${mutator.bodyTypeName}<${body.definition}>`, - ) - : toObjectString(props, 'implementation'); - - const requestOptions = isRequestOptions - ? generateMutatorRequestOptions( - override.requestOptions, - mutator.hasSecondArg, - ) - : ''; - - if (mutator.isHook) { - return `${ - override.query.shouldExportMutatorHooks ? 'export ' : '' - }const use${pascal(operationName)}Hook = () => { - const ${operationName} = ${mutator.name}<${ - response.definition.success || 'unknown' - }>(); - - return useCallback((\n ${propsImplementation}\n ${ - isRequestOptions && mutator.hasSecondArg - ? `options${context.output.optionsParamRequired ? '' : '?'}: SecondParameter>,` - : '' - }${ - !isBodyVerb && hasSignal ? 'signal?: AbortSignal\n' : '' - }) => {${bodyForm} - return ${operationName}( - ${mutatorConfig}, - ${requestOptions}); - }, [${operationName}]) - } - `; - } - - return `${override.query.shouldExportHttpClient ? 'export ' : ''}const ${operationName} = (\n ${propsImplementation}\n ${ - isRequestOptions && mutator.hasSecondArg - ? `options${context.output.optionsParamRequired ? '' : '?'}: SecondParameter,` - : '' - }${!isBodyVerb && hasSignal ? 'signal?: AbortSignal\n' : ''}) => { - ${isVue ? vueUnRefParams(props) : ''} - ${bodyForm} - return ${mutator.name}<${response.definition.success || 'unknown'}>( - ${mutatorConfig}, - ${requestOptions}); - } - `; - } - - const httpRequestFunctionImplementation = generateQueryHttpRequestFunction( - verbOptions, - context, - props, - route, - isVue, - isRequestOptions, - isFormData, - isFormUrlEncoded, - hasSignal, - isExactOptionalPropertyTypes, - bodyForm, - ); - - return httpRequestFunctionImplementation; -}; - type QueryType = 'infiniteQuery' | 'query'; const QueryType = { @@ -603,6 +474,7 @@ const generateQueryArguments = ({ hasQueryV5, queryParams, queryParam, + httpClient, }: { operationName: string; definitions: string; @@ -613,6 +485,7 @@ const generateQueryArguments = ({ hasQueryV5: boolean; queryParams?: GetterQueryParam; queryParam?: string; + httpClient: OutputHttpClient; }) => { const definition = getQueryOptionsDefinition({ operationName, @@ -630,7 +503,7 @@ const generateQueryArguments = ({ return `${type ? 'queryOptions' : 'mutationOptions'}?: ${definition}`; } - const requestType = getQueryArgumentsRequestType(mutator); + const requestType = getQueryArgumentsRequestType(httpClient, mutator); return `options?: { ${ type ? 'query' : 'mutation' @@ -761,6 +634,7 @@ const generateQueryImplementation = ({ isRequestOptions, response, outputClient, + httpClient, isExactOptionalPropertyTypes, hasSignal, route, @@ -789,6 +663,7 @@ const generateQueryImplementation = ({ queryOptionsMutator?: GeneratorMutator; queryKeyMutator?: GeneratorMutator; outputClient: OutputClient | OutputClientFunc; + httpClient: OutputHttpClient; isExactOptionalPropertyTypes: boolean; hasSignal: boolean; route: string; @@ -830,7 +705,12 @@ const generateQueryImplementation = ({ hasSvelteQueryV4, }); - const errorType = getQueryErrorType(operationName, response, mutator); + const errorType = getQueryErrorType( + operationName, + response, + httpClient, + mutator, + ); const dataType = mutator?.isHook ? `ReturnType` @@ -846,6 +726,7 @@ const generateQueryImplementation = ({ hasQueryV5, queryParams, queryParam, + httpClient, }); const queryOptions = getQueryOptions({ @@ -853,10 +734,12 @@ const generateQueryImplementation = ({ isExactOptionalPropertyTypes, mutator, hasSignal, + httpClient, }); const hookOptions = getHookOptions({ isRequestOptions, + httpClient, mutator, }); @@ -1062,6 +945,7 @@ const generateQueryHook = async ( outputClient as 'react-query' | 'vue-query' | 'svelte-query', ); + const httpClient = context.output.httpClient; const doc = jsDoc({ summary, deprecated }); let implementation = ''; @@ -1211,6 +1095,7 @@ const generateQueryHook = async ( queryParams, response, outputClient, + httpClient, isExactOptionalPropertyTypes, hasSignal: !!query.signal, queryOptionsMutator, @@ -1265,7 +1150,12 @@ const generateQueryHook = async ( .map(({ name, type }) => (type === GetterPropType.BODY ? 'data' : name)) .join(','); - let errorType = getQueryErrorType(operationName, response, mutator); + let errorType = getQueryErrorType( + operationName, + response, + httpClient, + mutator, + ); const dataType = mutator?.isHook ? `ReturnType` @@ -1287,6 +1177,7 @@ const generateQueryHook = async ( isRequestOptions, hasSvelteQueryV4, hasQueryV5, + httpClient, }); const mutationOptionsFnName = camel( @@ -1301,6 +1192,7 @@ const generateQueryHook = async ( const hooksOptionImplementation = getHooksOptionImplementation( isRequestOptions, + httpClient, mutator, ); const mutationOptionsFn = `export const ${mutationOptionsFnName} = { const imports = generateVerbImports(verbOptions); const functionImplementation = generateQueryRequestFunction( verbOptions, options, isVue(outputClient), - output, ); const { implementation: hookImplementation, mutators } = await generateQueryHook(verbOptions, options, outputClient); diff --git a/yarn.lock b/yarn.lock index 9a10671af..1cd3db995 100644 --- a/yarn.lock +++ b/yarn.lock @@ -969,6 +969,7 @@ __metadata: resolution: "@orval/query@workspace:packages/query" dependencies: "@orval/core": "npm:6.31.0" + "@orval/fetch": "npm:6.31.0" "@types/lodash.omitby": "npm:^4.6.7" lodash.omitby: "npm:^4.6.0" vitest: "npm:^0.34.6"