UNPKG

@graphql-tools/graphql

Version:
524 lines (523 loc) 21 kB
import { inspect } from '../jsutils/inspect.js'; import { Kind, } from '../language/index.js'; import { GraphQLDeprecatedDirective, isListType, isNonNullType, } from '../type/index.js'; import { astFromValue } from './astFromValue.js'; import { getRootTypeMap } from './getRootTypeMap.js'; function isSome(input) { return input != null; } export function astFromType(type) { if (isNonNullType(type)) { const innerType = astFromType(type.ofType); if (innerType.kind === Kind.NON_NULL_TYPE) { throw new Error(`Invalid type node ${inspect(type)}. Inner type of non-null type cannot be a non-null type.`); } return { kind: Kind.NON_NULL_TYPE, type: innerType, }; } else if (isListType(type)) { return { kind: Kind.LIST_TYPE, type: astFromType(type.ofType), }; } return { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: type.name, }, }; } export function astFromSchema(schema, pathToDirectivesInExtensions) { var _a, _b; const operationTypeMap = new Map([ ['query', undefined], ['mutation', undefined], ['subscription', undefined], ]); const nodes = []; if (schema.astNode != null) { nodes.push(schema.astNode); } if (schema.extensionASTNodes != null) { for (const extensionASTNode of schema.extensionASTNodes) { nodes.push(extensionASTNode); } } for (const node of nodes) { if (node.operationTypes) { for (const operationTypeDefinitionNode of node.operationTypes) { operationTypeMap.set(operationTypeDefinitionNode.operation, operationTypeDefinitionNode); } } } const rootTypeMap = getRootTypeMap(schema); for (const [operationTypeNode, operationTypeDefinitionNode] of operationTypeMap) { const rootType = rootTypeMap.get(operationTypeNode); if (rootType != null) { const rootTypeAST = astFromType(rootType); if (operationTypeDefinitionNode != null) { operationTypeDefinitionNode.type = rootTypeAST; } else { operationTypeMap.set(operationTypeNode, { kind: Kind.OPERATION_TYPE_DEFINITION, operation: operationTypeNode, type: rootTypeAST, }); } } } const operationTypes = [...operationTypeMap.values()].filter(isSome); const directives = getDirectiveNodes(schema, schema, pathToDirectivesInExtensions); if (!operationTypes.length && !directives.length) { return null; } const schemaNode = { kind: operationTypes != null ? Kind.SCHEMA_DEFINITION : Kind.SCHEMA_EXTENSION, operationTypes, // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: directives, }; // This code is so weird because it needs to support GraphQL.js 14 // In GraphQL.js 14 there is no `description` value on schemaNode schemaNode.description = ((_b = (_a = schema.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : schema.description != null) ? { kind: Kind.STRING, value: schema.description, block: true, } : undefined; return schemaNode; } export function astFromDirective(directive, schema, pathToDirectivesInExtensions) { var _a, _b, _c, _d; return { kind: Kind.DIRECTIVE_DEFINITION, description: (_b = (_a = directive.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (directive.description ? { kind: Kind.STRING, value: directive.description, } : undefined), name: { kind: Kind.NAME, value: directive.name, }, arguments: (_c = directive.args) === null || _c === void 0 ? void 0 : _c.map(arg => astFromArg(arg, schema, pathToDirectivesInExtensions)), repeatable: directive.isRepeatable, locations: ((_d = directive.locations) === null || _d === void 0 ? void 0 : _d.map(location => ({ kind: Kind.NAME, value: location, }))) || [], }; } export function getDirectiveNodes(entity, schema, pathToDirectivesInExtensions) { const directivesInExtensions = getDirectivesInExtensions(entity, pathToDirectivesInExtensions); let nodes = []; if (entity.astNode != null) { nodes.push(entity.astNode); } if ('extensionASTNodes' in entity && entity.extensionASTNodes != null) { nodes = nodes.concat(entity.extensionASTNodes); } let directives; if (directivesInExtensions != null) { directives = makeDirectiveNodes(schema, directivesInExtensions); } else { directives = []; for (const node of nodes) { if (node.directives) { directives.push(...node.directives); } } } return directives; } export function getDeprecatableDirectiveNodes(entity, schema, pathToDirectivesInExtensions) { var _a, _b; let directiveNodesBesidesDeprecated = []; let deprecatedDirectiveNode = null; const directivesInExtensions = getDirectivesInExtensions(entity, pathToDirectivesInExtensions); let directives; if (directivesInExtensions != null) { directives = makeDirectiveNodes(schema, directivesInExtensions); } else { directives = (_a = entity.astNode) === null || _a === void 0 ? void 0 : _a.directives; } if (directives != null) { directiveNodesBesidesDeprecated = directives.filter(directive => directive.name.value !== 'deprecated'); if (entity.deprecationReason != null) { deprecatedDirectiveNode = (_b = directives.filter(directive => directive.name.value === 'deprecated')) === null || _b === void 0 ? void 0 : _b[0]; } } if (entity.deprecationReason != null && deprecatedDirectiveNode == null) { deprecatedDirectiveNode = makeDeprecatedDirective(entity.deprecationReason); } return deprecatedDirectiveNode == null ? directiveNodesBesidesDeprecated : [deprecatedDirectiveNode].concat(directiveNodesBesidesDeprecated); } export function astFromArg(arg, schema, pathToDirectivesInExtensions) { var _a, _b, _c; return { kind: Kind.INPUT_VALUE_DEFINITION, description: (_b = (_a = arg.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (arg.description ? { kind: Kind.STRING, value: arg.description, block: true, } : undefined), name: { kind: Kind.NAME, value: arg.name, }, type: astFromType(arg.type), // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility defaultValue: arg.defaultValue !== undefined ? (_c = astFromValue(arg.defaultValue, arg.type)) !== null && _c !== void 0 ? _c : undefined : undefined, directives: getDeprecatableDirectiveNodes(arg, schema, pathToDirectivesInExtensions), }; } export function astFromObjectType(type, schema, pathToDirectivesInExtensions) { var _a, _b; return { kind: Kind.OBJECT_TYPE_DEFINITION, description: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (type.description ? { kind: Kind.STRING, value: type.description, block: true, } : undefined), name: { kind: Kind.NAME, value: type.name, }, fields: Object.values(type.getFields()).map(field => astFromField(field, schema, pathToDirectivesInExtensions)), interfaces: Object.values(type.getInterfaces()).map(iFace => astFromType(iFace)), directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions), }; } export function astFromInterfaceType(type, schema, pathToDirectivesInExtensions) { var _a, _b; const node = { kind: Kind.INTERFACE_TYPE_DEFINITION, description: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (type.description ? { kind: Kind.STRING, value: type.description, block: true, } : undefined), name: { kind: Kind.NAME, value: type.name, }, fields: Object.values(type.getFields()).map(field => astFromField(field, schema, pathToDirectivesInExtensions)), directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions), }; if ('getInterfaces' in type) { node.interfaces = Object.values(type.getInterfaces()).map(iFace => astFromType(iFace)); } return node; } export function astFromUnionType(type, schema, pathToDirectivesInExtensions) { var _a, _b; return { kind: Kind.UNION_TYPE_DEFINITION, description: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (type.description ? { kind: Kind.STRING, value: type.description, block: true, } : undefined), name: { kind: Kind.NAME, value: type.name, }, // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions), types: type.getTypes().map(type => astFromType(type)), }; } export function astFromInputObjectType(type, schema, pathToDirectivesInExtensions) { var _a, _b; return { kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, description: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (type.description ? { kind: Kind.STRING, value: type.description, block: true, } : undefined), name: { kind: Kind.NAME, value: type.name, }, fields: Object.values(type.getFields()).map(field => astFromInputField(field, schema, pathToDirectivesInExtensions)), // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions), }; } export function astFromEnumType(type, schema, pathToDirectivesInExtensions) { var _a, _b; return { kind: Kind.ENUM_TYPE_DEFINITION, description: (_b = (_a = type.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (type.description ? { kind: Kind.STRING, value: type.description, block: true, } : undefined), name: { kind: Kind.NAME, value: type.name, }, values: Object.values(type.getValues()).map(value => astFromEnumValue(value, schema, pathToDirectivesInExtensions)), // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions), }; } function getDirectivesInExtensions(node, pathToDirectivesInExtensions = ['directives']) { return pathToDirectivesInExtensions.reduce((acc, pathSegment) => (acc == null ? acc : acc[pathSegment]), node === null || node === void 0 ? void 0 : node.extensions); } export function astFromScalarType(type, schema, pathToDirectivesInExtensions) { var _a, _b, _c; const directivesInExtensions = getDirectivesInExtensions(type, pathToDirectivesInExtensions); const directives = directivesInExtensions ? makeDirectiveNodes(schema, directivesInExtensions) : ((_a = type.astNode) === null || _a === void 0 ? void 0 : _a.directives) || []; const specifiedByValue = (type.specifiedByUrl || type.specifiedByURL); if (specifiedByValue && !directives.some(directiveNode => directiveNode.name.value === 'specifiedBy')) { const specifiedByArgs = { url: specifiedByValue, }; directives.push(makeDirectiveNode('specifiedBy', specifiedByArgs)); } return { kind: Kind.SCALAR_TYPE_DEFINITION, description: (_c = (_b = type.astNode) === null || _b === void 0 ? void 0 : _b.description) !== null && _c !== void 0 ? _c : (type.description ? { kind: Kind.STRING, value: type.description, block: true, } : undefined), name: { kind: Kind.NAME, value: type.name, }, // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: directives, }; } export function astFromField(field, schema, pathToDirectivesInExtensions) { var _a, _b; return { kind: Kind.FIELD_DEFINITION, description: (_b = (_a = field.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (field.description ? { kind: Kind.STRING, value: field.description, block: true, } : undefined), name: { kind: Kind.NAME, value: field.name, }, arguments: field.args.map(arg => astFromArg(arg, schema, pathToDirectivesInExtensions)), type: astFromType(field.type), // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: getDeprecatableDirectiveNodes(field, schema, pathToDirectivesInExtensions), }; } export function astFromInputField(field, schema, pathToDirectivesInExtensions) { var _a, _b, _c; return { kind: Kind.INPUT_VALUE_DEFINITION, description: (_b = (_a = field.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (field.description ? { kind: Kind.STRING, value: field.description, block: true, } : undefined), name: { kind: Kind.NAME, value: field.name, }, type: astFromType(field.type), // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: getDeprecatableDirectiveNodes(field, schema, pathToDirectivesInExtensions), defaultValue: (_c = astFromValue(field.defaultValue, field.type)) !== null && _c !== void 0 ? _c : undefined, }; } export function astFromEnumValue(value, schema, pathToDirectivesInExtensions) { var _a, _b; return { kind: Kind.ENUM_VALUE_DEFINITION, description: (_b = (_a = value.astNode) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : (value.description ? { kind: Kind.STRING, value: value.description, block: true, } : undefined), name: { kind: Kind.NAME, value: value.name, }, // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility directives: getDeprecatableDirectiveNodes(value, schema, pathToDirectivesInExtensions), }; } /** * Produces a GraphQL Value AST given a JavaScript object. * Function will match JavaScript/JSON values to GraphQL AST schema format * by using the following mapping. * * | JSON Value | GraphQL Value | * | ------------- | -------------------- | * | Object | Input Object | * | Array | List | * | Boolean | Boolean | * | String | String | * | Number | Int / Float | * | null | NullValue | * */ export function astFromValueUntyped(value) { // only explicit null, not undefined, NaN if (value === null) { return { kind: Kind.NULL }; } // undefined if (value === undefined) { return null; } // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but // the value is not an array, convert the value using the list's item type. if (Array.isArray(value)) { const valuesNodes = []; for (const item of value) { const itemNode = astFromValueUntyped(item); if (itemNode != null) { valuesNodes.push(itemNode); } } return { kind: Kind.LIST, values: valuesNodes }; } if (typeof value === 'object') { const fieldNodes = []; for (const fieldName in value) { const fieldValue = value[fieldName]; const ast = astFromValueUntyped(fieldValue); if (ast) { fieldNodes.push({ kind: Kind.OBJECT_FIELD, name: { kind: Kind.NAME, value: fieldName }, value: ast, }); } } return { kind: Kind.OBJECT, fields: fieldNodes }; } // Others serialize based on their corresponding JavaScript scalar types. if (typeof value === 'boolean') { return { kind: Kind.BOOLEAN, value }; } // JavaScript numbers can be Int or Float values. if (typeof value === 'number' && isFinite(value)) { const stringNum = String(value); return integerStringRegExp.test(stringNum) ? { kind: Kind.INT, value: stringNum } : { kind: Kind.FLOAT, value: stringNum }; } if (typeof value === 'string') { return { kind: Kind.STRING, value }; } throw new TypeError(`Cannot convert value to AST: ${value}.`); } /** * IntValue: * - NegativeSign? 0 * - NegativeSign? NonZeroDigit ( Digit+ )? */ const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/; function makeDeprecatedDirective(deprecationReason) { return makeDirectiveNode('deprecated', { reason: deprecationReason }, GraphQLDeprecatedDirective); } function makeDirectiveNode(name, args, directive) { const directiveArguments = []; if (directive != null) { for (const arg of directive.args) { const argName = arg.name; const argValue = args[argName]; if (argValue !== undefined) { const value = astFromValue(argValue, arg.type); if (value) { directiveArguments.push({ kind: Kind.ARGUMENT, name: { kind: Kind.NAME, value: argName, }, value, }); } } } } else { for (const argName in args) { const argValue = args[argName]; const value = astFromValueUntyped(argValue); if (value) { directiveArguments.push({ kind: Kind.ARGUMENT, name: { kind: Kind.NAME, value: argName, }, value, }); } } } return { kind: Kind.DIRECTIVE, name: { kind: Kind.NAME, value: name, }, arguments: directiveArguments, }; } export function makeDirectiveNodes(schema, directiveValues) { const directiveNodes = []; for (const directiveName in directiveValues) { const arrayOrSingleValue = directiveValues[directiveName]; const directive = schema === null || schema === void 0 ? void 0 : schema.getDirective(directiveName); if (Array.isArray(arrayOrSingleValue)) { for (const value of arrayOrSingleValue) { directiveNodes.push(makeDirectiveNode(directiveName, value, directive)); } } else { directiveNodes.push(makeDirectiveNode(directiveName, arrayOrSingleValue, directive)); } } return directiveNodes; }