Skip to content

Bug: Prefix Transform Doesn't Strip Prefixes from Variable Types During Query Delegation #9210

@LeftoversTodayAppAdmin

Description

@LeftoversTodayAppAdmin

Bug: Prefix Transform Doesn't Strip Prefixes from Variable Types During Query Delegation

Description

When using createPrefixTransform in GraphQL Mesh, input types are correctly prefixed in the composed supergraph schema, but when queries are delegated to subgraphs, the variable type names in the query document retain the prefix, causing validation errors on the subgraph.

Environment

  • @graphql-mesh/compose-cli: ^1.11.4
  • @graphql-hive/gateway: ^2.3.1
  • Node.js: v20.x
  • GraphQL: 16.x

Reproduction

mesh.config.ts

import { createPrefixTransform, defineConfig, loadGraphQLHTTPSubgraph } from '@graphql-mesh/compose-cli';

export const composeConfig = defineConfig({
  subgraphs: [
    {
      sourceHandler: loadGraphQLHTTPSubgraph('VendureShop', {
        endpoint: 'http://localhost:3000/shop-api',
      }),
      transforms: [
        createPrefixTransform({
          value: 'vendureShop_',
          includeRootOperations: true
        }),
      ]
    }
  ]
});

Subgraph Schema (VendureShop)

input DateRange {
  start: DateTime!
  end: DateTime!
}

type Customer {
  id: ID!
  orders(options: OrderListOptions): OrderList!
}

input OrderListOptions {
  filter: OrderFilterParameter
}

input OrderFilterParameter {
  orderPlacedAt: DateOperators
}

input DateOperators {
  between: DateRange
}

Client Query

query GetRecentOrders($dateRange: vendureShop_DateRange) {
  vendureShop_activeCustomer {
    orders(
      options: {
        filter: {
          orderPlacedAt: { between: $dateRange }
        }
      }
    ) {
      items {
        id
        orderPlacedAt
      }
    }
  }
}

Variables

{
  "dateRange": {
    "start": "2026-01-01T00:00:00Z",
    "end": "2026-02-01T00:00:00Z"
  }
}

Expected Behavior

The gateway should:

  1. Accept the query with $dateRange: vendureShop_DateRange (prefixed type from supergraph)
  2. When delegating to the VendureShop subgraph, rewrite the query to use $dateRange: DateRange (unprefixed)
  3. Successfully execute the query against the subgraph

Actual Behavior

The query sent to the subgraph contains:

query GetRecentOrders($dateRange: vendureShop_DateRange) { ... }

The subgraph responds with:

{
  "errors": [{
    "message": "Unknown type \"vendureShop_DateRange\".",
    "extensions": {
      "code": "GRAPHQL_VALIDATION_FAILED"
    }
  }]
}

Root Cause Analysis

The createPrefixTransform:

  1. ✅ Correctly transforms the schema by prefixing type definitions
  2. ✅ Correctly transforms field types and arguments in the schema
  3. Does NOT transform variable type references in query documents during delegation

When a query is delegated to a subgraph, the gateway sends the query document as-is with prefixed variable type names, which don't exist in the original subgraph schema.

Impact

This bug affects all queries using variables with prefixed input types, which includes:

  • Filter parameters
  • Sort options
  • Pagination arguments
  • Complex input objects
  • Any custom input types

In our production system:

  • 280 Vendure input types affected
  • 47 Strapi input types affected
  • 327 total input types that cannot be used in variables

This is a critical blocker for using prefix transforms with any real-world GraphQL API that uses input types.

Current Workaround

We're forced to avoid using variables with prefixed input types:

Workaround 1: Remove variables, limit results, filter client-side

# Instead of filtering with DateRange variable, fetch more and filter in JS
query GetRecentOrders {
  vendureShop_activeCustomer {
    orders(
      options: {
        sort: { orderPlacedAt: DESC }
        take: 100  # Fetch recent orders
      }
    ) {
      items {
        id
        orderPlacedAt
      }
    }
  }
}
// Filter in application code
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const filtered = orders.items.filter(order => 
  new Date(order.orderPlacedAt) >= sevenDaysAgo
);

Problems:

  • Inefficient (fetches unnecessary data)
  • Doesn't scale (limited to max page size)
  • Moves filtering from database to application layer
  • Increases network payload and latency

Workaround 2: Inline values (loses variable benefits)

query GetRecentOrders {
  vendureShop_activeCustomer {
    orders(
      options: {
        filter: {
          orderPlacedAt: { 
            between: {
              start: "2026-01-01T00:00:00Z"
              end: "2026-02-01T00:00:00Z"
            }
          }
        }
      }
    ) {
      items { id }
    }
  }
}

Problems:

  • Can't use dynamic values
  • Breaks query caching
  • Requires string manipulation/injection
  • Security concerns with user input

Proposed Solution

The prefix transform should intercept query delegation and rewrite variable type names:

// Pseudo-code for the fix
class PrefixTransform {
  transformRequest(request, delegationContext) {
    if (delegationContext.targetSubgraph === this.subgraphName) {
      // Parse query document
      // Find all variable definitions
      // Replace prefixed type names with original names
      const fixedQuery = rewriteVariableTypes(
        request.document,
        this.prefix,
        'strip' // Remove prefix when delegating to subgraph
      );
      return { ...request, document: fixedQuery };
    }
    return request;
  }
}

The transform should:

  1. Detect when delegating to the source subgraph
  2. Parse variable definitions in the query
  3. Strip the prefix from variable type names
  4. Preserve the prefix in field selections and everywhere else

Example of Expected Transformation

Query sent to gateway (from client):

query GetOrders($dateRange: vendureShop_DateRange) {
  vendureShop_activeCustomer {
    orders(options: { filter: { orderPlacedAt: { between: $dateRange }}}) {
      items { id }
    }
  }
}

Query delegated to subgraph (after transformation):

query GetOrders($dateRange: DateRange) {  # ← Prefix stripped from variable type
  activeCustomer {  # ← Prefix stripped from field names
    orders(options: { filter: { orderPlacedAt: { between: $dateRange }}}) {
      items { id }
    }
  }
}

Related Issues

  • Similar issue in Apollo Federation was fixed in v2.0
  • GraphQL Tools wrapSchema has similar challenges with variable types
  • This affects any schema transformation that renames types

Additional Context

This bug makes createPrefixTransform unusable for production systems that need to:

  • Use multiple subgraphs with overlapping type names
  • Pass complex filter/sort/pagination parameters
  • Maintain efficient database queries
  • Follow GraphQL best practices (variables for dynamic values)

We're currently evaluating implementing a custom transform workaround.

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