1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | const graphql = require('graphql');
|
6 | const visitorPluginCommon = require('@graphql-codegen/visitor-plugin-common');
|
7 | const pluginHelpers = require('@graphql-codegen/plugin-helpers');
|
8 | const pascalCase = require('pascal-case');
|
9 |
|
10 | const handleTypeNameDuplicates = (result, name, prefix = '') => {
|
11 | let typeToUse = name;
|
12 | while (result[prefix + typeToUse]) {
|
13 | typeToUse = `_${typeToUse}`;
|
14 | }
|
15 | return prefix + typeToUse;
|
16 | };
|
17 | function 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 |
|
83 | class 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 |
|
216 | const REACT_APOLLO_PLUGIN_NAME = 'typescript-react-apollo';
|
217 | const 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 |
|
237 | exports.plugin = plugin;
|
238 |
|