| 
1 | 1 | import fs from 'fs';  | 
 | 2 | +import {  | 
 | 3 | +  dedent,  | 
 | 4 | +  isComposeOutputType,  | 
 | 5 | +  isFunction,  | 
 | 6 | +  isSomeOutputTypeDefinitionString,  | 
 | 7 | +  isWrappedTypeNameString,  | 
 | 8 | +  Resolver,  | 
 | 9 | +} from 'graphql-compose';  | 
2 | 10 | import { join, resolve, dirname, basename } from 'path';  | 
3 | 11 | import { FieldConfig, NamespaceConfig } from './typeDefs';  | 
4 | 12 | 
 
  | 
@@ -31,6 +39,12 @@ export interface AstFileNode extends AstBaseNode {  | 
31 | 39 |   code: {  | 
32 | 40 |     default?: FieldConfig | NamespaceConfig;  | 
33 | 41 |   };  | 
 | 42 | +  /**  | 
 | 43 | +   * This FieldConfig loaded from `code` and validated.  | 
 | 44 | +   * This property is used by ast transformers and stores the last version of modified config.  | 
 | 45 | +   * This value will be used by astToSchema method.  | 
 | 46 | +   */  | 
 | 47 | +  fieldConfig: FieldConfig;  | 
34 | 48 | }  | 
35 | 49 | 
 
  | 
36 | 50 | export type RootTypeNames = 'query' | 'mutation' | 'subscription';  | 
@@ -195,11 +209,16 @@ export function getAstForFile(  | 
195 | 209 |   if (absPath !== m.filename && checkInclusion(absPath, 'file', filename, options)) {  | 
196 | 210 |     // module name shouldn't include file extension  | 
197 | 211 |     const moduleName = filename.substring(0, filename.lastIndexOf('.'));  | 
 | 212 | +    // namespace configs may not have `type` property  | 
 | 213 | +    const checkType = moduleName !== 'index';  | 
 | 214 | +    const code = m.require(absPath);  | 
 | 215 | +    const fieldConfig = prepareFieldConfig(code, absPath, checkType);  | 
198 | 216 |     return {  | 
199 | 217 |       kind: 'file',  | 
200 | 218 |       name: moduleName,  | 
201 | 219 |       absPath,  | 
202 |  | -      code: m.require(absPath),  | 
 | 220 | +      code,  | 
 | 221 | +      fieldConfig,  | 
203 | 222 |     };  | 
204 | 223 |   }  | 
205 | 224 | }  | 
@@ -258,3 +277,62 @@ function checkInclusion(  | 
258 | 277 | 
 
  | 
259 | 278 |   return true;  | 
260 | 279 | }  | 
 | 280 | + | 
 | 281 | +function prepareFieldConfig(code: any, absPath: string, checkType = true): FieldConfig {  | 
 | 282 | +  const _fc = code?.default;  | 
 | 283 | +  if (!_fc || typeof _fc !== 'object') {  | 
 | 284 | +    throw new Error(dedent`  | 
 | 285 | +      GraphQL entrypoint MUST return FieldConfig as default export in '${absPath}'.   | 
 | 286 | +      Eg:  | 
 | 287 | +        export default {  | 
 | 288 | +          type: 'String',  | 
 | 289 | +          resolve: () => Date.now(),  | 
 | 290 | +        };  | 
 | 291 | +    `);  | 
 | 292 | +  }  | 
 | 293 | + | 
 | 294 | +  let fc: FieldConfig;  | 
 | 295 | +  if (code.default instanceof Resolver) {  | 
 | 296 | +    fc = (code.default as Resolver).getFieldConfig() as any;  | 
 | 297 | +  } else {  | 
 | 298 | +    // recreate object for immutability purposes (do not change object in module definition)  | 
 | 299 | +    // NB. I don't know should we here recreate (args, extensions) but let's keep them as is for now.  | 
 | 300 | +    fc = { ...code.default };  | 
 | 301 | +  }  | 
 | 302 | + | 
 | 303 | +  if (checkType) {  | 
 | 304 | +    if (!fc.type || !isSomeOutputTypeDefinition(fc.type)) {  | 
 | 305 | +      throw new Error(dedent`  | 
 | 306 | +      Module MUST return FieldConfig with correct 'type: xxx' property in '${absPath}'.   | 
 | 307 | +      Eg:  | 
 | 308 | +        export default {  | 
 | 309 | +          type: 'String'  | 
 | 310 | +        };  | 
 | 311 | +    `);  | 
 | 312 | +    }  | 
 | 313 | +  }  | 
 | 314 | + | 
 | 315 | +  if (fc.resolve && typeof fc.resolve !== 'function') {  | 
 | 316 | +    throw new Error(  | 
 | 317 | +      `Cannot load entrypoint config from ${absPath}. 'resolve' property must be a function or undefined.`  | 
 | 318 | +    );  | 
 | 319 | +  }  | 
 | 320 | + | 
 | 321 | +  return fc;  | 
 | 322 | +}  | 
 | 323 | + | 
 | 324 | +function isSomeOutputTypeDefinition(type: any): boolean {  | 
 | 325 | +  if (typeof type === 'string') {  | 
 | 326 | +    // type: 'String'  | 
 | 327 | +    return isSomeOutputTypeDefinitionString(type) || isWrappedTypeNameString(type);  | 
 | 328 | +  } else if (Array.isArray(type)) {  | 
 | 329 | +    // type: ['String']  | 
 | 330 | +    return isSomeOutputTypeDefinition(type[0]);  | 
 | 331 | +  } else if (isFunction(type)) {  | 
 | 332 | +    // pass thunked type without internal checks  | 
 | 333 | +    return true;  | 
 | 334 | +  } else {  | 
 | 335 | +    // type: 'type User { name: String }'  | 
 | 336 | +    return isComposeOutputType(type);  | 
 | 337 | +  }  | 
 | 338 | +}  | 
0 commit comments