UNPKG

megaera

Version:
151 lines (148 loc) 4.76 kB
import { isEnumType, isListType, isNonNullType, isNullableType, isScalarType, } from 'graphql/type/definition.js'; import { isObjectType } from 'graphql/type/index.js'; export function generate(content) { const code = []; for (const f of content.fragments.values()) { code.push(`const ${f.name} = \`#graphql ${f.source}\` export type ${f.name} = ${generateSelector(f, 0, true)} `); } for (const q of content.operations) { if (!q.source) { throw new Error('Empty query source for operation ' + q.name); } let querySource = q.source; for (const fName of usedFragments(q, content)) { querySource = '${' + fName + '}\n' + querySource; } code.push(`export const ${q.name} = \`#graphql ${querySource}\` as string & ${q.name} export type ${q.name} = (${generateVariables(q.variables)}) => ${generateSelector(q, 0, true)} `); } return code.join('\n'); } function usedFragments(q, content) { const fragments = []; for (const field of q.fields) { if (field.isFragment) { fragments.push(field.name); const fragment = content.fragments.get(field.name); if (!fragment) { throw new Error(`Fragment ${field.name} is not defined.`); } fragments.push(...usedFragments(fragment, content)); } fragments.push(...usedFragments(field, content)); } for (const inlineFragment of q.inlineFragments) { fragments.push(...usedFragments(inlineFragment, content)); } return fragments; } function generateVariables(variables) { if (!variables || variables.length === 0) { return ''; } return ('vars: { ' + variables .map((v) => { return v.name + (v.required ? '' : '?') + ': ' + generateType(v.type); }) .join(', ') + ' }'); } function generateSelector(s, depth = 0, nonNull = false) { if (s.fields.length === 0 && s.inlineFragments.length === 0) { return generateType(s.type); } const code = generateFields(s, depth) + generateFragments(s) + generateInlineFragments(s, depth - 1); if (isNonNullType(s.type)) { return wrapInArray(s.type.ofType, code); } return (wrapInArray(s.type, code) + (isNullableType(s.type) && !nonNull ? ' | null' : '')); } function wrapInArray(t, code) { return isListType(t) ? `Array<${code}>` : code; } function generateFragments(s) { let code = ''; for (const fragment of s.fields) { if (!fragment.isFragment) continue; code += ' & ' + fragment.name; } return code; } function generateFields(s, depth) { const nonFragmentFields = s.fields.filter((f) => !f.isFragment); if (nonFragmentFields.length === 0) { return '{}'; } const code = []; code.push('{'); for (const field of nonFragmentFields) { code.push(' '.repeat(depth + 1) + field.name + ': ' + generateSelector(field, depth + 1)); } code.push(' '.repeat(depth) + '}'); return code.join('\n'); } function generateInlineFragments(s, depth) { let code = ''; let nullable = false; for (const fragment of s.inlineFragments) { code += ' & ' + generateSelector(fragment, depth + 1, true); nullable ||= isNullableType(fragment.type); } return code + (nullable ? ' | null' : ''); } export function generateType(t, orNull = ' | null') { if (t === undefined) { return 'unknown'; } if (isNonNullType(t)) { return generateType(t.ofType, ''); } if (isListType(t)) { const subType = generateType(t.ofType); if (subType.includes(' ')) { return '(' + subType + ')[]' + orNull; } return subType + '[]' + orNull; } if (isEnumType(t)) { return t .getValues() .map((v) => `'${v.value}'`) .join(' | '); } if (isObjectType(t)) { const code = []; for (const field of Object.values(t.getFields())) { code.push(field.name + ': ' + generateType(field.type)); } return '{' + code.join(', ') + '}'; } if (isScalarType(t)) { switch (t.name) { case 'String': return 'string' + orNull; case 'Int': return 'number' + orNull; case 'Float': return 'number' + orNull; case 'Boolean': return 'boolean' + orNull; default: return 'string' + orNull; } } throw new Error(`Cannot generate TypeScript type from GraphQL type "${t}".`); }