UNPKG

12.7 kBJavaScriptView Raw
1import { printSchemaWithDirectives } from '@graphql-tools/utils';
2import { BaseResolversVisitor, getConfigValue, parseMapper } from '@graphql-codegen/visitor-plugin-common';
3import { addFederationReferencesToSchema } from '@graphql-codegen/plugin-helpers';
4import { printSchema, parse, visit } from 'graphql';
5import autoBind from 'auto-bind';
6import { TypeScriptOperationVariablesToObject } from '@graphql-codegen/typescript';
7
8const ENUM_RESOLVERS_SIGNATURE = 'export type EnumResolverSignature<T, AllowedValues = any> = { [key in keyof T]?: AllowedValues };';
9class TypeScriptResolversVisitor extends BaseResolversVisitor {
10 constructor(pluginConfig, schema) {
11 super(pluginConfig, {
12 avoidOptionals: getConfigValue(pluginConfig.avoidOptionals, false),
13 useIndexSignature: getConfigValue(pluginConfig.useIndexSignature, false),
14 wrapFieldDefinitions: getConfigValue(pluginConfig.wrapFieldDefinitions, false),
15 allowParentTypeOverride: getConfigValue(pluginConfig.allowParentTypeOverride, false),
16 optionalInfoArgument: getConfigValue(pluginConfig.optionalInfoArgument, false),
17 }, schema);
18 autoBind(this);
19 this.setVariablesTransformer(new TypeScriptOperationVariablesToObject(this.scalars, this.convertName, this.config.avoidOptionals, this.config.immutableTypes, this.config.namespacedImportName, [], this.config.enumPrefix, this.config.enumValues));
20 if (this.config.useIndexSignature) {
21 this._declarationBlockConfig = {
22 blockTransformer(block) {
23 return `ResolversObject<${block}>`;
24 },
25 };
26 }
27 }
28 transformParentGenericType(parentType) {
29 if (this.config.allowParentTypeOverride) {
30 return `ParentType = ${parentType}`;
31 }
32 return `ParentType extends ${parentType} = ${parentType}`;
33 }
34 formatRootResolver(schemaTypeName, resolverType, declarationKind) {
35 return `${schemaTypeName}${this.config.avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`;
36 }
37 clearOptional(str) {
38 if (str.startsWith('Maybe')) {
39 return str.replace(/Maybe<(.*?)>$/, '$1');
40 }
41 return str;
42 }
43 ListType(node) {
44 return `Maybe<${super.ListType(node)}>`;
45 }
46 wrapWithListType(str) {
47 return `${this.config.immutableTypes ? 'ReadonlyArray' : 'Array'}<${str}>`;
48 }
49 getParentTypeForSignature(node) {
50 if (this._federation.isResolveReferenceField(node) && this.config.wrapFieldDefinitions) {
51 return 'UnwrappedObject<ParentType>';
52 }
53 return 'ParentType';
54 }
55 NamedType(node) {
56 return `Maybe<${super.NamedType(node)}>`;
57 }
58 NonNullType(node) {
59 const baseValue = super.NonNullType(node);
60 return this.clearOptional(baseValue);
61 }
62 getPunctuation(declarationKind) {
63 return ';';
64 }
65 buildEnumResolverContentBlock(node, mappedEnumType) {
66 const valuesMap = `{ ${(node.values || [])
67 .map(v => `${v.name}${this.config.avoidOptionals ? '' : '?'}: any`)
68 .join(', ')} }`;
69 this._globalDeclarations.add(ENUM_RESOLVERS_SIGNATURE);
70 return `EnumResolverSignature<${valuesMap}, ${mappedEnumType}>`;
71 }
72 buildEnumResolversExplicitMappedValues(node, valuesMapping) {
73 return `{ ${(node.values || [])
74 .map(v => {
75 const valueName = v.name;
76 const mappedValue = valuesMapping[valueName];
77 return `${valueName}: ${typeof mappedValue === 'number' ? mappedValue : `'${mappedValue}'`}`;
78 })
79 .join(', ')} }`;
80 }
81}
82
83const plugin = (schema, documents, config) => {
84 const imports = [];
85 if (!config.customResolveInfo) {
86 imports.push('GraphQLResolveInfo');
87 }
88 const showUnusedMappers = typeof config.showUnusedMappers === 'boolean' ? config.showUnusedMappers : true;
89 const noSchemaStitching = typeof config.noSchemaStitching === 'boolean' ? config.noSchemaStitching : false;
90 if (config.noSchemaStitching === false) {
91 // eslint-disable-next-line no-console
92 console.warn(`The default behavior of 'noSchemaStitching' will be reversed in the next major release. Support for Schema Stitching will be disabled by default.`);
93 }
94 const indexSignature = config.useIndexSignature
95 ? [
96 'export type WithIndex<TObject> = TObject & Record<string, any>;',
97 'export type ResolversObject<TObject> = WithIndex<TObject>;',
98 ].join('\n')
99 : '';
100 const transformedSchema = config.federation ? addFederationReferencesToSchema(schema) : schema;
101 const visitor = new TypeScriptResolversVisitor(config, transformedSchema);
102 const namespacedImportPrefix = visitor.config.namespacedImportName ? `${visitor.config.namespacedImportName}.` : '';
103 const printedSchema = config.federation
104 ? printSchemaWithDirectives(transformedSchema)
105 : printSchema(transformedSchema);
106 const astNode = parse(printedSchema);
107 // runs visitor
108 const visitorResult = visit(astNode, { leave: visitor });
109 const optionalSignForInfoArg = visitor.config.optionalInfoArgument ? '?' : '';
110 const prepend = [];
111 const defsToInclude = [];
112 const legacyStitchingResolverType = `
113export type LegacyStitchingResolver<TResult, TParent, TContext, TArgs> = {
114 fragment: string;
115 resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
116};`;
117 const newStitchingResolverType = `
118export type NewStitchingResolver<TResult, TParent, TContext, TArgs> = {
119 selectionSet: string;
120 resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
121};`;
122 const stitchingResolverType = `export type StitchingResolver<TResult, TParent, TContext, TArgs> = LegacyStitchingResolver<TResult, TParent, TContext, TArgs> | NewStitchingResolver<TResult, TParent, TContext, TArgs>;`;
123 const resolverType = `export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> =`;
124 const resolverFnUsage = `ResolverFn<TResult, TParent, TContext, TArgs>`;
125 const stitchingResolverUsage = `StitchingResolver<TResult, TParent, TContext, TArgs>`;
126 if (visitor.hasFederation()) {
127 if (visitor.config.wrapFieldDefinitions) {
128 defsToInclude.push(`export type UnwrappedObject<T> = {
129 [P in keyof T]: T[P] extends infer R | Promise<infer R> | (() => infer R2 | Promise<infer R2>)
130 ? R & R2 : T[P]
131 };`);
132 }
133 defsToInclude.push(`export type ReferenceResolver<TResult, TReference, TContext> = (
134 reference: TReference,
135 context: TContext,
136 info${optionalSignForInfoArg}: GraphQLResolveInfo
137 ) => Promise<TResult> | TResult;`);
138 defsToInclude.push(`
139 type ScalarCheck<T, S> = S extends true ? T : NullableCheck<T, S>;
140 type NullableCheck<T, S> = Maybe<T> extends T ? Maybe<ListCheck<NonNullable<T>, S>> : ListCheck<T, S>;
141 type ListCheck<T, S> = T extends (infer U)[] ? NullableCheck<U, S>[] : GraphQLRecursivePick<T, S>;
142 export type GraphQLRecursivePick<T, S> = { [K in keyof T & keyof S]: ScalarCheck<T[K], S[K]> };
143 `);
144 }
145 if (noSchemaStitching) {
146 // Resolver = ResolverFn;
147 defsToInclude.push(`${resolverType} ${resolverFnUsage};`);
148 }
149 else {
150 // StitchingResolver
151 // Resolver =
152 // | ResolverFn
153 // | StitchingResolver;
154 defsToInclude.push([
155 legacyStitchingResolverType,
156 newStitchingResolverType,
157 stitchingResolverType,
158 resolverType,
159 ` | ${resolverFnUsage}`,
160 ` | ${stitchingResolverUsage};`,
161 ].join('\n'));
162 }
163 if (config.customResolverFn) {
164 const parsedMapper = parseMapper(config.customResolverFn);
165 if (parsedMapper.isExternal) {
166 if (parsedMapper.default) {
167 prepend.push(`import ResolverFn from '${parsedMapper.source}';`);
168 }
169 else {
170 prepend.push(`import { ${parsedMapper.import} ${parsedMapper.import !== 'ResolverFn' ? 'as ResolverFn ' : ''}} from '${parsedMapper.source}';`);
171 }
172 prepend.push(`export { ResolverFn };`);
173 }
174 else {
175 prepend.push(`export type ResolverFn<TResult, TParent, TContext, TArgs> = ${parsedMapper.type}`);
176 }
177 }
178 else {
179 const defaultResolverFn = `
180export type ResolverFn<TResult, TParent, TContext, TArgs> = (
181 parent: TParent,
182 args: TArgs,
183 context: TContext,
184 info${optionalSignForInfoArg}: GraphQLResolveInfo
185) => Promise<TResult> | TResult;`;
186 defsToInclude.push(defaultResolverFn);
187 }
188 const header = `${indexSignature}
189
190${visitor.getResolverTypeWrapperSignature()}
191
192${defsToInclude.join('\n')}
193
194export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
195 parent: TParent,
196 args: TArgs,
197 context: TContext,
198 info${optionalSignForInfoArg}: GraphQLResolveInfo
199) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>;
200
201export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
202 parent: TParent,
203 args: TArgs,
204 context: TContext,
205 info${optionalSignForInfoArg}: GraphQLResolveInfo
206) => TResult | Promise<TResult>;
207
208export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
209 subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
210 resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>;
211}
212
213export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
214 subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
215 resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
216}
217
218export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> =
219 | SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
220 | SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
221
222export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> =
223 | ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
224 | SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;
225
226export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
227 parent: TParent,
228 context: TContext,
229 info${optionalSignForInfoArg}: GraphQLResolveInfo
230) => ${namespacedImportPrefix}Maybe<TTypes> | Promise<${namespacedImportPrefix}Maybe<TTypes>>;
231
232export type IsTypeOfResolverFn<T = {}> = (obj: T, info${optionalSignForInfoArg}: GraphQLResolveInfo) => boolean | Promise<boolean>;
233
234export type NextResolverFn<T> = () => Promise<T>;
235
236export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = (
237 next: NextResolverFn<TResult>,
238 parent: TParent,
239 args: TArgs,
240 context: TContext,
241 info${optionalSignForInfoArg}: GraphQLResolveInfo
242) => TResult | Promise<TResult>;
243`;
244 const resolversTypeMapping = visitor.buildResolversTypes();
245 const resolversParentTypeMapping = visitor.buildResolversParentTypes();
246 const { getRootResolver, getAllDirectiveResolvers, mappersImports, unusedMappers, hasScalars } = visitor;
247 if (hasScalars()) {
248 imports.push('GraphQLScalarType', 'GraphQLScalarTypeConfig');
249 }
250 if (showUnusedMappers && unusedMappers.length) {
251 // eslint-disable-next-line no-console
252 console.warn(`Unused mappers: ${unusedMappers.join(',')}`);
253 }
254 if (imports.length) {
255 prepend.push(`import { ${imports.join(', ')} } from 'graphql';`);
256 }
257 if (config.customResolveInfo) {
258 const parsedMapper = parseMapper(config.customResolveInfo);
259 if (parsedMapper.isExternal) {
260 if (parsedMapper.default) {
261 prepend.push(`import GraphQLResolveInfo from '${parsedMapper.source}'`);
262 }
263 prepend.push(`import { ${parsedMapper.import} ${parsedMapper.import !== 'GraphQLResolveInfo' ? 'as GraphQLResolveInfo' : ''} } from '${parsedMapper.source}';`);
264 }
265 else {
266 prepend.push(`type GraphQLResolveInfo = ${parsedMapper.type}`);
267 }
268 }
269 prepend.push(...mappersImports, ...visitor.globalDeclarations);
270 return {
271 prepend,
272 content: [
273 header,
274 resolversTypeMapping,
275 resolversParentTypeMapping,
276 ...visitorResult.definitions.filter(d => typeof d === 'string'),
277 getRootResolver(),
278 getAllDirectiveResolvers(),
279 ].join('\n'),
280 };
281};
282
283export { TypeScriptResolversVisitor, plugin };
284//# sourceMappingURL=index.esm.js.map