UNPKG

@graphql-tools/stitch

Version:

A set of utils for faster development of GraphQL tools

313 lines (312 loc) • 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isolateComputedFieldsTransformer = void 0; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const wrap_1 = require("@graphql-tools/wrap"); function isolateComputedFieldsTransformer(subschemaConfig) { if (subschemaConfig.merge == null) { return [subschemaConfig]; } const baseSchemaTypes = Object.create(null); const isolatedSchemaTypes = 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 = Object.create(null); const isolatedFields = 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, }; isolatedSchemaTypes[typeName] = { ...mergedTypeConfig, // there might already be key fields keyFieldNames: isolatedSchemaTypes[typeName]?.keyFieldNames || [], fields: { ...(isolatedSchemaTypes[typeName]?.fields ?? {}), ...isolatedFields }, canonical: undefined, }; for (const fieldName in isolatedFields) { const returnType = (0, graphql_1.getNamedType)(objectType.getFields()[fieldName].type); const returnTypes = [returnType]; // for interfaces and unions the implementations/members need to be handled as well if ((0, graphql_1.isInterfaceType)(returnType)) { returnTypes.push(...(0, utils_1.getImplementingTypes)(returnType.name, subschemaConfig.schema).map(name => subschemaConfig.schema.getType(name))); } else if ((0, graphql_1.isUnionType)(returnType)) { returnTypes.push(...returnType.getTypes()); } for (const type of returnTypes) { const returnTypeMergeConfig = subschemaConfig.merge[type.name]; if ((0, graphql_1.isObjectType)(type)) { const returnTypeSelectionSet = returnTypeMergeConfig?.selectionSet; if (returnTypeSelectionSet) { // this is a merged type, include the selection set const keyFieldNames = []; const parsedSelectionSet = (0, utils_1.parseSelectionSet)(returnTypeSelectionSet); const keyFields = (0, utils_1.collectFields)(subschemaConfig.schema, {}, {}, type, parsedSelectionSet); keyFieldNames.push(...Array.from(keyFields.fields.keys())); for (const entryPoint of returnTypeMergeConfig.entryPoints ?? []) { if (entryPoint.selectionSet) { const parsedSelectionSet = (0, utils_1.parseSelectionSet)(entryPoint.selectionSet); const keyFields = (0, utils_1.collectFields)(subschemaConfig.schema, {}, {}, type, parsedSelectionSet); keyFieldNames.push(...Array.from(keyFields.fields.keys())); } } isolatedSchemaTypes[type.name] = { ...returnTypeMergeConfig, keyFieldNames, fields: { ...(isolatedSchemaTypes[type.name]?.fields ?? {}), }, }; } else if (!returnTypeMergeConfig) { // this is an unmerged type, add all fields to the isolated schema const fields = { ...isolatedSchemaTypes[type.name]?.fields, }; if ((0, graphql_1.isAbstractType)(type)) { for (const implementingType of (0, utils_1.getImplementingTypes)(type.name, subschemaConfig.schema)) { const implementingTypeFields = isolatedSchemaTypes[implementingType]?.fields; if (implementingTypeFields) { for (const fieldName in implementingTypeFields) { if (implementingTypeFields[fieldName]) { fields[fieldName] = { ...implementingTypeFields[fieldName], ...fields[fieldName], }; } } } } } if ((0, graphql_1.isInterfaceType)(type) || (0, graphql_1.isObjectType)(type)) { for (const fieldName in type.getFields()) { if (!fields[fieldName]) { fields[fieldName] = {}; } } } isolatedSchemaTypes[type.name] = { keyFieldNames: [], fields, canonical: true, }; } } } } } } } if (Object.keys(isolatedSchemaTypes).length) { return [ filterBaseSubschema({ ...subschemaConfig, merge: baseSchemaTypes }, isolatedSchemaTypes), filterIsolatedSubschema({ ...subschemaConfig, merge: isolatedSchemaTypes }), ]; } return [subschemaConfig]; } exports.isolateComputedFieldsTransformer = isolateComputedFieldsTransformer; function _createCompositeFieldFilter(schema) { // create TransformCompositeFields that will remove any field not in schema, const filteredFields = {}; for (const typeName in schema.getTypeMap()) { const type = schema.getType(typeName); if ((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type)) { filteredFields[typeName] = { __typename: true }; const fieldMap = type.getFields(); for (const fieldName in fieldMap) { filteredFields[typeName][fieldName] = true; } } } return new wrap_1.TransformCompositeFields((typeName, fieldName) => (filteredFields[typeName]?.[fieldName] ? undefined : null), (typeName, fieldName) => (filteredFields[typeName]?.[fieldName] ? undefined : 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 filteredSchema = (0, utils_1.pruneSchema)((0, utils_1.filterSchema)({ schema, objectFieldFilter: (typeName, fieldName) => !isIsolatedField(typeName, fieldName, isolatedSchemaTypes) || (isolatedSchemaTypes[typeName]?.keyFieldNames ?? []).includes(fieldName), interfaceFieldFilter: (typeName, fieldName) => { if (!typesForInterface[typeName]) { typesForInterface[typeName] = (0, utils_1.getImplementingTypes)(typeName, schema); } const isIsolatedFieldName = typesForInterface[typeName].some(implementingTypeName => isIsolatedField(implementingTypeName, fieldName, isolatedSchemaTypes)); return (!isIsolatedFieldName || (isolatedSchemaTypes[typeName]?.keyFieldNames ?? []).includes(fieldName)); }, })); const filteredSubschema = { ...subschemaConfig, merge: subschemaConfig.merge ? { ...subschemaConfig.merge, } : undefined, transforms: (subschemaConfig.transforms ?? []).concat([ _createCompositeFieldFilter(filteredSchema), new wrap_1.FilterTypes(// filter out empty types // filter out empty types type => (!(0, graphql_1.isObjectType)(type) && !(0, graphql_1.isInterfaceType)(type)) || Object.keys(type.getFields()).length > 0), ]), }; 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) { const rootFields = {}; const computedFieldTypes = {}; // contains types of computed fields that have no root field function listReachableTypesToIsolate(subschemaConfig, type, typeNames = new Set()) { if ((0, graphql_1.isScalarType)(type)) { return typeNames; } else if (((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type)) && subschemaConfig.merge && subschemaConfig.merge[type.name] && subschemaConfig.merge[type.name].selectionSet) { // this is a merged type, no need to descend further typeNames.add(type.name); return typeNames; } else if ((0, graphql_1.isCompositeType)(type)) { typeNames.add(type.name); // descent into all field types potentially via interfaces implementations/unions members const types = new Set(); if ((0, graphql_1.isObjectType)(type)) { types.add(type); } else if ((0, graphql_1.isInterfaceType)(type)) { (0, utils_1.getImplementingTypes)(type.name, subschemaConfig.schema).forEach(name => types.add(subschemaConfig.schema.getType(name))); } else if ((0, graphql_1.isUnionType)(type)) { type.getTypes().forEach(t => types.add(t)); } for (const type of types) { typeNames.add(type.name); for (const f of Object.values(type.getFields())) { const fieldType = (0, graphql_1.getNamedType)(f.type); if (!typeNames.has(fieldType.name) && (0, graphql_1.isCompositeType)(fieldType)) { listReachableTypesToIsolate(subschemaConfig, fieldType, typeNames); } } } return typeNames; } else if ((0, graphql_1.isUnionType)(type)) { typeNames.add(type.name); type.getTypes().forEach(t => listReachableTypesToIsolate(subschemaConfig, t, typeNames)); return typeNames; } else { return typeNames; } } for (const typeName in subschemaConfig.merge) { const mergedTypeConfig = subschemaConfig.merge[typeName]; const entryPoints = mergedTypeConfig.entryPoints ?? [mergedTypeConfig]; for (const entryPoint of entryPoints) { if (entryPoint.fieldName != null) { rootFields[entryPoint.fieldName] = true; if (computedFieldTypes[entryPoint.fieldName]) { delete computedFieldTypes[entryPoint.fieldName]; } } } const computedFields = [ ...Object.entries(mergedTypeConfig.fields || {}) .map(([k, v]) => (v.computed ? k : null)) .filter(fn => fn !== null), ].filter(fn => !rootFields[fn]); const type = subschemaConfig.schema.getType(typeName); for (const fieldName of computedFields) { const fieldType = (0, graphql_1.getNamedType)(type.getFields()[fieldName].type); computedFieldTypes[fieldType.name] = true; listReachableTypesToIsolate(subschemaConfig, fieldType).forEach(tn => { computedFieldTypes[tn] = true; }); } } const typesForInterface = {}; const filteredSchema = (0, utils_1.pruneSchema)((0, utils_1.filterSchema)({ schema: subschemaConfig.schema, rootFieldFilter: (_, fieldName, config) => { if (rootFields[fieldName]) { return true; } const returnType = (0, graphql_1.getNamedType)(config.type); if ((0, graphql_1.isAbstractType)(returnType)) { const typesForInterface = (0, utils_1.getImplementingTypes)(returnType.name, subschemaConfig.schema); return typesForInterface.some(t => computedFieldTypes[t] != null); } return computedFieldTypes[returnType.name] != null; }, objectFieldFilter: (typeName, fieldName) => subschemaConfig.merge[typeName] == null || subschemaConfig.merge[typeName]?.fields?.[fieldName] != null || (subschemaConfig.merge[typeName]?.keyFieldNames ?? []).includes(fieldName), interfaceFieldFilter: (typeName, fieldName) => { if (!typesForInterface[typeName]) { typesForInterface[typeName] = (0, utils_1.getImplementingTypes)(typeName, subschemaConfig.schema); } const isIsolatedFieldName = typesForInterface[typeName].some(implementingTypeName => isIsolatedField(implementingTypeName, fieldName, subschemaConfig.merge)); return (isIsolatedFieldName || (subschemaConfig.merge[typeName]?.keyFieldNames ?? []).includes(fieldName)); }, }), { skipPruning: typ => computedFieldTypes[typ.name] != null }); const merge = Object.fromEntries( // get rid of keyFieldNames again Object.entries(subschemaConfig.merge).map(([typeName, { keyFieldNames, ...config }]) => [ typeName, config, ])); const filteredSubschema = { ...subschemaConfig, merge, transforms: (subschemaConfig.transforms ?? []).concat([ _createCompositeFieldFilter(filteredSchema), new wrap_1.FilterTypes(// filter out empty types // filter out empty types type => (!(0, graphql_1.isObjectType)(type) && !(0, graphql_1.isInterfaceType)(type)) || Object.keys(type.getFields()).length > 0), ]), }; return filteredSubschema; }