Skip to content

Commit

Permalink
feat(query): query clients can use fetch as an http client (#1514)
Browse files Browse the repository at this point in the history
* feat: use `fetch` client in http client generater

* feat: use `fetch` in quey

* feat: use `fetch` in mutation

* chore: extract the entire function that generates the query to the client

* feat: signal can now be used in custom mutators

* feat: add `@orval/fetch` to `@orval/query` package deps
  • Loading branch information
soartec-lab authored Jul 14, 2024
1 parent b3d42c3 commit 1e20e51
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 187 deletions.
1 change: 1 addition & 0 deletions packages/query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"dependencies": {
"@orval/core": "6.31.0",
"@orval/fetch": "6.31.0",
"lodash.omitby": "^4.6.0"
},
"devDependencies": {
Expand Down
227 changes: 188 additions & 39 deletions packages/query/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
{
Expand All @@ -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<ReturnType<typeof ${mutator.name}>>,`
: ''
}${
!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<typeof ${mutator.name}>,`
: ''
}${!isBodyVerb && hasSignal ? 'signal?: AbortSignal\n' : ''}) => {
${isVue ? vueUnRefParams(props) : ''}
${bodyForm}
return ${mutator.name}<${response.definition.success || 'unknown'}>(
${mutatorConfig},
${requestOptions});
}
`;
}

const isSyntheticDefaultImportsAllowed = isSyntheticDefaultImportsAllow(
context.output.tsconfig,
);
Expand Down Expand Up @@ -94,7 +195,7 @@ export const generateQueryHttpRequestFunction = (

const queryProps = toObjectString(props, 'implementation');

return `export const ${operationName} = (\n ${queryProps} ${optionsArgs} ): Promise<AxiosResponse<${
const httpRequestFunctionImplementation = `export const ${operationName} = (\n ${queryProps} ${optionsArgs} ): Promise<AxiosResponse<${
response.definition.success || 'unknown'
}>> => {${bodyForm}
${isVue ? vueUnRefParams(props) : ''}
Expand All @@ -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) {
Expand All @@ -126,27 +248,35 @@ 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) {
if (!hasSignal) {
return 'requestOptions';
}

return 'requestOptions, signal';
return httpClient === OutputHttpClient.AXIOS
? 'requestOptions, signal'
: '{ signal, ...requestOptions }';
}

if (hasSignal) {
Expand All @@ -158,9 +288,11 @@ export const getQueryOptions = ({

export const getHookOptions = ({
isRequestOptions,
httpClient,
mutator,
}: {
isRequestOptions: boolean;
httpClient: OutputHttpClient;
mutator?: GeneratorMutator;
}) => {
if (!isRequestOptions) {
Expand All @@ -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) {
Expand All @@ -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'
: ''
Expand All @@ -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'
: ''
Expand Down
Loading

0 comments on commit 1e20e51

Please sign in to comment.