UNPKG

graphql-codegen-typescript-validation-schema

Version:

GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema

181 lines (180 loc) 7.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isInput = exports.isNamedType = exports.isNonNullType = exports.isListType = void 0; exports.ObjectTypeDefinitionBuilder = ObjectTypeDefinitionBuilder; exports.InterfaceTypeDefinitionBuilder = InterfaceTypeDefinitionBuilder; exports.topologicalSortAST = topologicalSortAST; exports.topsort = topsort; exports.isGeneratedByIntrospection = isGeneratedByIntrospection; exports.escapeGraphQLCharacters = escapeGraphQLCharacters; const graphlib_1 = require("graphlib"); const graphql_1 = require("graphql"); /** * Recursively unwraps a GraphQL type until it reaches the NamedType. * * Since a GraphQL type is defined as either a NamedTypeNode, ListTypeNode, or NonNullTypeNode, * this implementation safely recurses until the underlying NamedTypeNode is reached. */ function getNamedType(typeNode) { return typeNode.kind === graphql_1.Kind.NAMED_TYPE ? typeNode : getNamedType(typeNode.type); } const isListType = (typ) => typ?.kind === graphql_1.Kind.LIST_TYPE; exports.isListType = isListType; const isNonNullType = (typ) => typ?.kind === graphql_1.Kind.NON_NULL_TYPE; exports.isNonNullType = isNonNullType; const isNamedType = (typ) => typ?.kind === graphql_1.Kind.NAMED_TYPE; exports.isNamedType = isNamedType; const isInput = (kind) => kind.includes('Input'); exports.isInput = isInput; function ObjectTypeDefinitionBuilder(useObjectTypes, callback) { if (!useObjectTypes) return undefined; return (node) => { if (/^(Query|Mutation|Subscription)$/.test(node.name.value)) return; return callback(node); }; } function InterfaceTypeDefinitionBuilder(useInterfaceTypes, callback) { if (!useInterfaceTypes) return undefined; return (node) => { return callback(node); }; } function topologicalSortAST(schema, ast) { const dependencyGraph = new graphlib_1.Graph(); const targetKinds = [ graphql_1.Kind.OBJECT_TYPE_DEFINITION, graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION, graphql_1.Kind.INTERFACE_TYPE_DEFINITION, graphql_1.Kind.SCALAR_TYPE_DEFINITION, graphql_1.Kind.ENUM_TYPE_DEFINITION, graphql_1.Kind.UNION_TYPE_DEFINITION, ]; (0, graphql_1.visit)(ast, { enter: (node) => { switch (node.kind) { case graphql_1.Kind.OBJECT_TYPE_DEFINITION: case graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION: case graphql_1.Kind.INTERFACE_TYPE_DEFINITION: { const typeName = node.name.value; dependencyGraph.setNode(typeName); if (node.fields) { node.fields.forEach((field) => { // Unwrap the type const namedTypeNode = getNamedType(field.type); const dependency = namedTypeNode.name.value; const namedType = schema.getType(dependency); if (namedType?.astNode?.kind === undefined || !targetKinds.includes(namedType.astNode.kind)) { return; } if (!dependencyGraph.hasNode(dependency)) { dependencyGraph.setNode(dependency); } dependencyGraph.setEdge(typeName, dependency); }); } break; } case graphql_1.Kind.SCALAR_TYPE_DEFINITION: case graphql_1.Kind.ENUM_TYPE_DEFINITION: { dependencyGraph.setNode(node.name.value); break; } case graphql_1.Kind.UNION_TYPE_DEFINITION: { const dependency = node.name.value; if (!dependencyGraph.hasNode(dependency)) dependencyGraph.setNode(dependency); node.types?.forEach((type) => { const dependency = type.name.value; const typ = schema.getType(dependency); if (typ?.astNode?.kind === undefined || !targetKinds.includes(typ.astNode.kind)) return; dependencyGraph.setEdge(node.name.value, dependency); }); break; } default: break; } }, }); const sorted = topsort(dependencyGraph); // Create a map of definitions for quick access, using the definition's name as the key. const definitionsMap = new Map(); // SCHEMA_DEFINITION does not have a name. // https://spec.graphql.org/October2021/#sec-Schema const astDefinitions = ast.definitions.filter(def => def.kind !== graphql_1.Kind.SCHEMA_DEFINITION); astDefinitions.forEach((definition) => { if (hasNameField(definition) && definition.name) definitionsMap.set(definition.name.value, definition); }); // Two arrays to store sorted and unsorted definitions. const sortedDefinitions = []; const notSortedDefinitions = []; // Iterate over sorted type names and retrieve their corresponding definitions. sorted.forEach((sortedType) => { const definition = definitionsMap.get(sortedType); if (definition) { sortedDefinitions.push(definition); definitionsMap.delete(sortedType); } }); // Definitions that are left in the map were not included in sorted list // Add them to notSortedDefinitions. definitionsMap.forEach(definition => notSortedDefinitions.push(definition)); const newDefinitions = [...sortedDefinitions, ...notSortedDefinitions]; if (newDefinitions.length !== astDefinitions.length) { throw new Error(`Unexpected definition length after sorting: want ${astDefinitions.length} but got ${newDefinitions.length}`); } return { ...ast, definitions: newDefinitions, }; } function hasNameField(node) { return 'name' in node; } /** * [Re-implemented topsort function][topsort-ref] with cycle handling. This version iterates over * all nodes in the graph to ensure every node is visited, even if the graph contains cycles. * * [topsort-ref]: https://github.com/dagrejs/graphlib/blob/8d27cb89029081c72eb89dde652602805bdd0a34/lib/alg/topsort.js */ function topsort(g) { const visited = new Set(); const results = []; function visit(node) { if (visited.has(node)) return; visited.add(node); // Recursively visit all predecessors of the node. g.predecessors(node)?.forEach(visit); results.push(node); } // Visit every node in the graph (instead of only sinks) g.nodes().forEach(visit); return results.reverse(); } function isGeneratedByIntrospection(schema) { return Object.entries(schema.getTypeMap()) .filter(([name, type]) => !name.startsWith('__') && !(0, graphql_1.isSpecifiedScalarType)(type)) .every(([, type]) => type.astNode === undefined); } // https://spec.graphql.org/October2021/#EscapedCharacter const escapeMap = { '\"': '\\\"', '\\': '\\\\', '\/': '\\/', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', }; function escapeGraphQLCharacters(input) { // eslint-disable-next-line regexp/no-escape-backspace return input.replace(/["\\/\f\n\r\t\b]/g, match => escapeMap[match]); }