UNPKG

@theguild/federation-composition

Version:

Open Source Composition library for Apollo Federation

381 lines (380 loc) 17.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.objectTypeBuilder = exports.isRealExtension = void 0; const ast_js_1 = require("./ast.js"); const common_js_1 = require("./common.js"); function isRealExtension(meta, version) { return meta.extension ? meta.extensionType !== '@extends' && version === 'v1.0' ? false : true : false; } exports.isRealExtension = isRealExtension; function objectTypeBuilder() { return { visitSubgraphState(graph, state, typeName, type) { const objectTypeState = getOrCreateObjectType(state, typeName); type.tags.forEach(tag => objectTypeState.tags.add(tag)); if (type.inaccessible) { objectTypeState.inaccessible = true; } const isDefinition = type.isDefinition && (graph.version === 'v1.0' ? type.extensionType !== '@extends' : true); if (type.description && !objectTypeState.description) { objectTypeState.description = type.description; } if (isDefinition) { objectTypeState.hasDefinition = true; } if (type.ast.directives) { type.ast.directives.forEach(directive => { objectTypeState.ast.directives.push(directive); }); } type.interfaces.forEach(interfaceName => objectTypeState.interfaces.add(interfaceName)); objectTypeState.byGraph.set(graph.id, { extension: type.extension, extensionType: type.extensionType, external: type.external, keys: type.keys, inaccessible: type.inaccessible, shareable: type.shareable, interfaces: type.interfaces, }); for (const field of type.fields.values()) { const fieldState = getOrCreateField(objectTypeState, field.name, field.type); field.tags.forEach(tag => fieldState.tags.add(tag)); const usedAsKey = type.fieldsUsedAsKeys.has(field.name); if (usedAsKey) { fieldState.usedAsKey = true; } const isExternal = graph.version === 'v1.0' ? field.external && isRealExtension(type, graph.version) : field.external; const shouldForceType = !usedAsKey && !isExternal && !fieldState.internal.seenNonExternal; const shouldChangeType = shouldForceType || (!isExternal && !field.type.endsWith('!') && fieldState.type.endsWith('!')); if (shouldChangeType) { fieldState.type = field.type; } if (!fieldState.internal.seenNonExternal && !isExternal) { fieldState.internal.seenNonExternal = true; } if (field.inaccessible) { fieldState.inaccessible = true; } if (field.override) { fieldState.override = field.override; } if (field.description && !fieldState.description) { fieldState.description = field.description; } if (field.deprecated && !fieldState.deprecated) { fieldState.deprecated = field.deprecated; } field.ast.directives.forEach(directive => { fieldState.ast.directives.push(directive); }); fieldState.byGraph.set(graph.id, { type: field.type, external: field.external, inaccessible: field.inaccessible, override: field.override, provides: field.provides, requires: field.requires, provided: field.provided, required: field.required, shareable: field.shareable, used: field.used, usedAsKey, }); for (const arg of field.args.values()) { const argState = getOrCreateArg(fieldState, arg.name, arg.type); arg.tags.forEach(tag => argState.tags.add(tag)); if (arg.type.endsWith('!')) { argState.type = arg.type; } if (arg.inaccessible) { argState.inaccessible = true; } if (!field.external) { argState.description = arg.description; } if (arg.deprecated && !argState.deprecated) { argState.deprecated = arg.deprecated; } arg.ast.directives.forEach(directive => { argState.ast.directives.push(directive); }); if (typeof arg.defaultValue === 'string') { argState.defaultValue = arg.defaultValue; } argState.byGraph.set(graph.id, { type: arg.type, inaccessible: arg.inaccessible, defaultValue: arg.defaultValue, }); } } }, composeSupergraphNode(objectType, graphs) { const isQuery = objectType.name === 'Query'; return (0, ast_js_1.createObjectTypeNode)({ name: objectType.name, ast: { directives: (0, common_js_1.convertToConst)(objectType.ast.directives), }, description: objectType.description, fields: Array.from(objectType.fields.values()).map(field => { const fieldInGraphs = Array.from(field.byGraph.entries()); const hasDifferentOutputType = fieldInGraphs.some(([_, meta]) => meta.type !== field.type); const isDefinedEverywhere = field.byGraph.size === (isQuery ? graphs.size : objectType.byGraph.size); let joinFields = []; const differencesBetweenGraphs = { override: false, type: false, external: false, provides: false, requires: false, }; for (const [graphId, meta] of fieldInGraphs) { if (meta.external) { differencesBetweenGraphs.external = field.usedAsKey ? objectType.byGraph.get(graphId).extension !== true : true; } if (meta.override !== null) { differencesBetweenGraphs.override = true; } if (meta.provides !== null) { differencesBetweenGraphs.provides = true; } if (meta.requires !== null) { differencesBetweenGraphs.requires = true; } if (meta.type !== field.type) { differencesBetweenGraphs.type = true; } } if (isQuery) { if (differencesBetweenGraphs.override) { const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null); joinFields = graphsWithOverride.map(([graphId, meta]) => ({ graph: graphId, override: meta.override ?? undefined, type: differencesBetweenGraphs.type ? meta.type : undefined, external: meta.external ?? undefined, provides: meta.provides ?? undefined, requires: meta.requires ?? undefined, })); } else { joinFields = graphs.size > 1 && !isDefinedEverywhere ? fieldInGraphs.map(([graphId, meta]) => ({ graph: graphId, provides: differencesBetweenGraphs.provides ? meta.provides ?? undefined : undefined, })) : []; } } else if (isDefinedEverywhere) { const hasDifferencesBetweenGraphs = Object.values(differencesBetweenGraphs).some(value => value === true); if (differencesBetweenGraphs.override) { const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null); joinFields = graphsWithOverride.map(([graphId, meta]) => ({ graph: graphId, override: meta.override ?? undefined, type: differencesBetweenGraphs.type ? meta.type : undefined, external: meta.external ?? undefined, provides: meta.provides ?? undefined, requires: meta.requires ?? undefined, })); } else if (hasDifferencesBetweenGraphs) { joinFields = fieldInGraphs.map(([graphId, meta]) => ({ graph: graphId, type: differencesBetweenGraphs.type ? meta.type : undefined, override: meta.override ?? undefined, external: meta.external ? field.usedAsKey && objectType.byGraph.get(graphId).extension === true ? false : true : undefined, provides: meta.provides ?? undefined, requires: meta.requires ?? undefined, })); } } else { if (differencesBetweenGraphs.override) { const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null); joinFields = graphsWithOverride.map(([graphId, meta]) => ({ graph: graphId, override: meta.override ?? undefined, type: differencesBetweenGraphs.type ? meta.type : undefined, external: meta.external ?? undefined, provides: meta.provides ?? undefined, requires: meta.requires ?? undefined, })); } else { joinFields = fieldInGraphs.map(([graphId, meta]) => ({ graph: graphId, type: hasDifferentOutputType ? meta.type : undefined, override: meta.override ?? undefined, external: meta.external ?? undefined, provides: meta.provides ?? undefined, requires: meta.requires ?? undefined, })); } } return { name: field.name, type: field.type, inaccessible: field.inaccessible, tags: Array.from(field.tags), description: field.description, deprecated: field.deprecated, ast: { directives: (0, common_js_1.convertToConst)(field.ast.directives), }, join: { field: joinFields, }, arguments: Array.from(field.args.values()) .filter(arg => { if (arg.byGraph.size !== field.byGraph.size) { return false; } return true; }) .map(arg => { return { name: arg.name, type: arg.type, inaccessible: arg.inaccessible, tags: Array.from(arg.tags), defaultValue: arg.defaultValue, description: arg.description, deprecated: arg.deprecated, ast: { directives: (0, common_js_1.convertToConst)(arg.ast.directives), }, }; }), }; }), interfaces: Array.from(objectType.interfaces), tags: Array.from(objectType.tags), inaccessible: objectType.inaccessible, join: { type: isQuery ? Array.from(graphs.values()).map(graph => ({ graph: graph.id, })) : Array.from(objectType.byGraph.entries()) .map(([graphId, meta]) => { if (meta.keys.length) { return meta.keys.map(key => ({ graph: graphId, key: key.fields, extension: isRealExtension(meta, graphs.get(graphId).version), resolvable: key.resolvable, })); } return [ { graph: graphId, }, ]; }) .flat(1), implements: objectType.interfaces.size > 0 ? Array.from(objectType.byGraph.entries()) .map(([graph, meta]) => { if (meta.interfaces.size > 0) { return Array.from(meta.interfaces).map(interfaceName => ({ graph: graph.toUpperCase(), interface: interfaceName, })); } return []; }) .flat(1) : [], }, }); }, }; } exports.objectTypeBuilder = objectTypeBuilder; function getOrCreateObjectType(state, typeName) { const existing = state.get(typeName); if (existing) { return existing; } const def = { name: typeName, tags: new Set(), hasDefinition: false, inaccessible: false, interfaces: new Set(), byGraph: new Map(), fields: new Map(), ast: { directives: [], }, }; state.set(typeName, def); return def; } function getOrCreateField(objectTypeState, fieldName, fieldType) { const existing = objectTypeState.fields.get(fieldName); if (existing) { return existing; } const def = { name: fieldName, type: fieldType, tags: new Set(), inaccessible: false, usedAsKey: false, override: null, byGraph: new Map(), args: new Map(), ast: { directives: [], }, internal: { seenNonExternal: false, }, }; objectTypeState.fields.set(fieldName, def); return def; } function getOrCreateArg(fieldState, argName, argType) { const existing = fieldState.args.get(argName); if (existing) { return existing; } const def = { name: argName, type: argType, tags: new Set(), inaccessible: false, byGraph: new Map(), ast: { directives: [], }, }; fieldState.args.set(argName, def); return def; }