@graphql-codegen/typescript-vue-apollo
Version:
GraphQL Code Generator plugin for generating ready-to-use Vue-Apollo composition functions based on GraphQL operations
200 lines (199 loc) • 11.6 kB
JavaScript
import autoBind from 'auto-bind';
import { pascalCase, titleCase } from 'change-case-all';
import { ClientSideBaseVisitor, DocumentMode, getConfigValue, } from '@graphql-codegen/visitor-plugin-common';
function insertIf(condition, ...elements) {
return condition ? elements : [];
}
export class VueApolloVisitor extends ClientSideBaseVisitor {
constructor(schema, fragments, rawConfig, documents) {
super(schema, fragments, rawConfig, {
withCompositionFunctions: getConfigValue(rawConfig.withCompositionFunctions, true),
vueApolloComposableImportFrom: getConfigValue(rawConfig.vueApolloComposableImportFrom, '@vue/apollo-composable'),
vueCompositionApiImportFrom: getConfigValue(rawConfig.vueCompositionApiImportFrom, '@vue/composition-api'),
addDocBlocks: getConfigValue(rawConfig.addDocBlocks, true),
clientId: getConfigValue(rawConfig.clientId, null),
});
this.imports = new Set();
this.externalImportPrefix = this.config.importOperationTypesFrom
? `${this.config.importOperationTypesFrom}.`
: '';
this._documents = documents;
autoBind(this);
}
get vueApolloComposableImport() {
return `import * as VueApolloComposable from '${this.config.vueApolloComposableImportFrom}';`;
}
get vueCompositionApiImport() {
if (this.config.useTypeImports) {
return `import type * as VueCompositionApi from '${this.config.vueCompositionApiImportFrom}';`;
}
return `import * as VueCompositionApi from '${this.config.vueCompositionApiImportFrom}';`;
}
get reactiveFunctionType() {
return 'export type ReactiveFunction<TParam> = () => TParam;';
}
getDocumentNodeVariable(node, documentVariableName) {
return this.config.documentMode === DocumentMode.external
? `Operations.${node.name.value}`
: documentVariableName;
}
getImports() {
const baseImports = super.getImports();
const hasOperations = this._collectedOperations.length > 0;
if (!hasOperations) {
return baseImports;
}
return [...baseImports, ...Array.from(this.imports)];
}
buildCompositionFunctionsJSDoc(node, operationName, operationType) {
var _a, _b;
const operationHasVariables = ((_a = node.variableDefinitions) === null || _a === void 0 ? void 0 : _a.length) > 0;
const exampleVariablesString = (_b = node.variableDefinitions) === null || _b === void 0 ? void 0 : _b.reduce((accumulator, currentDefinition) => {
const name = currentDefinition.variable.name.value;
return `${accumulator}\n * ${operationType === 'Mutation' ? ' ' : ''}${name}: // value for '${name}'`;
}, '');
const exampleArguments = operationHasVariables
? operationType === 'Mutation'
? `{
* variables: {${exampleVariablesString}
* },
* }`
: `{${exampleVariablesString}
* }`
: '';
const queryDescription = `
* To run a query within a Vue component, call \`use${operationName}\` and pass it any options that fit your needs.
* When your component renders, \`use${operationName}\` returns an object from Apollo Client that contains result, loading and error properties
* you can use to render your UI.`;
const queryExample = `
* const { result, loading, error } = use${operationName}(${exampleArguments});`;
const mutationDescription = `
* To run a mutation, you first call \`use${operationName}\` within a Vue component and pass it any options that fit your needs.
* When your component renders, \`use${operationName}\` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return`;
const mutationExample = `
* const { mutate, loading, error, onDone } = use${operationName}(${exampleArguments});`;
return `
/**
* __use${operationName}__
*${operationType === 'Mutation' ? mutationDescription : queryDescription}
*${operationHasVariables && operationType !== 'Mutation'
? `
* @param variables that will be passed into the ${operationType.toLowerCase()}`
: ''}
* @param options that will be passed into the ${operationType.toLowerCase()}, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/${operationType === 'Mutation'
? 'mutation'
: operationType === 'Subscription'
? 'subscription'
: 'query'}.html#options;
*
* @example${operationType === 'Mutation' ? mutationExample : queryExample}
*/`;
}
getCompositionFunctionSuffix(name, operationType) {
if (!this.config.dedupeOperationSuffix) {
return this.config.omitOperationSuffix ? '' : pascalCase(operationType);
}
if (name.includes('Query') || name.includes('Mutation') || name.includes('Subscription')) {
return '';
}
return pascalCase(operationType);
}
buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) {
var _a, _b, _c;
operationResultType = this.externalImportPrefix + operationResultType;
operationVariablesTypes = this.externalImportPrefix + operationVariablesTypes;
if (!this.config.withCompositionFunctions) {
// todo - throw human readable error
return '';
}
if (!((_a = node.name) === null || _a === void 0 ? void 0 : _a.value)) {
// todo - throw human readable error
return '';
}
const suffix = this.getCompositionFunctionSuffix(node.name.value, operationType);
const operationName = this.convertName(node.name.value, {
suffix,
useTypesPrefix: false,
});
const operationHasVariables = ((_b = node.variableDefinitions) === null || _b === void 0 ? void 0 : _b.length) > 0;
const operationHasNonNullableVariable = !!((_c = node.variableDefinitions) === null || _c === void 0 ? void 0 : _c.some(({ type }) => type.kind === 'NonNullType'));
this.imports.add(this.vueApolloComposableImport);
this.imports.add(this.vueCompositionApiImport);
// hacky: technically not an import, but a typescript type that is required in the generated code
this.imports.add(this.reactiveFunctionType);
const documentNodeVariable = this.getDocumentNodeVariable(node, documentVariableName); // i.e. TestDocument
const compositionFunctionResultType = this.buildCompositionFunctionReturnType({
operationName,
operationType,
operationResultType,
operationVariablesTypes,
});
const compositionFunctions = [
this.buildCompositionFunction({
operationName,
operationType,
operationResultType,
operationVariablesTypes,
operationHasNonNullableVariable,
operationHasVariables,
documentNodeVariable,
}),
];
if (operationType === 'Query') {
const lazyOperationName = this.convertName(node.name.value, {
suffix: titleCase('LazyQuery'),
useTypesPrefix: false,
});
compositionFunctions.push(this.buildCompositionFunction({
operationName: lazyOperationName,
operationType: 'LazyQuery',
operationResultType,
operationVariablesTypes,
operationHasNonNullableVariable,
operationHasVariables,
documentNodeVariable,
}));
}
return [
...insertIf(this.config.addDocBlocks, [
this.buildCompositionFunctionsJSDoc(node, operationName, operationType),
]),
compositionFunctions.join('\n'),
compositionFunctionResultType,
].join('\n');
}
buildCompositionFunction({ operationName, operationType, operationResultType, operationVariablesTypes, operationHasNonNullableVariable, operationHasVariables, documentNodeVariable, }) {
const operationVariablesOptionalModifier = operationType === 'LazyQuery' && operationHasNonNullableVariable ? '?' : '';
const variables = operationHasVariables
? `variables${operationVariablesOptionalModifier}: ${operationVariablesTypes} | VueCompositionApi.Ref<${operationVariablesTypes}> | ReactiveFunction<${operationVariablesTypes}>${operationHasNonNullableVariable ? '' : ' = {}'}, `
: '';
const options = `${this.config.clientId ? `{ clientId: '${this.config.clientId}', ...options}` : 'options'}`;
switch (operationType) {
case 'Query': {
return `export function use${operationName}(${variables}options: VueApolloComposable.UseQueryOptions<${operationResultType}, ${operationVariablesTypes}> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<${operationResultType}, ${operationVariablesTypes}>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<${operationResultType}, ${operationVariablesTypes}>> = {}) {
return VueApolloComposable.useQuery<${operationResultType}, ${operationVariablesTypes}>(${documentNodeVariable}, ${operationHasVariables ? 'variables' : '{}'}, ${options});
}`;
}
case 'LazyQuery': {
return `export function use${operationName}(${variables}options: VueApolloComposable.UseQueryOptions<${operationResultType}, ${operationVariablesTypes}> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<${operationResultType}, ${operationVariablesTypes}>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<${operationResultType}, ${operationVariablesTypes}>> = {}) {
return VueApolloComposable.useLazyQuery<${operationResultType}, ${operationVariablesTypes}>(${documentNodeVariable}, ${operationHasVariables ? 'variables' : '{}'}, ${options});
}`;
}
case 'Mutation': {
return `export function use${operationName}(options: VueApolloComposable.UseMutationOptions<${operationResultType}, ${operationVariablesTypes}> | ReactiveFunction<VueApolloComposable.UseMutationOptions<${operationResultType}, ${operationVariablesTypes}>> = {}) {
return VueApolloComposable.useMutation<${operationResultType}, ${operationVariablesTypes}>(${documentNodeVariable}, ${options});
}`;
}
case 'Subscription': {
return `export function use${operationName}(${variables}options: VueApolloComposable.UseSubscriptionOptions<${operationResultType}, ${operationVariablesTypes}> | VueCompositionApi.Ref<VueApolloComposable.UseSubscriptionOptions<${operationResultType}, ${operationVariablesTypes}>> | ReactiveFunction<VueApolloComposable.UseSubscriptionOptions<${operationResultType}, ${operationVariablesTypes}>> = {}) {
return VueApolloComposable.useSubscription<${operationResultType}, ${operationVariablesTypes}>(${documentNodeVariable}, ${operationHasVariables ? 'variables' : '{}'}, ${options});
}`;
}
}
}
buildCompositionFunctionReturnType({ operationName, operationType, operationResultType, operationVariablesTypes, }) {
return `export type ${operationName}CompositionFunctionResult = VueApolloComposable.Use${operationType}Return<${operationResultType}, ${operationVariablesTypes}>;`;
}
}