UNPKG

8.57 kBJavaScriptView Raw
1import { oldVisit } from '@graphql-codegen/plugin-helpers';
2import { isEnumType, isNonNullType, concatAST, Kind } from 'graphql';
3import { BaseSelectionSetProcessor, BaseDocumentsVisitor, getConfigValue, normalizeAvoidOptionals, wrapTypeWithModifiers, PreResolveTypesProcessor, SelectionSetToObject, generateFragmentImportStatement, optimizeOperations } from '@graphql-codegen/visitor-plugin-common';
4import autoBind from 'auto-bind';
5import { TypeScriptOperationVariablesToObject as TypeScriptOperationVariablesToObject$1 } from '@graphql-codegen/typescript';
6
7class TypeScriptOperationVariablesToObject extends TypeScriptOperationVariablesToObject$1 {
8 formatTypeString(fieldType, isNonNullType, _hasDefaultValue) {
9 return fieldType;
10 }
11}
12
13class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor {
14 transformPrimitiveFields(schemaType, fields) {
15 if (fields.length === 0) {
16 return [];
17 }
18 const parentName = (this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '') +
19 this.config.convertName(schemaType.name, {
20 useTypesPrefix: true,
21 });
22 let hasConditionals = false;
23 const conditilnalsList = [];
24 let resString = `Pick<${parentName}, ${fields
25 .map(field => {
26 if (field.isConditional) {
27 hasConditionals = true;
28 conditilnalsList.push(field.fieldName);
29 }
30 return `'${field.fieldName}'`;
31 })
32 .join(' | ')}>`;
33 if (hasConditionals) {
34 const avoidOptional =
35 // TODO: check type and exec only if relevant
36 this.config.avoidOptionals === true ||
37 this.config.avoidOptionals.field ||
38 this.config.avoidOptionals.inputValue ||
39 this.config.avoidOptionals.object;
40 const transform = avoidOptional ? 'MakeMaybe' : 'MakeOptional';
41 resString = `${this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''}${transform}<${resString}, ${conditilnalsList.map(field => `'${field}'`).join(' | ')}>`;
42 }
43 return [resString];
44 }
45 transformTypenameField(type, name) {
46 return [`{ ${name}: ${type} }`];
47 }
48 transformAliasesPrimitiveFields(schemaType, fields) {
49 if (fields.length === 0) {
50 return [];
51 }
52 const parentName = (this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '') +
53 this.config.convertName(schemaType.name, {
54 useTypesPrefix: true,
55 });
56 return [
57 `{ ${fields
58 .map(aliasedField => {
59 const value = aliasedField.fieldName === '__typename'
60 ? `'${schemaType.name}'`
61 : `${parentName}['${aliasedField.fieldName}']`;
62 return `${aliasedField.alias}: ${value}`;
63 })
64 .join(', ')} }`,
65 ];
66 }
67 transformLinkFields(fields) {
68 if (fields.length === 0) {
69 return [];
70 }
71 return [`{ ${fields.map(field => `${field.alias || field.name}: ${field.selectionSet}`).join(', ')} }`];
72 }
73}
74
75class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor {
76 constructor(schema, config, allFragments) {
77 super(config, {
78 arrayInputCoercion: getConfigValue(config.arrayInputCoercion, true),
79 noExport: getConfigValue(config.noExport, false),
80 avoidOptionals: normalizeAvoidOptionals(getConfigValue(config.avoidOptionals, false)),
81 immutableTypes: getConfigValue(config.immutableTypes, false),
82 nonOptionalTypename: getConfigValue(config.nonOptionalTypename, false),
83 preResolveTypes: getConfigValue(config.preResolveTypes, true),
84 mergeFragmentTypes: getConfigValue(config.mergeFragmentTypes, false),
85 }, schema);
86 autoBind(this);
87 const preResolveTypes = getConfigValue(config.preResolveTypes, true);
88 const defaultMaybeValue = 'T | null';
89 const maybeValue = getConfigValue(config.maybeValue, defaultMaybeValue);
90 const wrapOptional = (type) => {
91 if (preResolveTypes === true) {
92 return maybeValue.replace('T', type);
93 }
94 const prefix = this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '';
95 return `${prefix}Maybe<${type}>`;
96 };
97 const wrapArray = (type) => {
98 const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array';
99 return `${listModifier}<${type}>`;
100 };
101 const formatNamedField = (name, type, isConditional = false) => {
102 const optional = isConditional || (!this.config.avoidOptionals.field && !!type && !isNonNullType(type));
103 return (this.config.immutableTypes ? `readonly ${name}` : name) + (optional ? '?' : '');
104 };
105 const processorConfig = {
106 namespacedImportName: this.config.namespacedImportName,
107 convertName: this.convertName.bind(this),
108 enumPrefix: this.config.enumPrefix,
109 scalars: this.scalars,
110 formatNamedField,
111 wrapTypeWithModifiers(baseType, type) {
112 return wrapTypeWithModifiers(baseType, type, { wrapOptional, wrapArray });
113 },
114 avoidOptionals: this.config.avoidOptionals,
115 };
116 const processor = new (preResolveTypes ? PreResolveTypesProcessor : TypeScriptSelectionSetProcessor)(processorConfig);
117 this.setSelectionSetHandler(new SelectionSetToObject(processor, this.scalars, this.schema, this.convertName.bind(this), this.getFragmentSuffix.bind(this), allFragments, this.config));
118 const enumsNames = Object.keys(schema.getTypeMap()).filter(typeName => isEnumType(schema.getType(typeName)));
119 this.setVariablesTransformer(new TypeScriptOperationVariablesToObject(this.scalars, this.convertName.bind(this), this.config.avoidOptionals.object, this.config.immutableTypes, this.config.namespacedImportName, enumsNames, this.config.enumPrefix, this.config.enumValues, this.config.arrayInputCoercion, undefined, 'InputMaybe'));
120 this._declarationBlockConfig = {
121 ignoreExport: this.config.noExport,
122 };
123 }
124 getImports() {
125 return !this.config.globalNamespace &&
126 (this.config.inlineFragmentTypes === 'combine' || this.config.inlineFragmentTypes === 'mask')
127 ? this.config.fragmentImports.map(fragmentImport => generateFragmentImportStatement(fragmentImport, 'type'))
128 : [];
129 }
130 getPunctuation(_declarationKind) {
131 return ';';
132 }
133 applyVariablesWrapper(variablesBlock) {
134 const prefix = this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '';
135 return `${prefix}Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>`;
136 }
137}
138
139const plugin = (schema, rawDocuments, config) => {
140 const documents = config.flattenGeneratedTypes
141 ? optimizeOperations(schema, rawDocuments, {
142 includeFragments: config.flattenGeneratedTypesIncludeFragments,
143 })
144 : rawDocuments;
145 const allAst = concatAST(documents.map(v => v.document));
146 const allFragments = [
147 ...allAst.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION).map(fragmentDef => ({
148 node: fragmentDef,
149 name: fragmentDef.name.value,
150 onType: fragmentDef.typeCondition.name.value,
151 isExternal: false,
152 })),
153 ...(config.externalFragments || []),
154 ];
155 const visitor = new TypeScriptDocumentsVisitor(schema, config, allFragments);
156 const visitorResult = oldVisit(allAst, {
157 leave: visitor,
158 });
159 let content = visitorResult.definitions.join('\n');
160 if (config.addOperationExport) {
161 const exportConsts = [];
162 allAst.definitions.forEach(d => {
163 if ('name' in d) {
164 exportConsts.push(`export declare const ${d.name.value}: import("graphql").DocumentNode;`);
165 }
166 });
167 content = visitorResult.definitions.concat(exportConsts).join('\n');
168 }
169 if (config.globalNamespace) {
170 content = `
171 declare global {
172 ${content}
173 }`;
174 }
175 return {
176 prepend: [...visitor.getImports(), ...visitor.getGlobalDeclarations(visitor.config.noExport)],
177 content,
178 };
179};
180
181export { TypeScriptDocumentsVisitor, plugin };