|
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