@graphql-tools/stitching-directives
Version: 
A set of utils for faster development of GraphQL tools
1,194 lines (1,181 loc) • 42.7 kB
JavaScript
import { visit, Kind, TypeNameMetaFieldDef, parseValue, valueFromASTUntyped, getNamedType, getNullableType, isListType, print, isInterfaceType, isUnionType, isObjectType, isNamedType, isAbstractType, GraphQLDirective, GraphQLNonNull, GraphQLString, GraphQLList, parse } from 'graphql';
import { cloneSubschemaConfig } from '@graphql-tools/delegate';
import { mapSchema, MapperKind, getDirective, parseSelectionSet, getImplementingTypes, mergeDeep, isSome } from '@graphql-tools/utils';
const defaultStitchingDirectiveOptions = {
  keyDirectiveName: "key",
  computedDirectiveName: "computed",
  canonicalDirectiveName: "canonical",
  mergeDirectiveName: "merge",
  pathToDirectivesInExtensions: ["directives"]
};
function extractVariables(inputValue) {
  const path = [];
  const variablePaths = /* @__PURE__ */ Object.create(null);
  const keyPathVisitor = {
    enter: (_node, key) => {
      if (typeof key === "number") {
        path.push(key);
      }
    },
    leave: (_node, key) => {
      if (typeof key === "number") {
        path.pop();
      }
    }
  };
  const fieldPathVisitor = {
    enter: (node) => {
      path.push(node.name.value);
    },
    leave: () => {
      path.pop();
    }
  };
  const variableVisitor = {
    enter: (node, key) => {
      if (typeof key === "number") {
        variablePaths[node.name.value] = path.concat([key]);
      } else {
        variablePaths[node.name.value] = path.slice();
      }
      return {
        kind: Kind.NULL
      };
    }
  };
  const newInputValue = visit(inputValue, {
    [Kind.OBJECT]: keyPathVisitor,
    [Kind.LIST]: keyPathVisitor,
    [Kind.OBJECT_FIELD]: fieldPathVisitor,
    [Kind.VARIABLE]: variableVisitor
  });
  return {
    inputValue: newInputValue,
    variablePaths
  };
}
function pathsFromSelectionSet(selectionSet, path = []) {
  const paths = [];
  for (const selection of selectionSet.selections) {
    const additions = pathsFromSelection(selection, path) ?? [];
    for (const addition of additions) {
      paths.push(addition);
    }
  }
  return paths;
}
function pathsFromSelection(selection, path) {
  if (selection.kind === Kind.FIELD) {
    const responseKey = selection.alias?.value ?? selection.name.value;
    if (selection.selectionSet) {
      return pathsFromSelectionSet(
        selection.selectionSet,
        path.concat([responseKey])
      );
    } else {
      return [path.concat([responseKey])];
    }
  } else if (selection.kind === Kind.INLINE_FRAGMENT) {
    return pathsFromSelectionSet(selection.selectionSet, path);
  }
  return void 0;
}
function getSourcePaths(mappingInstructions, selectionSet) {
  const sourcePaths = [];
  for (const mappingInstruction of mappingInstructions) {
    const { sourcePath } = mappingInstruction;
    if (sourcePath.length) {
      sourcePaths.push(sourcePath);
      continue;
    }
    if (selectionSet == null) {
      continue;
    }
    const paths = pathsFromSelectionSet(selectionSet);
    for (const path of paths) {
      sourcePaths.push(path);
    }
    sourcePaths.push([TypeNameMetaFieldDef.name]);
  }
  return sourcePaths;
}
const KEY_DELIMITER = "__dot__";
const EXPANSION_PREFIX = "__exp";
function preparseMergeArgsExpr(mergeArgsExpr) {
  const variableRegex = /\$[_A-Za-z][_A-Za-z0-9.]*/g;
  const dotRegex = /\./g;
  mergeArgsExpr = mergeArgsExpr.replace(
    variableRegex,
    (variable) => variable.replace(dotRegex, KEY_DELIMITER)
  );
  const segments = mergeArgsExpr.split("[[");
  const expansionExpressions = /* @__PURE__ */ Object.create(null);
  if (segments.length === 1) {
    return { mergeArgsExpr, expansionExpressions };
  }
  let finalSegments = [segments[0]];
  for (let i = 1; i < segments.length; i++) {
    const additionalSegments = segments[i]?.split("]]");
    if (additionalSegments?.length !== 2) {
      throw new Error(
        `Each opening "[[" must be matched by a closing "]]" without nesting.`
      );
    }
    finalSegments = finalSegments.concat(additionalSegments);
  }
  let finalMergeArgsExpr = finalSegments[0];
  for (let i = 1; i < finalSegments.length - 1; i += 2) {
    const variableName = `${EXPANSION_PREFIX}${(i - 1) / 2 + 1}`;
    expansionExpressions[variableName] = finalSegments[i];
    finalMergeArgsExpr += `$${variableName}${finalSegments[i + 1]}`;
  }
  return { mergeArgsExpr: finalMergeArgsExpr, expansionExpressions };
}
function addProperty(object, path, value) {
  const initialSegment = path[0];
  if (path.length === 1) {
    object[initialSegment] = value;
    return;
  }
  let field = object[initialSegment];
  if (field != null) {
    addProperty(field, path.slice(1), value);
    return;
  }
  if (typeof path[1] === "string") {
    field = /* @__PURE__ */ Object.create(null);
  } else {
    field = [];
  }
  addProperty(field, path.slice(1), value);
  object[initialSegment] = field;
}
function getProperty(object, path) {
  if (!path.length || object == null) {
    return object;
  }
  const newPath = path.slice();
  const key = newPath.shift();
  if (key == null) {
    return;
  }
  const prop = object[key];
  return getProperty(prop, newPath);
}
function getProperties(object, propertyTree) {
  if (object == null) {
    return object;
  }
  const newObject = /* @__PURE__ */ Object.create(null);
  for (const key in propertyTree) {
    const subKey = propertyTree[key];
    if (subKey == null) {
      newObject[key] = object[key];
      continue;
    }
    const prop = object[key];
    newObject[key] = deepMap(prop, function deepMapFn(item) {
      return getProperties(item, subKey);
    });
  }
  return newObject;
}
function propertyTreeFromPaths(paths) {
  const propertyTree = /* @__PURE__ */ Object.create(null);
  for (const path of paths) {
    addProperty(propertyTree, path, null);
  }
  return propertyTree;
}
function deepMap(arrayOrItem, fn) {
  if (Array.isArray(arrayOrItem)) {
    return arrayOrItem.map(
      (nestedArrayOrItem) => deepMap(nestedArrayOrItem, fn)
    );
  }
  return fn(arrayOrItem);
}
function parseMergeArgsExpr(mergeArgsExpr, selectionSet) {
  const { mergeArgsExpr: newMergeArgsExpr, expansionExpressions } = preparseMergeArgsExpr(mergeArgsExpr);
  const inputValue = parseValue(`{ ${newMergeArgsExpr} }`, {
    noLocation: true
  });
  const { inputValue: newInputValue, variablePaths } = extractVariables(inputValue);
  if (!Object.keys(expansionExpressions).length) {
    if (!Object.keys(variablePaths).length) {
      throw new Error("Merge arguments must declare a key.");
    }
    const mappingInstructions = getMappingInstructions(variablePaths);
    const usedProperties2 = propertyTreeFromPaths(
      getSourcePaths(mappingInstructions, selectionSet)
    );
    return {
      args: valueFromASTUntyped(newInputValue),
      usedProperties: usedProperties2,
      mappingInstructions
    };
  }
  const expansionRegEx = new RegExp(`^${EXPANSION_PREFIX}[0-9]+$`);
  for (const variableName in variablePaths) {
    if (!variableName.match(expansionRegEx)) {
      throw new Error(
        "Expansions cannot be mixed with single key declarations."
      );
    }
  }
  const expansions = [];
  const sourcePaths = [];
  for (const variableName in expansionExpressions) {
    const str = expansionExpressions[variableName];
    const valuePath = variablePaths[variableName];
    const {
      inputValue: expansionInputValue,
      variablePaths: expansionVariablePaths
    } = extractVariables(parseValue(`${str}`, { noLocation: true }));
    if (!Object.keys(expansionVariablePaths).length) {
      throw new Error("Merge arguments must declare a key.");
    }
    const mappingInstructions = getMappingInstructions(expansionVariablePaths);
    const value = valueFromASTUntyped(expansionInputValue);
    sourcePaths.push(...getSourcePaths(mappingInstructions, selectionSet));
    assertNotWithinList(valuePath);
    expansions.push({
      valuePath,
      value,
      mappingInstructions
    });
  }
  const usedProperties = propertyTreeFromPaths(sourcePaths);
  return {
    args: valueFromASTUntyped(newInputValue),
    usedProperties,
    expansions
  };
}
function getMappingInstructions(variablePaths) {
  const mappingInstructions = [];
  for (const keyPath in variablePaths) {
    const valuePath = variablePaths[keyPath];
    const splitKeyPath = keyPath.split(KEY_DELIMITER).slice(1);
    assertNotWithinList(valuePath);
    mappingInstructions.push({
      destinationPath: valuePath,
      sourcePath: splitKeyPath
    });
  }
  return mappingInstructions;
}
function assertNotWithinList(path) {
  for (const pathSegment of path) {
    if (typeof pathSegment === "number") {
      throw new Error("Insertions cannot be made into a list.");
    }
  }
}
function stitchingDirectivesTransformer(options = {}) {
  const {
    keyDirectiveName,
    computedDirectiveName,
    mergeDirectiveName,
    canonicalDirectiveName,
    pathToDirectivesInExtensions
  } = {
    ...defaultStitchingDirectiveOptions,
    ...options
  };
  return (subschemaConfig) => {
    const newSubschemaConfig = cloneSubschemaConfig(subschemaConfig);
    const selectionSetsByType = /* @__PURE__ */ Object.create(null);
    const computedFieldSelectionSets = /* @__PURE__ */ Object.create(null);
    const mergedTypesResolversInfo = /* @__PURE__ */ Object.create(null);
    const canonicalTypesInfo = /* @__PURE__ */ Object.create(null);
    const selectionSetsByTypeAndEntryField = /* @__PURE__ */ Object.create(null);
    const mergedTypesResolversInfoByEntryField = /* @__PURE__ */ Object.create(null);
    const schema = subschemaConfig.schema;
    function setCanonicalDefinition(typeName, fieldName) {
      canonicalTypesInfo[typeName] = canonicalTypesInfo[typeName] || /* @__PURE__ */ Object.create(null);
      if (fieldName) {
        const fields = canonicalTypesInfo[typeName]?.fields ?? /* @__PURE__ */ Object.create(null);
        canonicalTypesInfo[typeName].fields = fields;
        fields[fieldName] = true;
      } else {
        canonicalTypesInfo[typeName].canonical = true;
      }
    }
    mapSchema(schema, {
      [MapperKind.OBJECT_TYPE]: (type) => {
        const keyDirective = getDirective(
          schema,
          type,
          keyDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (keyDirective != null) {
          const selectionSet = parseSelectionSet(keyDirective["selectionSet"], {
            noLocation: true
          });
          selectionSetsByType[type.name] = selectionSet;
        }
        const canonicalDirective = getDirective(
          schema,
          type,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective != null) {
          setCanonicalDefinition(type.name);
        }
        return void 0;
      },
      [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
        const computedDirective = getDirective(
          schema,
          fieldConfig,
          computedDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (computedDirective != null) {
          const selectionSet = parseSelectionSet(
            computedDirective["selectionSet"],
            {
              noLocation: true
            }
          );
          if (!computedFieldSelectionSets[typeName]) {
            computedFieldSelectionSets[typeName] = /* @__PURE__ */ Object.create(null);
          }
          computedFieldSelectionSets[typeName][fieldName] = selectionSet;
        }
        const mergeDirective = getDirective(
          schema,
          fieldConfig,
          mergeDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (mergeDirective?.["keyField"] != null) {
          const mergeDirectiveKeyField = mergeDirective["keyField"];
          const selectionSet = parseSelectionSet(
            `{ ${mergeDirectiveKeyField}}`,
            {
              noLocation: true
            }
          );
          const typeNames = mergeDirective["types"];
          const returnType = getNamedType(fieldConfig.type);
          forEachConcreteType(schema, returnType, typeNames, (typeName2) => {
            if (typeNames == null || typeNames.includes(typeName2)) {
              let existingEntryFieldMap = selectionSetsByTypeAndEntryField[typeName2];
              if (existingEntryFieldMap == null) {
                existingEntryFieldMap = /* @__PURE__ */ Object.create(null);
                selectionSetsByTypeAndEntryField[typeName2] = existingEntryFieldMap;
              }
              let existingSelectionSet = existingEntryFieldMap[fieldName];
              if (existingSelectionSet == null) {
                existingSelectionSet = selectionSetsByType[typeName2];
              }
              existingEntryFieldMap[fieldName] = existingSelectionSet ? mergeSelectionSets(existingSelectionSet, selectionSet) : selectionSet;
            }
          });
        }
        const canonicalDirective = getDirective(
          schema,
          fieldConfig,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective != null) {
          setCanonicalDefinition(typeName, fieldName);
        }
        return void 0;
      },
      [MapperKind.INTERFACE_TYPE]: (type) => {
        const canonicalDirective = getDirective(
          schema,
          type,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective) {
          setCanonicalDefinition(type.name);
        }
        return void 0;
      },
      [MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName) => {
        const canonicalDirective = getDirective(
          schema,
          fieldConfig,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective) {
          setCanonicalDefinition(typeName, fieldName);
        }
        return void 0;
      },
      [MapperKind.INPUT_OBJECT_TYPE]: (type) => {
        const canonicalDirective = getDirective(
          schema,
          type,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective) {
          setCanonicalDefinition(type.name);
        }
        return void 0;
      },
      [MapperKind.INPUT_OBJECT_FIELD]: (inputFieldConfig, fieldName, typeName) => {
        const canonicalDirective = getDirective(
          schema,
          inputFieldConfig,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective != null) {
          setCanonicalDefinition(typeName, fieldName);
        }
        return void 0;
      },
      [MapperKind.UNION_TYPE]: (type) => {
        const canonicalDirective = getDirective(
          schema,
          type,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective != null) {
          setCanonicalDefinition(type.name);
        }
        return void 0;
      },
      [MapperKind.ENUM_TYPE]: (type) => {
        const canonicalDirective = getDirective(
          schema,
          type,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective != null) {
          setCanonicalDefinition(type.name);
        }
        return void 0;
      },
      [MapperKind.SCALAR_TYPE]: (type) => {
        const canonicalDirective = getDirective(
          schema,
          type,
          canonicalDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (canonicalDirective != null) {
          setCanonicalDefinition(type.name);
        }
        return void 0;
      }
    });
    if (subschemaConfig.merge) {
      for (const typeName in subschemaConfig.merge) {
        const mergedTypeConfig = subschemaConfig.merge[typeName];
        if (mergedTypeConfig?.selectionSet) {
          const selectionSet = parseSelectionSet(
            mergedTypeConfig.selectionSet,
            {
              noLocation: true
            }
          );
          if (selectionSet) {
            if (selectionSetsByType[typeName]) {
              selectionSetsByType[typeName] = mergeSelectionSets(
                selectionSetsByType[typeName],
                selectionSet
              );
            } else {
              selectionSetsByType[typeName] = selectionSet;
            }
          }
        }
        if (mergedTypeConfig?.fields) {
          for (const fieldName in mergedTypeConfig.fields) {
            const fieldConfig = mergedTypeConfig.fields[fieldName];
            if (!fieldConfig?.selectionSet) continue;
            const selectionSet = parseSelectionSet(fieldConfig.selectionSet, {
              noLocation: true
            });
            if (selectionSet) {
              if (computedFieldSelectionSets[typeName]?.[fieldName]) {
                computedFieldSelectionSets[typeName][fieldName] = mergeSelectionSets(
                  computedFieldSelectionSets[typeName][fieldName],
                  selectionSet
                );
              } else {
                if (computedFieldSelectionSets[typeName] == null) {
                  computedFieldSelectionSets[typeName] = /* @__PURE__ */ Object.create(null);
                }
                computedFieldSelectionSets[typeName][fieldName] = selectionSet;
              }
            }
          }
        }
      }
    }
    const allSelectionSetsByType = /* @__PURE__ */ Object.create(null);
    for (const typeName in selectionSetsByType) {
      allSelectionSetsByType[typeName] = allSelectionSetsByType[typeName] || [];
      const selectionSet = selectionSetsByType[typeName];
      allSelectionSetsByType[typeName].push(selectionSet);
    }
    for (const typeName in computedFieldSelectionSets) {
      const selectionSets = computedFieldSelectionSets[typeName];
      for (const i in selectionSets) {
        allSelectionSetsByType[typeName] = allSelectionSetsByType[typeName] || [];
        const selectionSet = selectionSets[i];
        allSelectionSetsByType[typeName].push(selectionSet);
      }
    }
    mapSchema(schema, {
      [MapperKind.OBJECT_FIELD]: function objectFieldMapper(fieldConfig, fieldName) {
        const mergeDirective = getDirective(
          schema,
          fieldConfig,
          mergeDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (mergeDirective != null) {
          const returnType = getNullableType(fieldConfig.type);
          const returnsList = isListType(returnType);
          const namedType = getNamedType(returnType);
          let mergeArgsExpr = mergeDirective["argsExpr"];
          if (mergeArgsExpr == null) {
            const key = mergeDirective["key"];
            const keyField = mergeDirective["keyField"];
            const keyExpr = key != null ? buildKeyExpr(key) : keyField != null ? `$key.${keyField}` : "$key";
            const keyArg = mergeDirective["keyArg"];
            const argNames = keyArg == null ? [Object.keys(fieldConfig.args ?? {})[0]] : keyArg.split(".");
            const lastArgName = argNames.pop();
            mergeArgsExpr = returnsList ? `${lastArgName}: [[${keyExpr}]]` : `${lastArgName}: ${keyExpr}`;
            for (const argName of argNames.reverse()) {
              mergeArgsExpr = `${argName}: { ${mergeArgsExpr} }`;
            }
          }
          const typeNames = mergeDirective["types"];
          forEachConcreteTypeName(
            namedType,
            schema,
            typeNames,
            function generateResolveInfo(typeName) {
              const mergedSelectionSets = [];
              if (allSelectionSetsByType[typeName]) {
                mergedSelectionSets.push(...allSelectionSetsByType[typeName]);
              }
              if (selectionSetsByTypeAndEntryField[typeName]?.[fieldName]) {
                mergedSelectionSets.push(
                  selectionSetsByTypeAndEntryField[typeName][fieldName]
                );
              }
              const parsedMergeArgsExpr = parseMergeArgsExpr(
                mergeArgsExpr,
                allSelectionSetsByType[typeName] == null ? void 0 : mergeSelectionSets(...mergedSelectionSets)
              );
              const additionalArgs = mergeDirective["additionalArgs"];
              if (additionalArgs != null) {
                parsedMergeArgsExpr.args = mergeDeep([
                  parsedMergeArgsExpr.args,
                  valueFromASTUntyped(
                    parseValue(`{ ${additionalArgs} }`, { noLocation: true })
                  )
                ]);
              }
              if (selectionSetsByTypeAndEntryField[typeName]?.[fieldName] != null) {
                const typeConfigByField = mergedTypesResolversInfoByEntryField[typeName] ||= /* @__PURE__ */ Object.create(null);
                typeConfigByField[fieldName] = {
                  fieldName,
                  returnsList,
                  ...parsedMergeArgsExpr
                };
              } else {
                mergedTypesResolversInfo[typeName] = {
                  fieldName,
                  returnsList,
                  ...parsedMergeArgsExpr
                };
              }
            }
          );
        }
        return void 0;
      }
    });
    for (const typeName in selectionSetsByType) {
      const selectionSet = selectionSetsByType[typeName];
      const mergeConfig = newSubschemaConfig.merge ?? /* @__PURE__ */ Object.create(null);
      newSubschemaConfig.merge = mergeConfig;
      if (mergeConfig[typeName] == null) {
        newSubschemaConfig.merge[typeName] = /* @__PURE__ */ Object.create(null);
      }
      const mergeTypeConfig = mergeConfig[typeName];
      mergeTypeConfig.selectionSet = print(selectionSet);
    }
    for (const typeName in computedFieldSelectionSets) {
      const selectionSets = computedFieldSelectionSets[typeName];
      const mergeConfig = newSubschemaConfig.merge ?? /* @__PURE__ */ Object.create(null);
      newSubschemaConfig.merge = mergeConfig;
      if (mergeConfig[typeName] == null) {
        mergeConfig[typeName] = /* @__PURE__ */ Object.create(null);
      }
      const mergeTypeConfig = newSubschemaConfig.merge[typeName];
      const mergeTypeConfigFields = mergeTypeConfig.fields ?? /* @__PURE__ */ Object.create(null);
      mergeTypeConfig.fields = mergeTypeConfigFields;
      for (const fieldName in selectionSets) {
        const selectionSet = selectionSets[fieldName];
        const fieldConfig = mergeTypeConfigFields[fieldName] ?? /* @__PURE__ */ Object.create(null);
        mergeTypeConfigFields[fieldName] = fieldConfig;
        fieldConfig.selectionSet = print(selectionSet);
        fieldConfig.computed = true;
      }
    }
    for (const typeName in mergedTypesResolversInfo) {
      const mergedTypeResolverInfo = mergedTypesResolversInfo[typeName];
      const mergeConfig = newSubschemaConfig.merge ?? /* @__PURE__ */ Object.create(null);
      newSubschemaConfig.merge = mergeConfig;
      if (newSubschemaConfig.merge[typeName] == null) {
        newSubschemaConfig.merge[typeName] = /* @__PURE__ */ Object.create(null);
      }
      const mergeTypeConfig = newSubschemaConfig.merge[typeName];
      mergeTypeConfig.fieldName = mergedTypeResolverInfo.fieldName;
      if (mergedTypeResolverInfo.returnsList) {
        mergeTypeConfig.key = generateKeyFn(mergedTypeResolverInfo);
        mergeTypeConfig.argsFromKeys = generateArgsFromKeysFn(
          mergedTypeResolverInfo
        );
      } else {
        mergeTypeConfig.args = generateArgsFn(mergedTypeResolverInfo);
      }
    }
    for (const typeName in canonicalTypesInfo) {
      const canonicalTypeInfo = canonicalTypesInfo[typeName];
      const mergeConfig = newSubschemaConfig.merge ?? /* @__PURE__ */ Object.create(null);
      newSubschemaConfig.merge = mergeConfig;
      if (newSubschemaConfig.merge[typeName] == null) {
        newSubschemaConfig.merge[typeName] = /* @__PURE__ */ Object.create(null);
      }
      const mergeTypeConfig = newSubschemaConfig.merge[typeName];
      if (canonicalTypeInfo.canonical) {
        mergeTypeConfig.canonical = true;
      }
      if (canonicalTypeInfo.fields) {
        const mergeTypeConfigFields = mergeTypeConfig.fields ?? /* @__PURE__ */ Object.create(null);
        mergeTypeConfig.fields = mergeTypeConfigFields;
        for (const fieldName in canonicalTypeInfo.fields) {
          if (mergeTypeConfigFields[fieldName] == null) {
            mergeTypeConfigFields[fieldName] = /* @__PURE__ */ Object.create(null);
          }
          mergeTypeConfigFields[fieldName].canonical = true;
        }
      }
    }
    for (const typeName in mergedTypesResolversInfoByEntryField) {
      const entryPoints = [];
      const existingMergeConfig = newSubschemaConfig.merge?.[typeName];
      const newMergeConfig = newSubschemaConfig.merge ||= /* @__PURE__ */ Object.create(null);
      if (existingMergeConfig) {
        const { fields, canonical, ...baseEntryPoint } = existingMergeConfig;
        newMergeConfig[typeName] = {
          fields,
          canonical,
          entryPoints
        };
        entryPoints.push(baseEntryPoint);
      } else {
        newMergeConfig[typeName] = {
          entryPoints
        };
      }
      for (const fieldName in mergedTypesResolversInfoByEntryField[typeName]) {
        const mergedTypeResolverInfo = mergedTypesResolversInfoByEntryField[typeName][fieldName];
        const newEntryPoint = {
          fieldName,
          selectionSet: print(
            selectionSetsByTypeAndEntryField[typeName][fieldName]
          )
        };
        if (mergedTypeResolverInfo.returnsList) {
          newEntryPoint.key = generateKeyFn(mergedTypeResolverInfo);
          newEntryPoint.argsFromKeys = generateArgsFromKeysFn(
            mergedTypeResolverInfo
          );
        } else {
          newEntryPoint.args = generateArgsFn(mergedTypeResolverInfo);
        }
        entryPoints.push(newEntryPoint);
      }
      if (entryPoints.length === 1) {
        const [entryPoint] = entryPoints;
        const { fields, canonical } = newMergeConfig[typeName];
        newMergeConfig[typeName] = {
          ...entryPoint,
          fields,
          canonical
        };
      }
    }
    return newSubschemaConfig;
  };
}
function forEachConcreteType(schema, type, typeNames, fn) {
  if (isInterfaceType(type)) {
    for (const typeName of getImplementingTypes(type.name, schema)) {
      if (typeNames == null || typeNames.includes(typeName)) {
        fn(typeName);
      }
    }
  } else if (isUnionType(type)) {
    for (const { name: typeName } of type.getTypes()) {
      if (typeNames == null || typeNames.includes(typeName)) {
        fn(typeName);
      }
    }
  } else if (isObjectType(type)) {
    fn(type.name);
  }
}
function generateKeyFn(mergedTypeResolverInfo) {
  return function keyFn(originalResult) {
    return getProperties(originalResult, mergedTypeResolverInfo.usedProperties);
  };
}
function generateArgsFromKeysFn(mergedTypeResolverInfo) {
  const { expansions, args } = mergedTypeResolverInfo;
  return function generateArgsFromKeys(keys) {
    const newArgs = { ...args };
    if (expansions) {
      for (const expansion of expansions) {
        const mappingInstructions = expansion.mappingInstructions;
        const expanded = [];
        for (const key of keys) {
          let newValue = {};
          for (const { destinationPath, sourcePath } of mappingInstructions) {
            if (destinationPath.length) {
              addProperty(
                newValue,
                destinationPath,
                getProperty(key, sourcePath)
              );
            } else {
              newValue = getProperty(key, sourcePath);
            }
          }
          expanded.push(newValue);
        }
        addProperty(newArgs, expansion.valuePath, expanded);
      }
    }
    return newArgs;
  };
}
function generateArgsFn(mergedTypeResolverInfo) {
  const { mappingInstructions, args, usedProperties } = mergedTypeResolverInfo;
  return function generateArgs(originalResult) {
    const newArgs = { ...args };
    const filteredResult = getProperties(originalResult, usedProperties);
    if (mappingInstructions) {
      for (const mappingInstruction of mappingInstructions) {
        const { destinationPath, sourcePath } = mappingInstruction;
        addProperty(
          newArgs,
          destinationPath,
          getProperty(filteredResult, sourcePath)
        );
      }
    }
    return newArgs;
  };
}
function buildKeyExpr(key) {
  let mergedObject = {};
  for (const keyDef of key) {
    let [aliasOrKeyPath, keyPath] = keyDef.split(":");
    let aliasPath;
    if (keyPath == null) {
      keyPath = aliasPath = aliasOrKeyPath;
    } else {
      aliasPath = aliasOrKeyPath;
    }
    const aliasParts = aliasPath.split(".");
    const lastAliasPart = aliasParts.pop();
    if (lastAliasPart == null) {
      throw new Error(`Key "${key}" is invalid, no path provided.`);
    }
    let object = {
      [lastAliasPart]: `$key.${keyPath}`
    };
    for (const aliasPart of aliasParts.reverse()) {
      object = { [aliasPart]: object };
    }
    mergedObject = mergeDeep([mergedObject, object]);
  }
  return JSON.stringify(mergedObject).replace(/"/g, "");
}
function mergeSelectionSets(...selectionSets) {
  const normalizedSelections = /* @__PURE__ */ Object.create(null);
  for (const selectionSet of selectionSets) {
    for (const selection of selectionSet.selections) {
      const normalizedSelection = print(selection);
      normalizedSelections[normalizedSelection] = selection;
    }
  }
  const newSelectionSet = {
    kind: Kind.SELECTION_SET,
    selections: Object.values(normalizedSelections)
  };
  return newSelectionSet;
}
function forEachConcreteTypeName(returnType, schema, typeNames, fn) {
  if (isInterfaceType(returnType)) {
    for (const typeName of getImplementingTypes(returnType.name, schema)) {
      if (typeNames == null || typeNames.includes(typeName)) {
        fn(typeName);
      }
    }
  } else if (isUnionType(returnType)) {
    for (const type of returnType.getTypes()) {
      if (typeNames == null || typeNames.includes(type.name)) {
        fn(type.name);
      }
    }
  } else if (isObjectType(returnType) && (typeNames == null || typeNames.includes(returnType.name))) {
    fn(returnType.name);
  }
}
const dottedNameRegEx = /^[_A-Za-z][_0-9A-Za-z]*(.[_A-Za-z][_0-9A-Za-z]*)*$/;
function stitchingDirectivesValidator(options = {}) {
  const {
    keyDirectiveName,
    computedDirectiveName,
    mergeDirectiveName,
    pathToDirectivesInExtensions
  } = {
    ...defaultStitchingDirectiveOptions,
    ...options
  };
  return (schema) => {
    const queryTypeName = schema.getQueryType()?.name;
    mapSchema(schema, {
      [MapperKind.OBJECT_TYPE]: (type) => {
        const keyDirective = getDirective(
          schema,
          type,
          keyDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (keyDirective != null) {
          parseSelectionSet(keyDirective["selectionSet"]);
        }
        return void 0;
      },
      [MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
        const computedDirective = getDirective(
          schema,
          fieldConfig,
          computedDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (computedDirective != null) {
          parseSelectionSet(computedDirective["selectionSet"]);
        }
        const mergeDirective = getDirective(
          schema,
          fieldConfig,
          mergeDirectiveName,
          pathToDirectivesInExtensions
        )?.[0];
        if (mergeDirective != null) {
          if (typeName !== queryTypeName) {
            throw new Error(
              "@merge directive may be used only for root fields of the root Query type."
            );
          }
          let returnType = getNullableType(fieldConfig.type);
          if (isListType(returnType)) {
            returnType = getNullableType(returnType.ofType);
          }
          if (!isNamedType(returnType)) {
            throw new Error(
              "@merge directive must be used on a field that returns an object or a list of objects."
            );
          }
          const mergeArgsExpr = mergeDirective["argsExpr"];
          if (mergeArgsExpr != null) {
            parseMergeArgsExpr(mergeArgsExpr);
          }
          const args = Object.keys(fieldConfig.args ?? {});
          const keyArg = mergeDirective["keyArg"];
          if (keyArg == null) {
            if (!mergeArgsExpr && args.length !== 1) {
              throw new Error(
                "Cannot use @merge directive without `keyArg` argument if resolver takes more than one argument."
              );
            }
          } else if (!keyArg.match(dottedNameRegEx)) {
            throw new Error(
              "`keyArg` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods."
            );
          }
          const keyField = mergeDirective["keyField"];
          if (keyField != null && !keyField.match(dottedNameRegEx)) {
            throw new Error(
              "`keyField` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods."
            );
          }
          const key = mergeDirective["key"];
          if (key != null) {
            if (keyField != null) {
              throw new Error(
                "Cannot use @merge directive with both `keyField` and `key` arguments."
              );
            }
            for (const keyDef of key) {
              let [aliasOrKeyPath, keyPath] = keyDef.split(":");
              let aliasPath;
              if (keyPath == null) {
                keyPath = aliasPath = aliasOrKeyPath;
              } else {
                aliasPath = aliasOrKeyPath;
              }
              if (keyPath != null && !keyPath.match(dottedNameRegEx)) {
                throw new Error(
                  "Each partial key within the `key` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods."
                );
              }
              if (aliasPath != null && !aliasOrKeyPath.match(dottedNameRegEx)) {
                throw new Error(
                  "Each alias within the `key` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods."
                );
              }
            }
          }
          const additionalArgs = mergeDirective["additionalArgs"];
          if (additionalArgs != null) {
            parseValue(`{ ${additionalArgs} }`, { noLocation: true });
          }
          if (mergeArgsExpr != null && (keyArg != null || additionalArgs != null)) {
            throw new Error(
              "Cannot use @merge directive with both `argsExpr` argument and any additional argument."
            );
          }
          if (!isInterfaceType(returnType) && !isUnionType(returnType) && !isObjectType(returnType)) {
            throw new Error(
              "@merge directive may be used only with resolver that return an object, interface, or union."
            );
          }
          const typeNames = mergeDirective["types"];
          if (typeNames != null) {
            if (!isAbstractType(returnType)) {
              throw new Error(
                "Types argument can only be used with a field that returns an abstract type."
              );
            }
            const implementingTypes = isInterfaceType(returnType) ? getImplementingTypes(returnType.name, schema).map(
              (typeName2) => schema.getType(typeName2)
            ) : returnType.getTypes();
            const implementingTypeNames = implementingTypes.map((type) => type?.name).filter(isSome);
            for (const typeName2 of typeNames) {
              if (!implementingTypeNames.includes(typeName2)) {
                throw new Error(
                  `Types argument can only include only type names that implement the field return type's abstract type.`
                );
              }
            }
          }
        }
        return void 0;
      }
    });
    return schema;
  };
}
function stitchingDirectives(options = {}) {
  const finalOptions = {
    ...defaultStitchingDirectiveOptions,
    ...options
  };
  const {
    keyDirectiveName,
    computedDirectiveName,
    mergeDirectiveName,
    canonicalDirectiveName
  } = finalOptions;
  const keyDirectiveTypeDefs = (
    /* GraphQL */
    `directive @${keyDirectiveName}(selectionSet: String!) on OBJECT`
  );
  const computedDirectiveTypeDefs = (
    /* GraphQL */
    `directive @${computedDirectiveName}(selectionSet: String!) on FIELD_DEFINITION`
  );
  const mergeDirectiveTypeDefs = (
    /* GraphQL */
    `directive @${mergeDirectiveName}(argsExpr: String, keyArg: String, keyField: String, key: [String!], additionalArgs: String) on FIELD_DEFINITION`
  );
  const canonicalDirectiveTypeDefs = (
    /* GraphQL */
    `directive @${canonicalDirectiveName} on OBJECT | INTERFACE | INPUT_OBJECT | UNION | ENUM | SCALAR | FIELD_DEFINITION | INPUT_FIELD_DEFINITION`
  );
  const keyDirective = new GraphQLDirective({
    name: keyDirectiveName,
    locations: ["OBJECT"],
    args: {
      selectionSet: { type: new GraphQLNonNull(GraphQLString) }
    }
  });
  const computedDirective = new GraphQLDirective({
    name: computedDirectiveName,
    locations: ["FIELD_DEFINITION"],
    args: {
      selectionSet: { type: new GraphQLNonNull(GraphQLString) }
    }
  });
  const mergeDirective = new GraphQLDirective({
    name: mergeDirectiveName,
    locations: ["FIELD_DEFINITION"],
    args: {
      argsExpr: { type: GraphQLString },
      keyArg: { type: GraphQLString },
      keyField: { type: GraphQLString },
      key: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) },
      additionalArgs: { type: GraphQLString }
    }
  });
  const canonicalDirective = new GraphQLDirective({
    name: canonicalDirectiveName,
    locations: [
      "OBJECT",
      "INTERFACE",
      "INPUT_OBJECT",
      "UNION",
      "ENUM",
      "SCALAR",
      "FIELD_DEFINITION",
      "INPUT_FIELD_DEFINITION"
    ]
  });
  const allStitchingDirectivesTypeDefs = [
    keyDirectiveTypeDefs,
    computedDirectiveTypeDefs,
    mergeDirectiveTypeDefs,
    canonicalDirectiveTypeDefs
  ].join("\n");
  return {
    keyDirectiveTypeDefs,
    computedDirectiveTypeDefs,
    mergeDirectiveTypeDefs,
    canonicalDirectiveTypeDefs,
    stitchingDirectivesTypeDefs: allStitchingDirectivesTypeDefs,
    // for backwards compatibility
    allStitchingDirectivesTypeDefs,
    keyDirective,
    computedDirective,
    mergeDirective,
    canonicalDirective,
    allStitchingDirectives: [
      keyDirective,
      computedDirective,
      mergeDirective,
      canonicalDirective
    ],
    stitchingDirectivesValidator: stitchingDirectivesValidator(finalOptions),
    stitchingDirectivesTransformer: stitchingDirectivesTransformer(finalOptions)
  };
}
const extensionKind = /Extension$/;
const entityKinds = [
  Kind.OBJECT_TYPE_DEFINITION,
  Kind.OBJECT_TYPE_EXTENSION,
  Kind.INTERFACE_TYPE_DEFINITION,
  Kind.INTERFACE_TYPE_EXTENSION
];
function isEntityKind(def) {
  return entityKinds.includes(def.kind);
}
function getQueryTypeDef(definitions) {
  const schemaDef = definitions.find(
    (def) => def.kind === Kind.SCHEMA_DEFINITION
  );
  const typeName = schemaDef ? schemaDef.operationTypes.find(({ operation }) => operation === "query")?.type.name.value : "Query";
  return definitions.find(
    (def) => def.kind === Kind.OBJECT_TYPE_DEFINITION && def.name.value === typeName
  );
}
function federationToStitchingSDL(federationSDL, stitchingConfig = stitchingDirectives()) {
  const doc = parse(federationSDL);
  const entityTypes = [];
  const baseTypeNames = doc.definitions.reduce((memo, typeDef) => {
    if (!extensionKind.test(typeDef.kind) && "name" in typeDef && typeDef.name) {
      memo[typeDef.name.value] = true;
    }
    return memo;
  }, {});
  doc.definitions.forEach((typeDef) => {
    if (extensionKind.test(typeDef.kind) && "name" in typeDef && typeDef.name && !baseTypeNames[typeDef.name.value]) {
      typeDef.kind = typeDef.kind.replace(
        extensionKind,
        "Definition"
      );
    }
    if (!isEntityKind(typeDef)) return;
    const keyDirs = [];
    const otherDirs = [];
    typeDef.directives?.forEach((dir) => {
      if (dir.name.value === "key") {
        keyDirs.push(dir);
      } else {
        otherDirs.push(dir);
      }
    });
    if (!keyDirs.length) return;
    const selectionSet = `{ ${keyDirs.map((dir) => dir.arguments[0].value.value).join(" ")} }`;
    const keyFields = parse(selectionSet).definitions[0].selectionSet.selections.map((sel) => sel.name.value);
    const keyDir = keyDirs[0];
    keyDir.name.value = stitchingConfig.keyDirective.name;
    keyDir.arguments[0].name.value = "selectionSet";
    keyDir.arguments[0].value.value = selectionSet;
    typeDef.directives = [keyDir, ...otherDirs];
    typeDef.fields = typeDef.fields?.filter((fieldDef) => {
      return keyFields.includes(fieldDef.name.value) || !fieldDef.directives?.find((dir) => dir.name.value === "external");
    });
    typeDef.fields?.forEach((fieldDef) => {
      fieldDef.directives = fieldDef.directives.filter(
        (dir) => !/^(external|provides)$/.test(dir.name.value)
      );
      fieldDef.directives.forEach((dir) => {
        if (dir.name.value === "requires") {
          dir.name.value = stitchingConfig.computedDirective.name;
          dir.arguments[0].name.value = "selectionSet";
          dir.arguments[0].value.value = `{ ${dir.arguments[0].value.value} }`;
        }
      });
    });
    if (typeDef.kind === Kind.OBJECT_TYPE_DEFINITION || typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) {
      entityTypes.push(typeDef.name.value);
    }
  });
  if (entityTypes.length) {
    const queryDef = getQueryTypeDef(doc.definitions);
    const entitiesSchema = parse(
      /* GraphQL */
      `
      scalar _Any
      union _Entity = ${entityTypes.filter((v, i, a) => a.indexOf(v) === i).join(" | ")}
      type Query { _entities(representations: [_Any!]!): [_Entity]! @${stitchingConfig.mergeDirective.name} }
    `
    ).definitions;
    doc.definitions.push(entitiesSchema[0]);
    doc.definitions.push(entitiesSchema[1]);
    if (queryDef) {
      queryDef.fields.push(entitiesSchema[2].fields[0]);
    } else {
      doc.definitions.push(entitiesSchema[2]);
    }
  }
  return [stitchingConfig.stitchingDirectivesTypeDefs, print(doc)].join("\n");
}
export { federationToStitchingSDL, stitchingDirectives };