megaera
Version:
GraphQL to TypeScript Generator
118 lines (117 loc) • 4.14 kB
JavaScript
import { isNonNullType, } from 'graphql/type/definition.js';
import { typeFromAST, TypeInfo, visitWithTypeInfo, } from 'graphql/utilities/index.js';
import { parse, print, visit } from 'graphql/language/index.js';
import { GraphQLError } from 'graphql/error/index.js';
import { firstLetterUpper } from './utils.js';
export function traverse(schema, source) {
const ast = parse(source);
const typeInfo = new TypeInfo(schema);
const content = {
operations: [],
fragments: new Map(),
};
const stack = [];
const visitor = visitWithTypeInfo(typeInfo, {
OperationDefinition: {
enter: function (node) {
if (node.name === undefined) {
throw new GraphQLError(firstLetterUpper(node.operation) + ' name is required', node, source);
}
checkUnique(node.name.value, content);
const variables = [];
for (const v of node.variableDefinitions ?? []) {
const type = typeFromAST(schema, v.type);
variables.push({
name: v.variable.name.value,
type: type,
required: v.defaultValue === undefined && isNonNullType(type),
});
}
const s = {
name: node.name.value,
type: typeInfo.getType() ?? undefined,
fields: [],
inlineFragments: [],
variables: variables,
source: print(node),
};
stack.push(s);
content.operations.push(s);
},
leave() {
stack.pop();
},
},
FragmentDefinition: {
enter(node) {
checkUnique(node.name.value, content);
const s = {
name: node.name.value,
type: typeInfo.getType() ?? undefined,
fields: [],
inlineFragments: [],
source: print(node),
};
stack.push(s);
content.fragments.set(s.name, s);
},
leave() {
stack.pop();
},
},
Field: {
enter(node) {
const s = {
name: node.alias?.value ?? node.name.value,
type: typeInfo.getType() ?? undefined,
fields: [],
inlineFragments: [],
};
stack.at(-1)?.fields.push(s);
stack.push(s);
},
leave() {
stack.pop();
},
},
FragmentSpread: {
enter(node) {
stack.at(-1)?.fields.push({
name: node.name.value,
type: typeInfo.getType() ?? undefined,
isFragment: true,
fields: [],
inlineFragments: [],
});
},
},
InlineFragment: {
enter(node) {
if (!node.typeCondition) {
throw new GraphQLError('Inline fragment must have type condition.', node, source);
}
const s = {
name: node.typeCondition.name.value,
type: typeInfo.getType() ?? undefined,
fields: [],
inlineFragments: [],
};
stack.at(-1)?.inlineFragments.push(s);
stack.push(s);
},
leave() {
stack.pop();
},
},
});
visit(ast, visitor);
return content;
}
function checkUnique(name, content) {
if (content.operations.find((o) => o.name === name)) {
throw new GraphQLError(`Operation with name "${name}" is already defined.`);
}
if (content.fragments.has(name)) {
throw new GraphQLError(`Fragment with name "${name}" is already defined.`);
}
}