UNPKG

@graphql-tools/stitch

Version:

A set of utils for faster development of GraphQL tools

1,385 lines (1,378 loc) • 111 kB
'use strict'; var delegate = require('@graphql-tools/delegate'); var merge = require('@graphql-tools/merge'); var schema = require('@graphql-tools/schema'); var utils = require('@graphql-tools/utils'); var graphql = require('graphql'); var promiseHelpers = require('@whatwg-node/promise-helpers'); var batchDelegate = require('@graphql-tools/batch-delegate'); var wrap = require('@graphql-tools/wrap'); var executor = require('@graphql-tools/executor'); function getFieldsNotInSubschema(schema, stitchingInfo, gatewayType, subschemaType, fieldNodes, fragments, variableValues, subschema) { const sourceSchema = subschema.transformedSchema; let { fields: subFieldNodesByResponseKey, patches } = utils.collectSubFields( schema, fragments, variableValues, gatewayType, fieldNodes ); let mapChanged = false; if (patches.length) { subFieldNodesByResponseKey = new Map(subFieldNodesByResponseKey); for (const patch of patches) { for (const [responseKey, fields2] of patch.fields) { if (!mapChanged) { subFieldNodesByResponseKey = new Map(subFieldNodesByResponseKey); mapChanged = true; } const existingSubFieldNodes = subFieldNodesByResponseKey.get(responseKey); if (existingSubFieldNodes) { existingSubFieldNodes.push(...fields2); } else { subFieldNodesByResponseKey.set(responseKey, fields2); } } } } const fieldsNotInSchema = /* @__PURE__ */ new Set(); if (graphql.isAbstractType(gatewayType)) { fieldsNotInSchema.add({ kind: graphql.Kind.FIELD, name: { kind: graphql.Kind.NAME, value: "__typename" } }); for (const possibleType of schema.getPossibleTypes(gatewayType)) { const { fields: subFieldNodesOfPossibleType, patches: patches2 } = utils.collectSubFields( schema, fragments, variableValues, possibleType, fieldNodes ); for (const patch of patches2) { for (const [responseKey, fields2] of patch.fields) { if (!mapChanged) { subFieldNodesByResponseKey = new Map(subFieldNodesByResponseKey); mapChanged = true; } const existingSubFieldNodes = subFieldNodesByResponseKey.get(responseKey); if (existingSubFieldNodes) { existingSubFieldNodes.push(...fields2); } else { subFieldNodesByResponseKey.set(responseKey, fields2); } } } for (const [responseKey, subFieldNodes] of subFieldNodesOfPossibleType) { if (!mapChanged) { subFieldNodesByResponseKey = new Map(subFieldNodesByResponseKey); mapChanged = true; } const existingSubFieldNodes = subFieldNodesByResponseKey.get(responseKey); if (existingSubFieldNodes) { existingSubFieldNodes.push(...subFieldNodes); } else { subFieldNodesByResponseKey.set(responseKey, subFieldNodes); } } } } const fieldNodesByField = stitchingInfo?.fieldNodesByField; const fields = subschemaType.getFields(); const fieldNodesByFieldForType = fieldNodesByField?.[gatewayType.name]; for (const [, subFieldNodes] of subFieldNodesByResponseKey) { let fieldNotInSchema = false; const fieldName = subFieldNodes[0]?.name.value; const field = fields[fieldName]; if (!field) { fieldNotInSchema = true; for (const subFieldNode of subFieldNodes) { fieldsNotInSchema.add(subFieldNode); } } else { for (const subFieldNode of subFieldNodes) { const unavailableFields = delegate.extractUnavailableFields( sourceSchema, field, subFieldNode, (fieldType) => { if (stitchingInfo.mergedTypes[fieldType.name]?.resolvers.get( subschema )) { return false; } return true; } ); if (unavailableFields.length) { fieldNotInSchema = true; fieldsNotInSchema.add({ ...subFieldNode, selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: unavailableFields } }); } } } const isComputedField = subschema.merge?.[gatewayType.name]?.fields?.[fieldName]?.computed; let addedSubFieldNodes = false; if ((isComputedField || fieldNotInSchema) && fieldNodesByFieldForType) { const visitedFieldNames = /* @__PURE__ */ new Set(); addMissingRequiredFields({ fieldName, fields, fieldsNotInSchema, visitedFieldNames, onAdd: () => { if (!addedSubFieldNodes) { for (const subFieldNode of subFieldNodes) { fieldsNotInSchema.add(subFieldNode); } addedSubFieldNodes = true; } }, fieldNodesByField: fieldNodesByFieldForType }); } } return Array.from(fieldsNotInSchema); } function addMissingRequiredFields({ fieldName, fields, fieldsNotInSchema, onAdd, fieldNodesByField, visitedFieldNames }) { if (visitedFieldNames.has(fieldName)) { return; } visitedFieldNames.add(fieldName); const fieldNodesForField = fieldNodesByField?.[fieldName]; if (fieldNodesForField) { for (const fieldNode of fieldNodesForField) { if (fieldNode.name.value !== "__typename" && !fields[fieldNode.name.value]) { onAdd(); fieldsNotInSchema.add(fieldNode); addMissingRequiredFields({ fieldName: fieldNode.name.value, fields, fieldsNotInSchema, onAdd, fieldNodesByField, visitedFieldNames }); } } } } function memoize5of7(fn) { const memoize5Cache = /* @__PURE__ */ new WeakMap(); return function memoized(a1, a2, a3, a4, a5, a6, a7) { let cache2 = memoize5Cache.get(a1); if (!cache2) { cache2 = /* @__PURE__ */ new WeakMap(); memoize5Cache.set(a1, cache2); const cache32 = /* @__PURE__ */ new WeakMap(); cache2.set(a2, cache32); const cache42 = /* @__PURE__ */ new WeakMap(); cache32.set(a3, cache42); const cache52 = /* @__PURE__ */ new WeakMap(); cache42.set(a4, cache52); const newValue = fn(a1, a2, a3, a4, a5, a6, a7); cache52.set(a5, newValue); return newValue; } let cache3 = cache2.get(a2); if (!cache3) { cache3 = /* @__PURE__ */ new WeakMap(); cache2.set(a2, cache3); const cache42 = /* @__PURE__ */ new WeakMap(); cache3.set(a3, cache42); const cache52 = /* @__PURE__ */ new WeakMap(); cache42.set(a4, cache52); const newValue = fn(a1, a2, a3, a4, a5, a6, a7); cache52.set(a5, newValue); return newValue; } let cache4 = cache3.get(a3); if (!cache4) { cache4 = /* @__PURE__ */ new WeakMap(); cache3.set(a3, cache4); const cache52 = /* @__PURE__ */ new WeakMap(); cache4.set(a4, cache52); const newValue = fn(a1, a2, a3, a4, a5, a6, a7); cache52.set(a5, newValue); return newValue; } let cache5 = cache4.get(a4); if (!cache5) { cache5 = /* @__PURE__ */ new WeakMap(); cache4.set(a4, cache5); const newValue = fn(a1, a2, a3, a4, a5, a6, a7); cache5.set(a5, newValue); return newValue; } const cachedValue = cache5.get(a5); if (cachedValue === void 0) { const newValue = fn(a1, a2, a3, a4, a5, a6, a7); cache5.set(a5, newValue); return newValue; } return cachedValue; }; } function calculateDelegationStage(mergedTypeInfo, sourceSubschemas, targetSubschemas, fieldNodes, fragments) { const { selectionSets, fieldSelectionSets, uniqueFields, nonUniqueFields } = mergedTypeInfo; const proxiableSubschemas = []; const nonProxiableSubschemas = []; for (const t of targetSubschemas) { const selectionSet = selectionSets.get(t); const fieldSelectionSetsMap = fieldSelectionSets.get(t); if (selectionSet != null && !subschemaTypesContainSelectionSet( mergedTypeInfo, sourceSubschemas, selectionSet )) { nonProxiableSubschemas.push(t); } else { if (fieldSelectionSetsMap == null || fieldNodes.every((fieldNode) => { const fieldName = fieldNode.name.value; const fieldSelectionSet = fieldSelectionSetsMap[fieldName]; return fieldSelectionSet == null || subschemaTypesContainSelectionSet( mergedTypeInfo, sourceSubschemas, fieldSelectionSet ); })) { proxiableSubschemas.push(t); } else { nonProxiableSubschemas.push(t); } } } const unproxiableFieldNodes = []; const delegationMap = /* @__PURE__ */ new Map(); for (const fieldNode of fieldNodes) { const fieldName = fieldNode.name.value; if (fieldName === "__typename") { continue; } const sourcesWithUnsatisfiedDependencies = sourceSubschemas.filter( (s) => fieldSelectionSets.get(s) != null && fieldSelectionSets.get(s)[fieldName] != null && !subschemaTypesContainSelectionSet( mergedTypeInfo, sourceSubschemas, fieldSelectionSets.get(s)[fieldName] ) ); if (sourcesWithUnsatisfiedDependencies.length === sourceSubschemas.length) { unproxiableFieldNodes.push(fieldNode); for (const source of sourcesWithUnsatisfiedDependencies) { if (!nonProxiableSubschemas.includes(source)) { nonProxiableSubschemas.push(source); } } continue; } const uniqueSubschema = uniqueFields[fieldName]; if (uniqueSubschema != null) { if (!proxiableSubschemas.includes(uniqueSubschema)) { unproxiableFieldNodes.push(fieldNode); continue; } const existingSubschema2 = delegationMap.get(uniqueSubschema)?.selections; if (existingSubschema2 != null) { existingSubschema2.push(fieldNode); } else { delegationMap.set(uniqueSubschema, { kind: graphql.Kind.SELECTION_SET, selections: [fieldNode] }); } continue; } let nonUniqueSubschemas = nonUniqueFields[fieldNode.name.value]; if (nonUniqueSubschemas == null) { unproxiableFieldNodes.push(fieldNode); continue; } nonUniqueSubschemas = nonUniqueSubschemas.filter( (s) => proxiableSubschemas.includes(s) ); if (!nonUniqueSubschemas.length) { unproxiableFieldNodes.push(fieldNode); continue; } const existingSubschema = nonUniqueSubschemas.find( (s) => delegationMap.has(s) ); if (existingSubschema != null) { delegationMap.get(existingSubschema).selections.push(fieldNode); } else { let bestUniqueSubschema = nonUniqueSubschemas[0]; let bestScore = Infinity; for (const nonUniqueSubschema of nonUniqueSubschemas) { const typeInSubschema = nonUniqueSubschema.transformedSchema.getType( mergedTypeInfo.typeName ); const fields = typeInSubschema.getFields(); const field = fields[fieldNode.name.value]; if (field != null) { const unavailableFields = delegate.extractUnavailableFields( nonUniqueSubschema.transformedSchema, field, fieldNode, (fieldType) => { if (!nonUniqueSubschema.merge?.[fieldType.name]) { let nonUniqueSubschemaSelections = ( // We have to cast it to `SelectionNode[]` because it is Readonly<SelectionNode[]> and it doesn't allow us to push new elements. delegationMap.get(nonUniqueSubschema)?.selections ); if (nonUniqueSubschemaSelections == null) { nonUniqueSubschemaSelections = []; delegationMap.set(nonUniqueSubschema, { kind: graphql.Kind.SELECTION_SET, selections: nonUniqueSubschemaSelections }); } nonUniqueSubschemaSelections.push(fieldNode); return false; } return true; } ); const currentScore = calculateSelectionScore( unavailableFields, fragments ); if (currentScore < bestScore) { bestScore = currentScore; bestUniqueSubschema = nonUniqueSubschema; } } } delegationMap.set(bestUniqueSubschema, { kind: graphql.Kind.SELECTION_SET, selections: [fieldNode] }); } } if (delegationMap.size > 1) { optimizeDelegationMap(delegationMap, mergedTypeInfo.typeName, fragments); } return { delegationMap, proxiableSubschemas, nonProxiableSubschemas, unproxiableFieldNodes }; } const calculateSelectionScore = utils.memoize2( function calculateSelectionScore2(selections, fragments) { let score = 0; for (const selectionNode of selections) { switch (selectionNode.kind) { case graphql.Kind.FIELD: score++; if (selectionNode.selectionSet?.selections) { score += calculateSelectionScore2( selectionNode.selectionSet.selections, fragments ); } break; case graphql.Kind.INLINE_FRAGMENT: score += calculateSelectionScore2( selectionNode.selectionSet.selections, fragments ); break; case graphql.Kind.FRAGMENT_SPREAD: const fragment = fragments?.[selectionNode.name.value]; if (fragment) { score += calculateSelectionScore2( fragment.selectionSet.selections, fragments ); } break; } } return score; } ); function getStitchingInfo(schema) { const stitchingInfo = schema.extensions?.["stitchingInfo"]; if (!stitchingInfo) { throw new Error(`Schema is not a stitched schema.`); } return stitchingInfo; } function createDelegationPlanBuilder(mergedTypeInfo) { mergedTypeInfo.nonMemoizedDelegationPlanBuilder = function delegationPlanBuilder(schema, sourceSubschema, variableValues, fragments, fieldNodes) { const stitchingInfo = getStitchingInfo(schema); const targetSubschemas = mergedTypeInfo?.targetSubschemas.get(sourceSubschema); if (!targetSubschemas || !targetSubschemas.length) { return []; } const typeName = mergedTypeInfo.typeName; const typeInSubschema = sourceSubschema.transformedSchema.getType( typeName ); const fieldsNotInSubschema = getFieldsNotInSubschema( schema, stitchingInfo, schema.getType(typeName), mergedTypeInfo.typeMaps.get(sourceSubschema)?.[typeName], fieldNodes, fragments, variableValues, sourceSubschema ); if (!fieldsNotInSubschema.length) { return []; } const delegationMaps = []; let sourceSubschemas = createSubschemas(sourceSubschema); let delegationStage = calculateDelegationStage( mergedTypeInfo, sourceSubschemas, targetSubschemas, fieldsNotInSubschema, fragments ); let { delegationMap } = delegationStage; while (delegationMap.size) { delegationMaps.push(delegationMap); const { proxiableSubschemas, nonProxiableSubschemas, unproxiableFieldNodes } = delegationStage; sourceSubschemas = combineSubschemas( sourceSubschemas, proxiableSubschemas ); delegationStage = calculateDelegationStage( mergedTypeInfo, sourceSubschemas, nonProxiableSubschemas, unproxiableFieldNodes, fragments ); delegationMap = delegationStage.delegationMap; } if (graphql.isAbstractType(typeInSubschema) && fieldsNotInSubschema.some( (fieldNode) => fieldNode.name.value === "__typename" )) { const inlineFragments = []; for (const fieldNode of fieldNodes) { if (fieldNode.selectionSet) { for (const selection of fieldNode.selectionSet.selections) { if (selection.kind === graphql.Kind.INLINE_FRAGMENT) { inlineFragments.push(selection); } } } } const implementedSubschemas = targetSubschemas.filter((subschema) => { const typeInTargetSubschema = mergedTypeInfo.typeMaps.get(subschema)?.[typeName]; return graphql.isAbstractType(typeInTargetSubschema) && subschema.transformedSchema.getPossibleTypes(typeInTargetSubschema).length; }); let added = false; for (const implementedSubgraphs of implementedSubschemas) { for (const delegationMap2 of delegationMaps) { const existingSelections = delegationMap2.get(implementedSubgraphs)?.selections; if (existingSelections) { existingSelections.push({ kind: graphql.Kind.FIELD, name: { kind: graphql.Kind.NAME, value: "__typename" } }); existingSelections.push(...inlineFragments); added = true; break; } if (added) { break; } } } if (!added) { const subschemaWithTypeName = implementedSubschemas[0]; if (subschemaWithTypeName) { const delegationStageToFetchTypeName = /* @__PURE__ */ new Map(); delegationStageToFetchTypeName.set(subschemaWithTypeName, { kind: graphql.Kind.SELECTION_SET, selections: [ { kind: graphql.Kind.FIELD, name: { kind: graphql.Kind.NAME, value: "__typename" } }, ...inlineFragments ] }); delegationMaps.push(delegationStageToFetchTypeName); } } } if (delegationStage.unproxiableFieldNodes.length && delegationStage.nonProxiableSubschemas.length) { delegate.leftOverByDelegationPlan.set(delegationMaps, { unproxiableFieldNodes: delegationStage.unproxiableFieldNodes, nonProxiableSubschemas: delegationStage.nonProxiableSubschemas, missingFieldsParentMap: /* @__PURE__ */ new Map(), missingFieldsParentDeferredMap: /* @__PURE__ */ new Map() }); } return delegationMaps; }; return memoize5of7(function wrappedDelegationPlanBuilder(schema, sourceSubschema, variableValues, fragments, fieldNodes, context, info) { return mergedTypeInfo.nonMemoizedDelegationPlanBuilder( schema, sourceSubschema, variableValues, fragments, fieldNodes, context, info ); }); } function optimizeDelegationMap(delegationMap, typeName, fragments) { for (const [subschema, selectionSet] of delegationMap) { for (const [subschema2, selectionSet2] of delegationMap) { if (subschema === subschema2) { continue; } const unavailableFields = delegate.extractUnavailableFieldsFromSelectionSet( subschema2.transformedSchema, // Unfortunately, getType returns GraphQLNamedType, but we already know the type is a GraphQLObjectType, so we can cast it. subschema2.transformedSchema.getType(typeName), selectionSet, () => true, fragments ); if (!unavailableFields.length) { delegationMap.set(subschema2, { kind: graphql.Kind.SELECTION_SET, selections: [...selectionSet2.selections, ...selectionSet.selections] }); delegationMap.delete(subschema); } } } return delegationMap; } const createSubschemas = utils.memoize1(function createSubschemas2(sourceSubschema) { return [sourceSubschema]; }); const combineSubschemas = utils.memoize2(function combineSubschemas2(sourceSubschemas, additionalSubschemas) { return sourceSubschemas.concat(additionalSubschemas); }); const subschemaTypesContainSelectionSet = utils.memoize3( function subschemaTypesContainSelectionSet2(mergedTypeInfo, sourceSubchemas, selectionSet) { return typesContainSelectionSet( sourceSubchemas.map( (sourceSubschema) => sourceSubschema.transformedSchema.getType( mergedTypeInfo.typeName ) ), selectionSet ); } ); function typesContainSelectionSet(types, selectionSet) { const fieldMaps = types.map((type) => type.getFields()); for (const selection of selectionSet.selections) { if (selection.kind === graphql.Kind.FIELD) { const fields = fieldMaps.map((fieldMap) => fieldMap[selection.name.value]).filter((field) => field != null); if (!fields.length) { return false; } if (selection.selectionSet != null) { return typesContainSelectionSet( fields.map( (field) => graphql.getNamedType(field.type) ), selection.selectionSet ); } } else if (selection.kind === graphql.Kind.INLINE_FRAGMENT && selection.typeCondition?.name.value === types[0]?.name) { return typesContainSelectionSet(types, selection.selectionSet); } } return true; } function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) { const { fieldName, argsFromKeys, valuesFromResults, args } = mergedTypeResolverOptions; function getType(info) { if (!mergedType) { return graphql.getNamedType(info.returnType); } if (typeof mergedType === "string") { return info.schema.getType(mergedType); } return mergedType; } if (argsFromKeys != null) { return function mergedBatchedTypeResolver(_originalResult, context, info, subschema, selectionSet, key, type = getType(info)) { return batchDelegate.batchDelegateToSchema({ schema: subschema, operation: "query", fieldName, returnType: new graphql.GraphQLList(type), key, argsFromKeys, valuesFromResults, selectionSet, context, info, skipTypeMerging: true, dataLoaderOptions: mergedTypeResolverOptions.dataLoaderOptions }); }; } if (args != null) { return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet, _key, type = getType(info)) { return delegate.delegateToSchema({ schema: subschema, operation: "query", fieldName, returnType: type, args: args(originalResult), selectionSet, context, info, skipTypeMerging: true }); }; } return void 0; } function createStitchingInfo(subschemaMap, typeCandidates, mergeTypes) { const mergedTypes = createMergedTypes(typeCandidates, mergeTypes); return { subschemaMap, fieldNodesByType: /* @__PURE__ */ Object.create(null), fieldNodesByField: /* @__PURE__ */ Object.create(null), dynamicSelectionSetsByField: /* @__PURE__ */ Object.create(null), mergedTypes }; } function createMergedTypes(typeCandidates, mergeTypes) { const mergedTypes = /* @__PURE__ */ Object.create( null ); const typeInterfacesMap = /* @__PURE__ */ Object.create(null); for (const typeName in typeCandidates) { if (typeCandidates[typeName]) { for (const { type } of typeCandidates[typeName]) { if ("getInterfaces" in type) { const interfaces = type.getInterfaces(); for (const iface of interfaces) { const interfaceName = iface.name; let implementingTypes = typeInterfacesMap[typeName]; if (implementingTypes == null) { implementingTypes = /* @__PURE__ */ new Set(); typeInterfacesMap[typeName] = implementingTypes; } implementingTypes.add(interfaceName); } } } } } for (const typeName in typeCandidates) { const typeCandidatesOfTypeName = typeCandidates[typeName]; if (!typeCandidatesOfTypeName) { throw new Error(`Invalid type candidates for type name ${typeName}`); } const typeCandidate = typeCandidatesOfTypeName[0]; if (graphql.isObjectType(typeCandidate?.type) || graphql.isInterfaceType(typeCandidate?.type)) { const typeCandidatesWithMergedTypeConfig = typeCandidatesOfTypeName.filter( (typeCandidate2) => typeCandidate2.transformedSubschema != null && typeCandidate2.transformedSubschema.merge != null && typeName in typeCandidate2.transformedSubschema.merge ); if (mergeTypes === true || typeof mergeTypes === "function" && mergeTypes(typeCandidatesOfTypeName, typeName) || Array.isArray(mergeTypes) && mergeTypes.includes(typeName) || typeCandidatesWithMergedTypeConfig.length) { const targetSubschemas = []; const typeMaps = /* @__PURE__ */ new Map(); const supportedBySubschemas = /* @__PURE__ */ Object.create({}); const selectionSets = /* @__PURE__ */ new Map(); const fieldSelectionSets = /* @__PURE__ */ new Map(); const resolvers = /* @__PURE__ */ new Map(); const providedSelectionsByField = /* @__PURE__ */ new Map(); for (const typeCandidate2 of typeCandidatesOfTypeName) { const subschema = typeCandidate2.transformedSubschema; if (subschema == null) { continue; } typeMaps.set(subschema, subschema.transformedSchema.getTypeMap()); let mergedTypeConfig2 = subschema?.merge?.[typeName]; if (!mergedTypeConfig2) { for (const interfaceName of typeInterfacesMap[typeName] ?? []) { mergedTypeConfig2 = subschema?.merge?.[interfaceName]; if (mergedTypeConfig2) { break; } } } if (mergedTypeConfig2 == null) { continue; } if (mergedTypeConfig2.selectionSet) { const selectionSet2 = utils.parseSelectionSet( mergedTypeConfig2.selectionSet, { noLocation: true } ); selectionSets.set(subschema, selectionSet2); } if (mergedTypeConfig2.fields) { const parsedFieldSelectionSets = /* @__PURE__ */ Object.create(null); for (const fieldName in mergedTypeConfig2.fields) { if (mergedTypeConfig2.fields[fieldName]?.selectionSet) { const rawFieldSelectionSet = mergedTypeConfig2.fields[fieldName].selectionSet; parsedFieldSelectionSets[fieldName] = rawFieldSelectionSet ? utils.parseSelectionSet(rawFieldSelectionSet, { noLocation: true }) : void 0; } if (mergedTypeConfig2.fields[fieldName]?.provides) { let providedSelectionsForSubschema = providedSelectionsByField.get(subschema); if (providedSelectionsForSubschema == null) { providedSelectionsForSubschema = /* @__PURE__ */ Object.create({}); providedSelectionsByField.set( subschema, providedSelectionsForSubschema ); } providedSelectionsForSubschema[fieldName] = mergedTypeConfig2.fields[fieldName].provides; } } fieldSelectionSets.set(subschema, parsedFieldSelectionSets); } const type = subschema.transformedSchema.getType(typeName); const resolver = mergedTypeConfig2.resolve ?? createMergedTypeResolver(mergedTypeConfig2, type); if (resolver == null) { continue; } const keyFn = mergedTypeConfig2.key; resolvers.set( subschema, keyFn ? function batchMergedTypeResolverWrapper(originalResult, context, info, subschema2, selectionSet2, type2) { return promiseHelpers.handleMaybePromise( () => keyFn(originalResult), (key) => resolver( originalResult, context, info, subschema2, selectionSet2, key, type2 ) ); } : resolver ); targetSubschemas.push(subschema); const fieldMap = type.getFields(); const selectionSet = selectionSets.get(subschema); for (const fieldName in fieldMap) { const field = fieldMap[fieldName]; const fieldType = graphql.getNamedType(field?.type); if (selectionSet && graphql.isLeafType(fieldType) && selectionSetContainsTopLevelField(selectionSet, fieldName)) { continue; } if (!supportedBySubschemas[fieldName]) { supportedBySubschemas[fieldName] = []; } supportedBySubschemas[fieldName]?.push(subschema); } } const sourceSubschemas = typeCandidates[typeName]?.map((typeCandidate2) => typeCandidate2?.transformedSubschema).filter(utils.isSome); const targetSubschemasBySubschema = /* @__PURE__ */ new Map(); if (sourceSubschemas) { for (const subschema of sourceSubschemas) { const filteredSubschemas = targetSubschemas.filter( (s) => s !== subschema ); if (filteredSubschemas.length) { targetSubschemasBySubschema.set(subschema, filteredSubschemas); } } } const mergedTypeConfig = { typeName, targetSubschemas: targetSubschemasBySubschema, typeMaps, selectionSets, fieldSelectionSets, uniqueFields: /* @__PURE__ */ Object.create({}), nonUniqueFields: /* @__PURE__ */ Object.create({}), resolvers, providedSelectionsByField }; mergedTypes[typeName] = mergedTypeConfig; mergedTypeConfig.delegationPlanBuilder = createDelegationPlanBuilder( mergedTypeConfig ); for (const fieldName in supportedBySubschemas) { if (supportedBySubschemas[fieldName]?.length === 1 && supportedBySubschemas[fieldName][0]) { mergedTypeConfig.uniqueFields[fieldName] = supportedBySubschemas[fieldName][0]; } else if (supportedBySubschemas[fieldName]) { mergedTypeConfig.nonUniqueFields[fieldName] = supportedBySubschemas[fieldName]; } } } } } return mergedTypes; } function completeStitchingInfo(stitchingInfo, resolvers, schema) { const { fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField, mergedTypes } = stitchingInfo; const rootTypes = [schema.getQueryType(), schema.getMutationType()]; for (const rootType of rootTypes) { if (rootType) { fieldNodesByType[rootType.name] = [ utils.parseSelectionSet("{ __typename }", { noLocation: true }).selections[0] ]; } } const selectionSetsByField = /* @__PURE__ */ Object.create(null); for (const typeName in mergedTypes) { const mergedTypeInfo = mergedTypes[typeName]; if (mergedTypeInfo?.selectionSets == null && mergedTypeInfo?.fieldSelectionSets == null) { continue; } for (const [ subschemaConfig, selectionSet ] of mergedTypeInfo.selectionSets) { const schema2 = subschemaConfig.transformedSchema; const type = schema2.getType(typeName); const fields = type.getFields(); for (const fieldName in fields) { const field = fields[fieldName]; const fieldType = graphql.getNamedType(field?.type); if (selectionSet && graphql.isLeafType(fieldType) && selectionSetContainsTopLevelField(selectionSet, fieldName)) { continue; } updateSelectionSetMap( selectionSetsByField, typeName, fieldName, selectionSet, true ); } if (graphql.isAbstractType(type)) { updateSelectionSetMap( selectionSetsByField, typeName, "__typename", selectionSet ); } } for (const [, selectionSetFieldMap] of mergedTypeInfo.fieldSelectionSets) { for (const fieldName in selectionSetFieldMap) { const selectionSet = selectionSetFieldMap[fieldName]; if (selectionSet) { updateSelectionSetMap( selectionSetsByField, typeName, fieldName, selectionSet, true ); } } } } for (const typeName in resolvers) { const type = schema.getType(typeName); if (type === void 0 || graphql.isLeafType(type) || graphql.isInputObjectType(type) || graphql.isUnionType(type)) { continue; } const resolver = resolvers[typeName]; for (const fieldName in resolver) { const field = resolver[fieldName]; if (typeof field.selectionSet === "function") { if (!dynamicSelectionSetsByField[typeName]) { dynamicSelectionSetsByField[typeName] = /* @__PURE__ */ Object.create(null); } if (!dynamicSelectionSetsByField[typeName][fieldName]) { dynamicSelectionSetsByField[typeName][fieldName] = []; } dynamicSelectionSetsByField[typeName][fieldName].push( field.selectionSet ); } else if (field.selectionSet) { const selectionSet = utils.parseSelectionSet(field.selectionSet, { noLocation: true }); updateSelectionSetMap( selectionSetsByField, typeName, fieldName, selectionSet ); } } } const variableValues = /* @__PURE__ */ Object.create(null); const fragments = /* @__PURE__ */ Object.create(null); const fieldNodeMap = /* @__PURE__ */ Object.create(null); for (const typeName in selectionSetsByField) { const type = schema.getType(typeName); for (const fieldName in selectionSetsByField[typeName]) { for (const selectionSet of selectionSetsByField[typeName][fieldName]) { const { fields } = utils.collectFields( schema, fragments, variableValues, type, selectionSet ); for (const [, fieldNodes] of fields) { for (const fieldNode of fieldNodes) { const key = graphql.print(fieldNode); if (fieldNodeMap[key] == null) { fieldNodeMap[key] = fieldNode; updateArrayMap(fieldNodesByField, typeName, fieldName, fieldNode); } else { updateArrayMap( fieldNodesByField, typeName, fieldName, fieldNodeMap[key] ); } } } } } } return stitchingInfo; } function updateSelectionSetMap(map, typeName, fieldName, selectionSet, includeTypename) { if (includeTypename) { const typenameSelectionSet = utils.parseSelectionSet("{ __typename }", { noLocation: true }); updateArrayMap( map, typeName, fieldName, selectionSet, typenameSelectionSet ); return; } updateArrayMap(map, typeName, fieldName, selectionSet); } function updateArrayMap(map, typeName, fieldName, value, initialValue) { if (map[typeName] == null) { const initialItems = initialValue === void 0 ? [value] : [initialValue, value]; map[typeName] = { [fieldName]: initialItems }; } else if (map[typeName][fieldName] == null) { const initialItems = initialValue === void 0 ? [value] : [initialValue, value]; map[typeName][fieldName] = initialItems; } else { map[typeName][fieldName].push(value); } } function addStitchingInfo(stitchedSchema, stitchingInfo) { stitchedSchema.extensions = { ...stitchedSchema.extensions, stitchingInfo }; } function selectionSetContainsTopLevelField(selectionSet, fieldName) { return selectionSet.selections.some( (selection) => selection.kind === graphql.Kind.FIELD && selection.name.value === fieldName ); } function isolateComputedFieldsTransformer(subschemaConfig) { if (subschemaConfig.merge == null) { return [subschemaConfig]; } const baseSchemaTypes = /* @__PURE__ */ Object.create(null); const isolatedSchemaTypes = /* @__PURE__ */ Object.create(null); for (const typeName in subschemaConfig.merge) { const mergedTypeConfig = subschemaConfig.merge[typeName]; const objectType = subschemaConfig.schema.getType( typeName ); baseSchemaTypes[typeName] = mergedTypeConfig; if (mergedTypeConfig.fields) { const baseFields = /* @__PURE__ */ Object.create(null); const isolatedFields = /* @__PURE__ */ Object.create(null); for (const fieldName in mergedTypeConfig.fields) { const mergedFieldConfig = mergedTypeConfig.fields[fieldName]; if (mergedFieldConfig?.computed && mergedFieldConfig?.selectionSet) { isolatedFields[fieldName] = mergedFieldConfig; } else if (mergedFieldConfig?.computed) { throw new Error( `A selectionSet is required for computed field "${typeName}.${fieldName}"` ); } else { baseFields[fieldName] = mergedFieldConfig; } } const isolatedFieldCount = Object.keys(isolatedFields).length; if (isolatedFieldCount && isolatedFieldCount !== Object.keys(objectType.getFields()).length) { baseSchemaTypes[typeName] = { ...mergedTypeConfig, fields: baseFields }; const keyFieldNames = isolatedSchemaTypes[typeName]?.keyFieldNames ?? []; if (keyFieldNames.length === 0) { if (mergedTypeConfig.selectionSet) { const parsedSelectionSet = utils.parseSelectionSet( mergedTypeConfig.selectionSet, { noLocation: true } ); const keyFields = utils.collectFields( subschemaConfig.schema, {}, {}, objectType, parsedSelectionSet ); keyFieldNames.push(...Array.from(keyFields.fields.keys())); } for (const entryPoint of mergedTypeConfig.entryPoints ?? []) { if (entryPoint.selectionSet) { const parsedSelectionSet = utils.parseSelectionSet( entryPoint.selectionSet, { noLocation: true } ); const keyFields = utils.collectFields( subschemaConfig.schema, {}, {}, objectType, parsedSelectionSet ); keyFieldNames.push(...Array.from(keyFields.fields.keys())); } } } isolatedSchemaTypes[typeName] = { ...mergedTypeConfig, // there might already be key fields keyFieldNames, fields: { ...isolatedSchemaTypes[typeName]?.fields ?? {}, ...isolatedFields }, canonical: void 0 }; for (const fieldName in isolatedFields) { const returnType = graphql.getNamedType( objectType.getFields()[fieldName]?.type ); const returnTypes = [returnType]; if (graphql.isInterfaceType(returnType)) { returnTypes.push( ...utils.getImplementingTypes( returnType.name, subschemaConfig.schema ).map( (name) => subschemaConfig.schema.getType( name ) ) ); } else if (graphql.isUnionType(returnType)) { returnTypes.push(...returnType.getTypes()); } for (const type of returnTypes) { const returnTypeMergeConfig = subschemaConfig.merge[type.name]; if (Object.values(subschemaConfig.schema.getTypeMap()).filter(graphql.isObjectType).filter((t) => t !== type).filter((t) => !isolatedSchemaTypes[t.name]).find( (t) => Object.values(t.getFields()).find( (f) => graphql.getNamedType(f.type) === type ) )) { continue; } if (graphql.isObjectType(type)) { const returnTypeSelectionSet = returnTypeMergeConfig?.selectionSet; if (returnTypeSelectionSet) { const keyFieldNames2 = []; const parsedSelectionSet = utils.parseSelectionSet( returnTypeSelectionSet, { noLocation: true } ); const keyFields = utils.collectFields( subschemaConfig.schema, {}, {}, type, parsedSelectionSet ); keyFieldNames2.push(...Array.from(keyFields.fields.keys())); for (const entryPoint of returnTypeMergeConfig.entryPoints ?? []) { if (entryPoint.selectionSet) { const parsedSelectionSet2 = utils.parseSelectionSet( entryPoint.selectionSet, { noLocation: true } ); const keyFields2 = utils.collectFields( subschemaConfig.schema, {}, {}, type, parsedSelectionSet2 ); keyFieldNames2.push(...Array.from(keyFields2.fields.keys())); } } isolatedSchemaTypes[type.name] = { ...returnTypeMergeConfig, keyFieldNames: keyFieldNames2, fields: { ...isolatedSchemaTypes[type.name]?.fields ?? {} } }; } else if (!returnTypeMergeConfig) { const fields = { ...isolatedSchemaTypes[type.name]?.fields }; if (graphql.isAbstractType(type)) { for (const implementingType of utils.getImplementingTypes( type.name, subschemaConfig.schema )) { const implementingTypeFields = isolatedSchemaTypes[implementingType]?.fields; if (implementingTypeFields) { for (const fieldName2 in implementingTypeFields) { if (implementingTypeFields[fieldName2]) { fields[fieldName2] = { ...implementingTypeFields[fieldName2], ...fields[fieldName2] }; } } } } } if (graphql.isInterfaceType(type) || graphql.isObjectType(type)) { for (const fieldName2 in type.getFields()) { fields[fieldName2] ||= {}; } } isolatedSchemaTypes[type.name] = { keyFieldNames: [], fields, canonical: true }; } } } } } } } if (Object.keys(isolatedSchemaTypes).length) { return [ filterIsolatedSubschema(subschemaConfig, isolatedSchemaTypes), filterBaseSubschema( { ...subschemaConfig, merge: baseSchemaTypes }, isolatedSchemaTypes ) ]; } return [subschemaConfig]; } function _createCompositeFieldFilter(schema) { const filteredFields = {}; for (const typeName in schema.getTypeMap()) { const type = schema.getType(typeName); if (graphql.isObjectType(type) || graphql.isInterfaceType(type)) { const filteredFieldsOfType = { __typename: true }; let hasField = false; const fieldMap = type.getFields(); for (const fieldName in fieldMap) { filteredFieldsOfType[fieldName] = true; hasField = true; } if (hasField) { filteredFields[typeName] = filteredFieldsOfType; } } } return new wrap.TransformCompositeFields( (typeName, fieldName) => filteredFields[typeName]?.[fieldName] ? void 0 : null, (typeName, fieldName) => filteredFields[typeName]?.[fieldName] ? void 0 : null ); } function isIsolatedField(typeName, fieldName, isolatedSchemaTypes) { const fieldConfig = isolatedSchemaTypes[typeName]?.fields?.[fieldName]; if (fieldConfig) { return true; } return false; } function filterBaseSubschema(subschemaConfig, isolatedSchemaTypes) { const schema = subschemaConfig.schema; const typesForInterface = {}; const iFacesForTypes = {}; const filteredSchema = utils.filterSchema({ schema, objectFieldFilter: (typeName, fieldName) => { const iFacesForType = iFacesForTypes[typeName] ||= []; if (!iFacesForType) { let addIface2 = function(iFace) { if (!iFacesForType.includes(iFace.name)) { iFacesForType.push(iFace.name); iFace.getInterfaces().forEach(addIface2); } }; const type = schema.getType(typeName); let iFaces = type.getInterfaces(); for (const iface of iFaces) { addIface2(iface); } } const allTypes = [typeName, ...iFacesForType]; const isIsolatedFieldName = allTypes.every( (implementingTypeName) => isIsolatedField(implementingTypeName, fieldName, isolatedSchemaTypes) ); const isKeyFieldName = allTypes.some( (implementingTypeName) => (isolatedSchemaTypes[implementingTypeName]?.keyFieldNames ?? []).includes(fieldName) ); return !isIsolatedFieldName || isKeyFieldName; }, interfaceFieldFilter: (typeName, fieldName) => { if (!typesForInterface[typeName]) { typesForInterface[typeName] = utils.getImplementingTypes(typeName, schema); } const iFacesForType = iFacesForTypes[typeName] ||= []; if (!iFacesForType) { let addIface2 = function(iFace) { if (!iFacesForType.includes(iFace.name)) { iFacesForType.push(iFace.name); iFace.getInterfaces().forEach(addIface2); } }; const type = schema.getType(typeName); let iFaces = type.getInterfaces(); for (const iface of iFaces) { addIface2(iface); } } const allTypes = [ typeName, ...iFacesForType, ...typesForInterface[typeName] ]; const isIsolatedFieldName = allTypes.every( (implementingTypeName) => isIsolatedField(implementingTypeName, fieldName, isolatedSchemaTypes) ); const isKeyFieldName = allTypes.some( (implementingTypeName) => (isolatedSchemaTypes[implementingTypeName]?.keyFieldNames ?? []).includes(fieldName) ); return !isIsolatedFieldName || isKeyFieldName; } }); const filteredSubschema = { ...subschemaConfig, merge: subschemaConfig.merge ? { ...subschemaConfig.merge } : void 0, transforms: (subschemaConfig.transforms ?? []).concat([ _createCompositeFieldFilter(filteredSchema), new wrap.FilterTypes((type) => { const typeName = type.name; const typeInFiltered = filteredSchema.getType(typeName); if (!typeInFiltered) { return false; } if (graphql.isObjectType(type) || graphql.isInterfaceType(type)) { return Object.keys(type.getFields()).length > 0; } return true; }) ]) }; const remainingTypes = filteredSchema.getTypeMap(); const mergeConfig = filteredSubschema.merge; if (mergeConfig) { for (const mergeType in mergeConfig) { if (!remainingTypes[mergeType]) { delete mergeConfig[mergeType]; } } if (!Object.keys(mergeConfig).length) { delete filteredSubschema.merge; } } return filteredSubschema; } function filterIsolatedSubschema(subschemaConfig, isolatedSchemaTypes) { const computedFieldTypes = {}; const queryRootFields = {}; function listReachableTypesToIsolate(subschemaConfig2, type, typeNames = /* @__PURE__ */ new Set()) { if (graphql.isScalarType(type)) { return typeNames; } else if ((graphql.isObjectType(type) || graphql.isInterfaceType(type)) && subschemaConfig2.merge?.[type.name]) { typeNames.add(type.name); return typeNames; } else if (graphql.isCompositeType(type)) { typeNames.add(type.name); const types = /* @__PURE__ */ new Set(); if (graphql.isObjectType(type)) { types.add(type); } else if (graphql.isInterfaceType(type)) { utils.getImplementingTypes(type.name, subschemaConfig2.schema).forEach( (name) => types.add( subschemaConfig2.schema.getType(name) ) ); } else if (graphql.isUnionType(type)) { type.getTypes().forEach((t) => types.add(t)); } for (const type2 of types) { typeNames.add(type2.name); for (const f of Object.values(type2.getFields())) { const fieldType = graphql.getNamedType(f.type); if (!typeNames.has(fieldType.name) && graphql.isCompositeType(fieldType)) { listReachableTypesToIsolate(subschemaConfig2, fieldType, typeNames); } } } return typeNames; } else if (graphql.isUnionType(type)) { typeNames.add(type.name); type.getTypes().forEach( (t) => listReachableTypesToIsolate(subschemaConfig2, t, typeNames) ); return typeNames; } else { return typeNames; } } const queryType = subschemaConfig.schema.getQueryType(); for (const typeName in subschemaConfig.merge) { const mergedTypeConfig = sub