@graphql-tools/stitch
Version:
A set of utils for faster development of GraphQL tools
313 lines (312 loc) • 16.3 kB
JavaScript
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;
}
;