Skip to content

V2 Federation Directives do not get injected before the GraphQL loader, loads schemas #7670

@mehdiamiri1440

Description

@mehdiamiri1440

Issue workflow progress

  • 1. The issue provides a reproduction available on Github, Stackblitz or CodeSandbox
  • 2. A failing test has been provided
  • 3. A local solution has been provided
  • 4. A pull request is pending review

Describe the bug

The @graphql-tools/import package fails to process schemas with Federation v2 directives due to insufficient directive recognition in its core validation logic. This is a critical issue because:

  1. When a GraphQL schema uses #import statements, the GraphQL loader chain exclusively delegates to processImport from @graphql-tools/import
  2. The directive validation in processImport checks against a fixed array of builtinDirectives that's missing Federation v2 directives
  3. The validation fails on line 584 of index.js where it calls: if (!builtinDirectives.includes(directiveName)) { for directives like @shareable, @tag, etc.

Interestingly, the package already defines a Federation-related array that's never used for validation:

// in @graphql-tools/import/cjs/index.js
const federationImports = [
    '@composeDirective',
    '@extends',
    '@external',
    '@inaccessible',
    '@interfaceObject',
    '@key',
    '@override',
    '@provides',
    '@requires',
    '@shareable',
    '@tag',
    'FieldSet',
];

However:

  1. This array is never referenced during directive validation (the code at line 584 only checks against builtinDirectives)
  2. Even if it were used, it's still missing several Federation v2 directives like @link, and Apollo directives like @connect, @source, etc.
  3. The directive names include the @ symbol, which doesn't match how they're referenced during validation

This issue creates a complete roadblock for projects using both Federation v2 and schema imports, as there is no alternative path for handling imports - the only code path triggers validation failures.

To Reproduce

Create a GraphQL schema with imports and Federation v2 directives:

  # main.graphql
  import User from './user.graphql'

  type Query {
    getUser(id: ID!): User
  }

  # user.graphql
  type User @key(fields: "id") {
    id: ID!
    name: String @shareable
    email: String @inaccessible
    role: String @tag(name: "internal")
  }

  Load the schema with the standard GraphQL tools chain:

  const { loadSchema } = require('@graphql-tools/load');
  const { GraphQLFileLoader } = require('@graphql-tools/graphql-file-loader');

  async function loadGraphQLSchema() {
    try {
      const schema = await loadSchema('./main.graphql', {
        loaders: [new GraphQLFileLoader()]
      });
      console.log('Schema loaded successfully');
    } catch (error) {
      console.error('Failed to load schema:', error.message);
      // Fails with: Couldn't find type tag in any of the schemas.
    }
  }

Expected behavior

The schema loading process should successfully recognize Federation v2 directives (e.g., @Shareable, @tag, @inaccessible) as valid built-in directives. These directives should be part of the builtinDirectives array in @graphql-tools/import, allowing the processImport function to properly
handle schemas that use Federation v2 features.

Environment:

  • OS: macOS Ventura
  • @graphql-tools/import: 7.0.0
  • @graphql-tools/load: ^7.0.0
  • @graphql-tools/graphql-file-loader: ^7.0.0
  • NodeJS: 18.x

Additional context

The technical root of the issue is in index.js of @graphql-tools/import where the built-in directives are defined:

const builtinDirectives = [
'deprecated',
'skip',
'include',
'cacheControl',
'connection',
'client',
'specifiedBy',
...federationV1Directives,
];

It only includes Federation v1 directives (key, provides, requires, external) but completely omits Federation v2 directives like:

const federationV2Directives = [
'extends',
'tag',
'shareable',
'inaccessible',
'override',
'interfaceObject',
'composeDirective',
'link',
];

And Apollo Connectors/Router directives:

const apolloConnectorsDirectives = [
'connect',
'source',
'authenticated',
'requiresScopes',
'policy',
];

The federationImports array defined in the module is not connected to the directive validation logic - it appears to be used for something else entirely, and isn't helping with the directive validation.

We've implemented a workaround by patching the module at runtime using Module._load interception:

// This is our patched module that intercepts loading of @graphql-tools/import
// and extends the builtinDirectives array to include Federation v2 directives
const Module = require('module');
const originalLoad = Module._load;

Module._load = function(request, parent) {
const exports = originalLoad.apply(this, arguments);

if (request === '@graphql-tools/import' && parent && parent.path) {
  const resolvedPath = require.resolve('@graphql-tools/import', {
    paths: [parent.path],
  });

  if (resolvedPath) {
    // Use rewire to modify the internal builtinDirectives array
    const rewire = require('rewire');
    const patchedModule = rewire(resolvedPath);
    const builtinDirectives = patchedModule.__get__('builtinDirectives');

    // Add Federation v2 and Apollo directives
    [
      'extends', 'tag', 'shareable', 'inaccessible', 'override',
      'interfaceObject', 'composeDirective', 'link', 'connect',
      'source', 'authenticated', 'requiresScopes', 'policy'
    ].forEach(directive => {
      if (!builtinDirectives.includes(directive)) {
        builtinDirectives.push(directive);
      }
    });

    // Replace the processImport function on the cached module
    require.cache[resolvedPath].exports.processImport = patchedModule.processImport;
  }
}

return exports;

};

However, this is obviously not sustainable as it requires runtime patching of the module system, which has its own complications. The proper fix would be to add support for all Federation v2 directives directly in the @graphql-tools/import package's builtinDirectives array.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions