UNPKG

11.8 kBJavaScriptView Raw
1import { Kind, isUnionType, isInterfaceType, isObjectType, isNonNullType, isListType, concatAST, visit } from 'graphql';
2import { BaseVisitor, getConfigValue, buildScalars, indent, DeclarationBlock } from '@graphql-codegen/visitor-plugin-common';
3import { getBaseType } from '@graphql-codegen/plugin-helpers';
4import { pascalCase } from 'pascal-case';
5
6const handleTypeNameDuplicates = (result, name, prefix = '') => {
7 let typeToUse = name;
8 while (result[prefix + typeToUse]) {
9 typeToUse = `_${typeToUse}`;
10 }
11 return prefix + typeToUse;
12};
13function selectionSetToTypes(typesPrefix, baseVisitor, schema, parentTypeName, stack, fieldName, selectionSet, preResolveTypes, result = {}) {
14 const parentType = schema.getType(parentTypeName);
15 const typeName = baseVisitor.convertName(fieldName);
16 if (selectionSet && selectionSet.selections && selectionSet.selections.length) {
17 const typeToUse = handleTypeNameDuplicates(result, typeName, typesPrefix);
18 result[typeToUse] = { export: 'type', name: stack };
19 for (const selection of selectionSet.selections) {
20 switch (selection.kind) {
21 case Kind.FIELD: {
22 if (isObjectType(parentType) || isInterfaceType(parentType)) {
23 const selectionName = selection.alias && selection.alias.value ? selection.alias.value : selection.name.value;
24 if (!selectionName.startsWith('__')) {
25 const field = parentType.getFields()[selection.name.value];
26 const baseType = getBaseType(field.type);
27 const wrapWithNonNull = (baseVisitor.config.strict || baseVisitor.config.preResolveTypes) && !isNonNullType(field.type);
28 const isArray = (isNonNullType(field.type) && isListType(field.type.ofType)) || isListType(field.type);
29 const typeRef = `${stack}['${selectionName}']`;
30 const nonNullableInnerType = `${wrapWithNonNull ? `(NonNullable<${typeRef}>)` : typeRef}`;
31 const arrayInnerType = isArray ? `${nonNullableInnerType}[0]` : nonNullableInnerType;
32 const wrapArrayWithNonNull = baseVisitor.config.strict || baseVisitor.config.preResolveTypes;
33 const newStack = isArray && wrapArrayWithNonNull ? `(NonNullable<${arrayInnerType}>)` : arrayInnerType;
34 selectionSetToTypes(typesPrefix, baseVisitor, schema, baseType.name, newStack, selectionName, selection.selectionSet, preResolveTypes, result);
35 }
36 }
37 break;
38 }
39 case Kind.INLINE_FRAGMENT: {
40 const typeCondition = selection.typeCondition.name.value;
41 const fragmentName = baseVisitor.convertName(typeCondition, { suffix: 'InlineFragment' });
42 let inlineFragmentValue;
43 if (isUnionType(parentType) || isInterfaceType(parentType)) {
44 inlineFragmentValue = `DiscriminateUnion<RequireField<${stack}, '__typename'>, { __typename: '${typeCondition}' }>`;
45 }
46 else {
47 let encounteredNestedInlineFragment = false;
48 const subSelections = selection.selectionSet.selections
49 .map(subSelection => {
50 switch (subSelection.kind) {
51 case Kind.FIELD:
52 return `'${subSelection.name.value}'`;
53 case Kind.FRAGMENT_SPREAD:
54 return `keyof ${baseVisitor.convertName(subSelection.name.value, { suffix: 'Fragment' })}`;
55 case Kind.INLINE_FRAGMENT:
56 encounteredNestedInlineFragment = true;
57 return null;
58 }
59 })
60 .filter(a => a);
61 if (encounteredNestedInlineFragment) {
62 throw new Error('Nested inline fragments are not supported the `typescript-compatibility` plugin');
63 }
64 else if (subSelections.length) {
65 inlineFragmentValue = `{ __typename: '${typeCondition}' } & Pick<${stack}, ${subSelections.join(' | ')}>`;
66 }
67 }
68 if (inlineFragmentValue) {
69 selectionSetToTypes(typesPrefix, baseVisitor, schema, typeCondition, `(${inlineFragmentValue})`, fragmentName, selection.selectionSet, preResolveTypes, result);
70 }
71 break;
72 }
73 }
74 }
75 }
76 return result;
77}
78
79class CompatibilityPluginVisitor extends BaseVisitor {
80 constructor(rawConfig, _schema, options) {
81 super(rawConfig, {
82 reactApollo: options.reactApollo,
83 noNamespaces: getConfigValue(rawConfig.noNamespaces, false),
84 preResolveTypes: getConfigValue(rawConfig.preResolveTypes, false),
85 strict: getConfigValue(rawConfig.strict, false),
86 scalars: buildScalars(_schema, rawConfig.scalars),
87 });
88 this._schema = _schema;
89 }
90 getRootType(operationType) {
91 if (operationType === 'query') {
92 return this._schema.getQueryType().name;
93 }
94 else if (operationType === 'mutation') {
95 return this._schema.getMutationType().name;
96 }
97 else if (operationType === 'subscription') {
98 return this._schema.getSubscriptionType().name;
99 }
100 return null;
101 }
102 buildOperationBlock(node) {
103 const typeName = this.getRootType(node.operation);
104 const baseName = this.convertName(node.name.value, { suffix: `${pascalCase(node.operation)}` });
105 const typesPrefix = this.config.noNamespaces ? this.convertName(node.name.value) : '';
106 const selectionSetTypes = {
107 [typesPrefix + this.convertName('Variables')]: {
108 export: 'type',
109 name: this.convertName(node.name.value, { suffix: `${pascalCase(node.operation)}Variables` }),
110 },
111 };
112 selectionSetToTypes(typesPrefix, this, this._schema, typeName, baseName, node.operation, node.selectionSet, this.config.preResolveTypes, selectionSetTypes);
113 return selectionSetTypes;
114 }
115 buildFragmentBlock(node) {
116 const typeName = this._schema.getType(node.typeCondition.name.value).name;
117 const baseName = this.convertName(node.name.value, { suffix: `Fragment` });
118 const typesPrefix = this.config.noNamespaces ? this.convertName(node.name.value) : '';
119 const selectionSetTypes = {};
120 selectionSetToTypes(typesPrefix, this, this._schema, typeName, baseName, 'fragment', node.selectionSet, this.config.preResolveTypes, selectionSetTypes);
121 return selectionSetTypes;
122 }
123 printTypes(selectionSetTypes) {
124 return Object.keys(selectionSetTypes)
125 .filter(typeName => typeName !== selectionSetTypes[typeName].name)
126 .map(typeName => `export ${selectionSetTypes[typeName].export} ${typeName} = ${selectionSetTypes[typeName].name};`)
127 .map(m => (this.config.noNamespaces ? m : indent(m)))
128 .join('\n');
129 }
130 FragmentDefinition(node) {
131 const baseName = node.name.value;
132 const results = [];
133 const convertedName = this.convertName(baseName);
134 const selectionSetTypes = this.buildFragmentBlock(node);
135 const fragmentBlock = this.printTypes(selectionSetTypes);
136 if (!this.config.noNamespaces) {
137 results.push(new DeclarationBlock(this._declarationBlockConfig)
138 .export()
139 .asKind('namespace')
140 .withName(convertedName)
141 .withBlock(fragmentBlock).string);
142 }
143 else {
144 results.push(fragmentBlock);
145 }
146 return results.join('\n');
147 }
148 OperationDefinition(node) {
149 const baseName = node.name.value;
150 const convertedName = this.convertName(baseName);
151 const results = [];
152 const selectionSetTypes = this.buildOperationBlock(node);
153 if (this.config.reactApollo) {
154 const reactApolloConfig = this.config.reactApollo;
155 let hoc = true;
156 let component = true;
157 let hooks = false;
158 if (typeof reactApolloConfig === 'object') {
159 if (reactApolloConfig.withHOC === false) {
160 hoc = false;
161 }
162 if (reactApolloConfig.withComponent === false) {
163 component = false;
164 }
165 if (reactApolloConfig.withHooks) {
166 hooks = true;
167 }
168 }
169 const prefix = this.config.noNamespaces ? convertedName : '';
170 selectionSetTypes[prefix + 'Document'] = {
171 export: 'const',
172 name: this.convertName(baseName, { suffix: 'Document' }),
173 };
174 if (hoc) {
175 selectionSetTypes[prefix + 'Props'] = {
176 export: 'type',
177 name: this.convertName(baseName, { suffix: 'Props' }),
178 };
179 selectionSetTypes[prefix + 'HOC'] = {
180 export: 'const',
181 name: `with${convertedName}`,
182 };
183 }
184 if (component) {
185 selectionSetTypes[prefix + 'Component'] = {
186 export: 'const',
187 name: this.convertName(baseName, { suffix: 'Component' }),
188 };
189 }
190 if (hooks) {
191 selectionSetTypes['use' + prefix] = {
192 export: 'const',
193 name: 'use' + this.convertName(baseName, { suffix: pascalCase(node.operation) }),
194 };
195 }
196 }
197 const operationsBlock = this.printTypes(selectionSetTypes);
198 if (!this.config.noNamespaces) {
199 results.push(new DeclarationBlock(this._declarationBlockConfig)
200 .export()
201 .asKind('namespace')
202 .withName(convertedName)
203 .withBlock(operationsBlock).string);
204 }
205 else {
206 results.push(operationsBlock);
207 }
208 return results.join('\n');
209 }
210}
211
212const REACT_APOLLO_PLUGIN_NAME = 'typescript-react-apollo';
213const plugin = async (schema, documents, config, additionalData) => {
214 const allAst = concatAST(documents.map(v => v.document));
215 const reactApollo = ((additionalData || {}).allPlugins || []).find(p => Object.keys(p)[0] === REACT_APOLLO_PLUGIN_NAME);
216 const visitor = new CompatibilityPluginVisitor(config, schema, {
217 reactApollo: reactApollo
218 ? {
219 ...(config || {}),
220 ...reactApollo[REACT_APOLLO_PLUGIN_NAME],
221 }
222 : null,
223 });
224 const visitorResult = visit(allAst, {
225 leave: visitor,
226 });
227 const discriminateUnion = `type DiscriminateUnion<T, U> = T extends U ? T : never;\n`;
228 const requireField = `type RequireField<T, TNames extends string> = T & { [P in TNames]: (T & { [name: string]: never })[P] };\n`;
229 const result = visitorResult.definitions.filter(a => a && typeof a === 'string').join('\n');
230 return result.includes('DiscriminateUnion') ? [discriminateUnion, requireField, result].join('\n') : result;
231};
232
233export { plugin };
234//# sourceMappingURL=index.esm.js.map