UNPKG

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