@graphql-codegen/c-sharp-operations
Version:
GraphQL Code Generator plugin for generating ready-to-use Angular Components based on GraphQL operations
256 lines (245 loc) • 10 kB
JavaScript
import { visit, print, Kind, concatAST } from 'graphql';
import { ClientSideBaseVisitor, DocumentMode, indentMultiline } from '@graphql-codegen/visitor-plugin-common';
import autoBind from 'auto-bind';
import { camelCase } from 'camel-case';
import { extname } from 'path';
import gql from 'graphql-tag';
const R_NAME = /name:\s*"([^"]+)"/;
function R_DEF(directive) {
return new RegExp(`\\s+\\@${directive}\\([^)]+\\)`, 'gm');
}
class CSharpOperationsVisitor extends ClientSideBaseVisitor {
constructor(schema, fragments, rawConfig, documents) {
super(schema, fragments, rawConfig, {
namedClient: rawConfig.namedClient,
serviceName: rawConfig.serviceName,
querySuffix: rawConfig.querySuffix,
mutationSuffix: rawConfig.mutationSuffix,
subscriptionSuffix: rawConfig.subscriptionSuffix,
}, documents);
this._operationsToInclude = [];
autoBind(this);
}
_operationHasDirective(operation, directive) {
if (typeof operation === 'string') {
return operation.includes(`${directive}`);
}
let found = false;
visit(operation, {
Directive(node) {
if (node.name.value === directive) {
found = true;
}
},
});
return found;
}
_extractDirective(operation, directive) {
const directives = print(operation).match(R_DEF(directive));
if (directives.length > 1) {
throw new Error(`The ${directive} directive used multiple times in '${operation.name}' operation`);
}
return directives[0];
}
_namedClient(operation) {
let name;
if (this._operationHasDirective(operation, 'namedClient')) {
name = this._extractNamedClient(operation);
}
else if (this.config.namedClient) {
name = this.config.namedClient;
}
return name ? `client = '${name}';` : '';
}
_extractNamedClient(operation) {
const [, name] = this._extractDirective(operation, 'namedClient').match(R_NAME);
return name;
}
_gql(node) {
const fragments = this._transformFragments(node);
let doc = this._prepareDocument(`
${print(node).split('\\').join('\\\\')}
${this._includeFragments(fragments)}`);
doc = doc.replace(/"/g, '""');
if (this.config.documentMode === DocumentMode.string) {
return '@"' + doc + '"';
}
return '@"' + doc + '"';
}
_getDocumentNodeVariable(node, documentVariableName) {
return this.config.documentMode === DocumentMode.external ? `Operations.${node.name.value}` : documentVariableName;
}
_operationSuffix(operationType) {
const defaultSuffix = 'GQL';
switch (operationType) {
case 'Query':
return this.config.querySuffix || defaultSuffix;
case 'Mutation':
return this.config.mutationSuffix || defaultSuffix;
case 'Subscription':
return this.config.subscriptionSuffix || defaultSuffix;
default:
return defaultSuffix;
}
}
buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) {
const serviceName = `${this.convertName(node)}${this._operationSuffix(operationType)}`;
this._operationsToInclude.push({
node,
documentVariableName,
operationType,
operationResultType,
operationVariablesTypes,
serviceName,
});
const content = `
public class ${serviceName}{
public static GraphQLRequest get${serviceName}() {
return new GraphQLRequest {
Query = ${this._getDocumentNodeVariable(node, documentVariableName)},
OperationName = "${this.convertName(node)}"
};
}
${this._namedClient(node)}
}
`;
return content;
}
get sdkClass() {
const actionType = operation => {
switch (operation) {
case 'Mutation':
return 'mutate';
case 'Subscription':
return 'subscribe';
default:
return 'fetch';
}
};
const allPossibleActions = this._operationsToInclude
.map(o => {
const optionalVariables = !o.node.variableDefinitions ||
o.node.variableDefinitions.length === 0 ||
o.node.variableDefinitions.every(v => v.type.kind !== Kind.NON_NULL_TYPE || !!v.defaultValue);
const options = o.operationType === 'Mutation'
? `${o.operationType}OptionsAlone<${o.operationResultType}, ${o.operationVariablesTypes}>`
: `${o.operationType}OptionsAlone<${o.operationVariablesTypes}>`;
const method = `
${camelCase(o.node.name.value)}(variables${optionalVariables ? '?' : ''}: ${o.operationVariablesTypes}, options?: ${options}) {
return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(variables, options)
}`;
let watchMethod;
if (o.operationType === 'Query') {
watchMethod = `
${camelCase(o.node.name.value)}Watch(variables${optionalVariables ? '?' : ''}: ${o.operationVariablesTypes}, options?: WatchQueryOptionsAlone<${o.operationVariablesTypes}>) {
return this.${camelCase(o.serviceName)}.watch(variables, options)
}`;
}
return [method, watchMethod].join('');
})
.map(s => indentMultiline(s, 2));
const injectString = (service) => `private ${camelCase(service)}: ${service}`;
const injections = this._operationsToInclude
.map(op => injectString(op.serviceName))
.map(s => indentMultiline(s, 3))
.join(',\n');
const serviceName = this.config.serviceName || 'GraphQLSDK';
return `
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
interface WatchQueryOptionsAlone<V>
extends Omit<ApolloCore.WatchQueryOptions<V>, 'query' | 'variables'> {}
interface QueryOptionsAlone<V>
extends Omit<ApolloCore.QueryOptions<V>, 'query' | 'variables'> {}
interface MutationOptionsAlone<T, V>
extends Omit<ApolloCore.MutationOptions<T, V>, 'mutation' | 'variables'> {}
interface SubscriptionOptionsAlone<V>
extends Omit<ApolloCore.SubscriptionOptions<V>, 'query' | 'variables'> {}
public class ${serviceName} {
constructor(
${injections}
) {}
${allPossibleActions.join('\n')}
}`;
}
OperationDefinition(node) {
if (!node.name || !node.name.value) {
return null;
}
this._collectedOperations.push(node);
const documentVariableName = this.convertName(node, {
suffix: this.config.documentVariableSuffix,
prefix: this.config.documentVariablePrefix,
useTypesPrefix: false,
});
let documentString = '';
if (this.config.documentMode !== DocumentMode.external) {
const isDocumentNode = this.config.documentMode === DocumentMode.documentNode;
documentString = `${this.config.noExport ? '' : 'public'} static string ${documentVariableName}${isDocumentNode ? ': DocumentNode' : ''} = ${this._gql(node)};`;
}
const operationType = node.operation;
const operationTypeSuffix = this.config.dedupeOperationSuffix && node.name.value.toLowerCase().endsWith(node.operation)
? ''
: !operationType
? ''
: operationType;
const operationResultType = this.convertName(node, {
suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
});
const operationVariablesTypes = this.convertName(node, {
suffix: operationTypeSuffix + 'Variables',
});
const serviceName = `${this.convertName(node)}${this._operationSuffix(operationType)}`;
this._operationsToInclude.push({
node,
documentVariableName,
operationType,
operationResultType,
operationVariablesTypes,
serviceName,
});
const content = `
public class ${serviceName}{
public static GraphQLRequest get${serviceName}() {
return new GraphQLRequest {
Query = ${this._getDocumentNodeVariable(node, documentVariableName)},
OperationName = "${this.convertName(node)}"
};
}
${this._namedClient(node)}
${documentString}
}
`;
return [content].filter(a => a).join('\n');
}
}
const plugin = (schema, documents, config) => {
const openNameSpace = 'namespace GraphQLCodeGen {';
const allAst = concatAST(documents.map(v => v.document));
const allFragments = [
...allAst.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION).map(fragmentDef => ({
node: fragmentDef,
name: fragmentDef.name.value,
onType: fragmentDef.typeCondition.name.value,
isExternal: false,
})),
...(config.externalFragments || []),
];
const visitor = new CSharpOperationsVisitor(schema, allFragments, config, documents);
const visitorResult = visit(allAst, { leave: visitor });
return {
prepend: [],
content: [openNameSpace, visitor.fragments, ...visitorResult.definitions.filter(t => typeof t === 'string'), '}']
.filter(a => a)
.join('\n'),
};
};
const addToSchema = gql `
directive @namedClient(name: String!) on OBJECT | FIELD
`;
const validate = async (schema, documents, config, outputFile) => {
if (extname(outputFile) !== '.cs') {
throw new Error(`Plugin "c-sharp-operations" requires extension to be ".cs"!`);
}
};
export { CSharpOperationsVisitor, addToSchema, plugin, validate };
//# sourceMappingURL=index.esm.js.map