.then(response => { const schema = JSON.parse(response.body).data const sdl = printSchema(buildClientSchema(schema)) const parsedSdl = parse(sdl) const mutatedSdl = visit(parsedSdl, { ObjectTypeDefinition: { enter(node: ObjectTypeDefinitionNode) { if ( !['Query', 'Mutation', 'Subscription'].includes(node.name.value) ) { const nodeWithValidFields = visit(node, { FieldDefinition: { enter: (fieldNode: FieldDefinitionNode) => { if ( (fieldNode.arguments && fieldNode.arguments.length > 0 && fieldNode.type.kind === 'NamedType') || (fieldNode.type.kind === 'NonNullType' && !isScalarType(fieldNode.type)) ) { return { ...fieldNode, arguments: fieldNode.arguments ? fieldNode.arguments.filter( arg => arg.name.value !== 'where', ) : [], } } else { return fieldNode } }, }, }) return nodeWithValidFields } }, }, EnumTypeDefinition: { enter(enumNode: EnumTypeDefinitionNode) { if (enumNode.name.value === 'PrismaDatabase') { return null } }, }, FieldDefinition: { enter(fieldNode: FieldDefinitionNode) { if (fieldNode.name.value === 'executeRaw') { return null } }, }, }) console.log(print(mutatedSdl)) })
export function printWithReducedWhitespace(ast: DocumentNode): string { // In a GraphQL AST (which notably does not contain comments), the only place // where meaningful whitespace (or double quotes) can exist is in // StringNodes. So to print with reduced whitespace, we: // - temporarily sanitize strings by replacing their contents with hex // - use the default GraphQL printer // - minimize the whitespace with a simple regexp replacement // - convert strings back to their actual value // We normalize all strings to non-block strings for simplicity. const sanitizedAST = visit(ast, { StringValue(node: StringValueNode): StringValueNode { return { ...node, value: Buffer.from(node.value, 'utf8').toString('hex'), block: false, }; }, }); const withWhitespace = print(sanitizedAST); const minimizedButStillHex = withWhitespace .replace(/\s+/g, ' ') .replace(/([^_a-zA-Z0-9]) /g, (_, c) => c) .replace(/ ([^_a-zA-Z0-9])/g, (_, c) => c); return minimizedButStillHex.replace(/"([a-f0-9]+)"/g, (_, hex) => JSON.stringify(Buffer.from(hex, 'hex').toString('utf8')), ); }
export function removeAliases(ast: DocumentNode): DocumentNode { return visit(ast, { Field(node: FieldNode): FieldNode { return { ...node, alias: undefined, }; }, }); }
public transformRequest(originalRequest: Request): Request { let fromSelection: SelectionSetNode; const ourPathFrom = JSON.stringify(this.from); const ourPathTo = JSON.stringify(this.to); let fieldPath: Array<string> = []; visit(originalRequest.document, { [Kind.FIELD]: { enter: (node: FieldNode) => { fieldPath.push(node.name.value); if (ourPathFrom === JSON.stringify(fieldPath)) { fromSelection = node.selectionSet; return BREAK; } }, leave: (node: FieldNode) => { fieldPath.pop(); }, }, }); fieldPath = []; const newDocument = visit(originalRequest.document, { [Kind.FIELD]: { enter: (node: FieldNode) => { fieldPath.push(node.name.value); if (ourPathTo === JSON.stringify(fieldPath) && fromSelection) { return { ...node, selectionSet: fromSelection, }; } }, leave: (node: FieldNode) => { fieldPath.pop(); }, }, }); return { ...originalRequest, document: newDocument, }; }
function extractFragmentName(node: ASTNode) { visit(node, { enter(node) { if (node.kind !== "FragmentSpread") { return; } else if (relatedFragments.some(fragmentName => fragmentName === node.name.value)) { return; } relatedFragments = [...relatedFragments, node.name.value]; } }); }
export function sortAST(ast: DocumentNode): DocumentNode { return visit(ast, { OperationDefinition( node: OperationDefinitionNode, ): OperationDefinitionNode { return { ...node, variableDefinitions: sorted( node.variableDefinitions, 'variable.name.value', ), }; }, SelectionSet(node: SelectionSetNode): SelectionSetNode { return { ...node, // Define an ordering for field names in a SelectionSet. Field first, // then FragmentSpread, then InlineFragment. By a lovely coincidence, // the order we want them to appear in is alphabetical by node.kind. // Use sortBy instead of sorted because 'selections' is not optional. selections: sortBy(node.selections, 'kind', 'name.value'), }; }, Field(node: FieldNode): FieldNode { return { ...node, arguments: sorted(node.arguments, 'name.value'), }; }, FragmentSpread(node: FragmentSpreadNode): FragmentSpreadNode { return { ...node, directives: sorted(node.directives, 'name.value') }; }, InlineFragment(node: InlineFragmentNode): InlineFragmentNode { return { ...node, directives: sorted(node.directives, 'name.value') }; }, FragmentDefinition(node: FragmentDefinitionNode): FragmentDefinitionNode { return { ...node, directives: sorted(node.directives, 'name.value'), variableDefinitions: sorted( node.variableDefinitions, 'variable.name.value', ), }; }, Directive(node: DirectiveNode): DirectiveNode { return { ...node, arguments: sorted(node.arguments, 'name.value') }; }, }); }
function replaceFieldsWithFragments( targetSchema: GraphQLSchema, document: DocumentNode, mapping: FieldToFragmentMapping, ): DocumentNode { const typeInfo = new TypeInfo(targetSchema); return visit( document, visitWithTypeInfo(typeInfo, { [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { const parentType: GraphQLType = typeInfo.getParentType(); if (parentType) { const parentTypeName = parentType.name; let selections = node.selections; if (mapping[parentTypeName]) { node.selections.forEach(selection => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const fragments = mapping[parentTypeName][name]; if (fragments && fragments.length > 0) { const fragment = concatInlineFragments( parentTypeName, fragments, ); selections = selections.concat(fragment); } } }); } if (selections !== node.selections) { return { ...node, selections, }; } } }, }), ); }
export function hideLiterals(ast: DocumentNode): DocumentNode { return visit(ast, { IntValue(node: IntValueNode): IntValueNode { return { ...node, value: '0' }; }, FloatValue(node: FloatValueNode): FloatValueNode { return { ...node, value: '0' }; }, StringValue(node: StringValueNode): StringValueNode { return { ...node, value: '', block: false }; }, ListValue(node: ListValueNode): ListValueNode { return { ...node, values: [] }; }, ObjectValue(node: ObjectValueNode): ObjectValueNode { return { ...node, fields: [] }; }, }); }
function addTypenameToAbstract( targetSchema: GraphQLSchema, document: DocumentNode, ): DocumentNode { const typeInfo = new TypeInfo(targetSchema); return visit( document, visitWithTypeInfo(typeInfo, { [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { const parentType: GraphQLType = typeInfo.getParentType(); let selections = node.selections; if ( parentType && (parentType instanceof GraphQLInterfaceType || parentType instanceof GraphQLUnionType) && !selections.find( _ => (_ as FieldNode).kind === Kind.FIELD && (_ as FieldNode).name.value === '__typename', ) ) { selections = selections.concat({ kind: Kind.FIELD, name: { kind: Kind.NAME, value: '__typename', }, }); } if (selections !== node.selections) { return { ...node, selections, }; } }, }), ); }
public transformRequest(originalRequest: Request): Request { const newDocument = visit(originalRequest.document, { [Kind.NAMED_TYPE]: (node: NamedTypeNode) => { const name = node.name.value; if (name in this.reverseMap) { return { ...node, name: { kind: Kind.NAME, value: this.reverseMap[name], }, }; } }, }); return { document: newDocument, variables: originalRequest.variables, }; }