UNPKG

@theguild/federation-composition

Version:
269 lines (268 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSupergraphStateBuilder = createSupergraphStateBuilder; const graphql_1 = require("graphql"); const printer_js_1 = require("../graphql/printer.js"); const link_js_1 = require("../specifications/link.js"); const helpers_js_1 = require("../subgraph/helpers.js"); const ast_js_1 = require("./composition/ast.js"); const directive_js_1 = require("./composition/directive.js"); const enum_type_js_1 = require("./composition/enum-type.js"); const input_object_type_js_1 = require("./composition/input-object-type.js"); const interface_type_js_1 = require("./composition/interface-type.js"); const object_type_js_1 = require("./composition/object-type.js"); const scalar_type_js_1 = require("./composition/scalar-type.js"); const union_type_js_1 = require("./composition/union-type.js"); function createSupergraphStateBuilder() { const state = { subgraphs: new Map(), scalarTypes: new Map(), objectTypes: new Map(), interfaceTypes: new Map(), unionTypes: new Map(), enumTypes: new Map(), inputObjectTypes: new Map(), directives: new Map(), links: [], specs: { tag: false, inaccessible: false, policy: false, requiresScopes: false, authenticated: false, }, }; const directive = (0, directive_js_1.directiveBuilder)(); const scalarType = (0, scalar_type_js_1.scalarTypeBuilder)(); const enumType = (0, enum_type_js_1.enumTypeBuilder)(); const inputObjectType = (0, input_object_type_js_1.inputObjectTypeBuilder)(); const interfaceType = (0, interface_type_js_1.interfaceTypeBuilder)(); const objectType = (0, object_type_js_1.objectTypeBuilder)(); const unionType = (0, union_type_js_1.unionTypeBuilder)(); const subgraphStates = new Map(); const graphNameToIdMap = {}; return { addSubgraph(subgraph) { if (state.subgraphs.has(subgraph.graph.id)) { throw new Error(`Graph with ID "${subgraph.graph.id}" already exists`); } state.subgraphs.set(subgraph.graph.id, subgraph); graphNameToIdMap[subgraph.graph.name] = subgraph.graph.id; }, getGraph(id) { return state.subgraphs.get(id); }, visitSubgraphState(subgraphState) { subgraphStates.set(subgraphState.graph.id, subgraphState); for (const link of subgraphState.links) { state.links.push(linkWithGraph(link, subgraphState.graph.id)); } if (subgraphState.specs.tag) { state.specs.tag = true; } if (subgraphState.specs.inaccessible) { state.specs.inaccessible = true; } if (subgraphState.specs.requiresScopes) { state.specs.requiresScopes = true; } if (subgraphState.specs.authenticated) { state.specs.authenticated = true; } if (subgraphState.specs.policy) { state.specs.policy = true; } for (const [typeName, type] of subgraphState.types) { switch (type.kind) { case 'OBJECT': { objectType.visitSubgraphState(subgraphState.graph, state.objectTypes, typeName, type); break; } case 'INTERFACE': { interfaceType.visitSubgraphState(subgraphState.graph, state.interfaceTypes, typeName, type); break; } case 'UNION': { unionType.visitSubgraphState(subgraphState.graph, state.unionTypes, typeName, type); break; } case 'ENUM': { enumType.visitSubgraphState(subgraphState.graph, state.enumTypes, typeName, type); break; } case 'INPUT_OBJECT': { inputObjectType.visitSubgraphState(subgraphState.graph, state.inputObjectTypes, typeName, type); break; } case 'DIRECTIVE': { directive.visitSubgraphState(subgraphState.graph, state.directives, typeName, type); break; } case 'SCALAR': { scalarType.visitSubgraphState(subgraphState.graph, state.scalarTypes, typeName, type); break; } } } }, getSupergraphState() { return state; }, getSubgraphState(graphId) { return subgraphStates.get(graphId); }, links() { return (0, link_js_1.mergeLinks)(state.links); }, build() { const transformFields = createFieldsTransformer(state); const helpers = { graphNameToId(graphName) { return graphNameToIdMap[graphName] ?? null; }, supergraphState: state, }; const numberOfExpectedNodes = state.directives.size + state.scalarTypes.size + state.objectTypes.size + state.interfaceTypes.size + state.unionTypes.size + state.enumTypes.size + state.inputObjectTypes.size + 1; const nodes = new Array(numberOfExpectedNodes); let i = 0; nodes[i++] = (0, ast_js_1.createJoinGraphEnumTypeNode)(Array.from(state.subgraphs.values()).map(subgraph => ({ name: subgraph.graph.name, enumValue: subgraph.graph.id, url: subgraph.graph.url ?? '', }))); for (const directiveState of state.directives.values()) { nodes[i++] = directive.composeSupergraphNode(directiveState, state.subgraphs, helpers); } for (const scalarTypeState of state.scalarTypes.values()) { nodes[i++] = scalarType.composeSupergraphNode(scalarTypeState, state.subgraphs, helpers); } for (const objectTypeState of state.objectTypes.values()) { nodes[i++] = objectType.composeSupergraphNode(transformFields(objectTypeState), state.subgraphs, helpers); } for (const interfaceTypeState of state.interfaceTypes.values()) { nodes[i++] = interfaceType.composeSupergraphNode(interfaceTypeState, state.subgraphs, helpers); } for (const unionTypeState of state.unionTypes.values()) { nodes[i++] = unionType.composeSupergraphNode(unionTypeState, state.subgraphs, helpers); } for (const enumTypeState of state.enumTypes.values()) { nodes[i++] = enumType.composeSupergraphNode(enumTypeState, state.subgraphs, helpers); } for (const inputObjectTypeState of state.inputObjectTypes.values()) { nodes[i++] = inputObjectType.composeSupergraphNode(inputObjectTypeState, state.subgraphs, helpers); } return nodes; }, }; } function linkWithGraph(link, graph) { link.graph = graph; return link; } function visitSelectionSetNode(selectionSet, locations, currentPath) { for (const selection of selectionSet.selections) { if (selection.kind === graphql_1.Kind.FIELD) { if (selection.selectionSet) { visitSelectionSetNode(selection.selectionSet, locations, currentPath.concat(selection.name.value)); } else { locations.add(currentPath.concat(selection.name.value).join('.')); } } } } function createFieldsSet(selectionSet) { const locations = new Set(); visitSelectionSetNode(selectionSet, locations, []); return locations; } function filterSelectionSetNode(selectionSet, removableFields, currentPath) { const fieldsToDelete = []; for (const selection of selectionSet.selections) { if (selection.kind === graphql_1.Kind.FIELD) { if (selection.selectionSet) { filterSelectionSetNode(selection.selectionSet, removableFields, currentPath + '.' + selection.name.value); if (selectionSet.selections.length === 0) { fieldsToDelete.push(selection.name.value); } } else { const lookingFor = currentPath === '' ? selection.name.value : currentPath + '.' + selection.name.value; if (removableFields.has(lookingFor)) { fieldsToDelete.push(selection.name.value); } } } } for (const fieldName of fieldsToDelete) { selectionSet.selections = selectionSet.selections.filter(selection => selection.kind === graphql_1.Kind.FIELD && selection.name.value === fieldName ? false : true); } } function createFieldsTransformer(state) { function transformFields(removableFields, fields) { const parsedFields = (0, helpers_js_1.parseFields)(fields); if (!parsedFields) { return fields; } filterSelectionSetNode(parsedFields, removableFields, ''); const printed = (0, graphql_1.stripIgnoredCharacters)((0, printer_js_1.print)(parsedFields)); return printed.replace(/^\{/, '').replace(/\}$/, ''); } function mergeKeyFieldsCollection(fieldsCollection) { const fieldsSets = []; for (const fields of fieldsCollection) { const parsedFields = (0, helpers_js_1.parseFields)(fields); if (!parsedFields) { continue; } fieldsSets.push(createFieldsSet(parsedFields)); } let commonFields; for (const fieldsSet of fieldsSets) { if (!commonFields) { commonFields = fieldsSet; continue; } for (const field of commonFields) { if (!fieldsSet.has(field)) { commonFields.delete(field); } } } return commonFields ?? new Set(); } return function visitTypeState(typeState) { for (const [_, fieldState] of typeState.fields) { for (const [graphId, fieldStateInGraph] of fieldState.byGraph) { if (fieldStateInGraph.requires) { const keyFields = mergeKeyFieldsCollection(typeState.byGraph.get(graphId).keys.map(key => key.fields)); const newRequires = transformFields(keyFields, fieldStateInGraph.requires); fieldStateInGraph.requires = newRequires.trim().length === 0 ? null : newRequires; } if (fieldStateInGraph.provides) { const referencedTypeName = fieldState.type.replace(/[!\[\]]+/g, ''); const referencedType = state.objectTypes.get(referencedTypeName) ?? state.interfaceTypes.get(referencedTypeName); if (!referencedType) { throw new Error('Referenced type not found: ' + referencedTypeName); } const referencedTypeInGraph = referencedType.byGraph.get(graphId); const keyFields = referencedTypeInGraph.extension === true ? mergeKeyFieldsCollection(referencedTypeInGraph.keys.map(key => key.fields)) : new Set(); fieldStateInGraph.provides = transformFields(keyFields, fieldStateInGraph.provides); } } } return typeState; }; }