Nestjs Graphql Federation 2 problem when migrating from @apollo/gateway to @apollo/router

54 Views Asked by At

I'm facing errors using Rover CLI to compose my subgraphs, however I don't face any issues with the gateway approach using the same code.

I'm using Nestjs graphql driver.

It has been a blocker for a couple of days. any help would be appreciated. thank you.

The errors :

$ rover supergraph compose --config ./supergraph.yaml -o supergraph.graphql
resolving SDL for subgraphs defined in ./supergraph.yaml
composing supergraph with Federation v2.3.2.exe
error[E029]: Encountered 28 build errors while trying to build a supergraph.

Caused by:
    UNKNOWN: The following supergraph API query:
    mutation {
      refreshToken {
        user {
          resources {
            ...
          }
        }
      }
    }
    cannot be satisfied by the subgraphs because:
    - from subgraph "users":
      - cannot find field "UserDto.resources".
      - cannot move to subgraph "resources", which has field "UserDto.resources", because type "UserDto" has no @key defined in subgraph "resources".
    UNKNOWN: The following supergraph API query:
    mutation {
      refreshToken {
        user {
          rooms {
            ...
          }
        }
      }
    }
    cannot be satisfied by the subgraphs because:
    - from subgraph "users":
      - cannot find field "UserDto.rooms".
      - cannot move to subgraph "rooms", which has field "UserDto.rooms", because type "UserDto" has no @key defined in subgraph "rooms".

users dto and it's generated schema :

import { Directive, ID, ObjectType, registerEnumType } from '@nestjs/graphql';
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';

import { IUser, UserRole } from '@core';

@ObjectType()
@Directive('@key(fields: "id")')
export class UserDto implements Omit<IUser, 'password' | 'hashedRefreshToken'> {
  @IDField(() => ID)
  id: string;

  @FilterableField(() => String)
  firstName: string;

  @FilterableField()
  lastName: string;

  @FilterableField()
  username: string;

  @FilterableField()
  email: string;

  @FilterableField()
  verified: boolean;

  @FilterableField({
    nullable: true,
  })
  birthday?: Date;

  @FilterableField(() => UserRole)
  role: UserRole;

  @FilterableField()
  createdAt: Date;

  @FilterableField()
  updatedAt: Date;

  constructor(user: UserDto) {
    Object.assign(this, user);
  }
}

registerEnumType(UserRole, {
  name: 'UserRole',
});

###################################################################################################

# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

type UserDto {
  id: ID!
  firstName: String!
  lastName: String!
  username: String!
  email: String!
  verified: Boolean!
  birthday: DateTime
  role: UserRole!
  createdAt: DateTime!
  updatedAt: DateTime!
}

"""
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
"""
scalar DateTime

enum UserRole {
  ADMIN
  USER
}

"""Login response"""
type LoginResponseDto {
  accessToken: String!
  refreshToken: String!
  user: UserDto!
}

"""Register response"""
type RegisterResponse {
  accessToken: String!
  refreshToken: String!
  user: UserDto!
}

type Query {
  me: UserDto!
}

type Mutation {
  """Login a user with username or email and password, returns JWT token."""
  login(credentials: Credentials!): LoginResponseDto!
  register(userInfo: RegisterUserDto!): RegisterResponse!
  refreshToken: LoginResponseDto!
}

"""Login user"""
input Credentials {
  email: String
  username: String
  password: String!
}

"""Create new user"""
input RegisterUserDto {
  email: String!
  firstName: String!
  password: String!
  lastName: String!
  username: String!
  birthday: DateTime
}

rooms dto and it's generated schema :

import {
  Directive,
  Field,
  GraphQLISODateTime,
  ID,
  ObjectType,
  registerEnumType,
} from '@nestjs/graphql';
import { IRoom, IRoomDatabaseEntity, RoomType } from '@core';
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { UserDto } from './user-dto.directive';

@ObjectType()
@Directive('@key(fields: "id")')
export class RoomDto implements Omit<IRoom, keyof IRoomDatabaseEntity> {
  @IDField(() => ID)
  id: string;

  @FilterableField()
  title: string;

  @FilterableField(() => String, { nullable: true })
  description?: string;

  @FilterableField(() => RoomType)
  roomType: RoomType;

  @Field(() => [String])
  resourceIds: string[];

  @FilterableField(() => GraphQLISODateTime)
  createdAt: Date;

  @FilterableField(() => GraphQLISODateTime)
  updatedAt: Date;

  @Field(() => UserDto)
  author: UserDto;

  @Field(() => [UserDto])
  participants: UserDto[];

  @Field(() => [UserDto])
  owners: UserDto[];

  @Field(() => [UserDto])
  moderators: UserDto[];

  @Field(() => [UserDto])
  collaborators: UserDto[];
}

registerEnumType(RoomType, {
  name: 'RoomType',
});

####################################################################################
# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

type UserDto {
  id: ID!
  rooms: [RoomDto!]!
  moderatedRooms: [RoomDto!]!
  participatedRooms: [RoomDto!]!
  collaboratedRooms: [RoomDto!]!
}

type RoomDto {
  id: ID!
  title: String!
  description: String
  roomType: RoomType!
  resourceIds: [String!]!
  createdAt: DateTime!
  updatedAt: DateTime!
  author: UserDto!
  participants: [UserDto!]!
  owners: [UserDto!]!
  moderators: [UserDto!]!
  collaborators: [UserDto!]!
}

enum RoomType {
  PUBLIC
  PRIVATE
}

"""
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
"""
scalar DateTime

type DeleteResponse {
  message: String!
  success: Boolean!
}

type Query {
  room(id: String!): RoomDto!
  rooms: [RoomDto!]!
}

type Mutation {
  createRoom(room: CreateRoomInput!): RoomDto!
  deleteRoom(id: String!): DeleteResponse!
}

"""Input to create a new room"""
input CreateRoomInput {
  description: String
  title: String!
  roomType: String
  resourceIds: [String!]!
}

resources dto and it's generated schema :

import {
  Directive,
  Field,
  GraphQLISODateTime,
  ID,
  ObjectType,
  registerEnumType,
} from '@nestjs/graphql';
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';

import { IResource, ResourceFormat, ResourceType } from '@core';

import { UserDto } from './user-refrence.dto';

@ObjectType('Resource', {
  description: 'Resources uploaded by users',
})
@Directive('@key(fields: "id")')
export class ResourceDto implements IResource {
  @IDField(() => ID)
  id: string;

  @FilterableField()
  title: string;

  @FilterableField()
  description?: string;

  @FilterableField(() => ResourceType)
  type: ResourceType;

  @FilterableField(() => ResourceFormat)
  format: ResourceFormat;

  @FilterableField(() => GraphQLISODateTime)
  createdAt: Date;

  @FilterableField(() => GraphQLISODateTime)
  updatedAt: Date;

  @Field(() => UserDto)
  author: UserDto;

  @Field(() => String, {
    nullable: true,
  })
  indexed: string;
  @Field(() => String, {
    nullable: true,
  })
  raw: string;
}

registerEnumType(ResourceType, {
  name: 'ResourceType',
});

registerEnumType(ResourceFormat, {
  name: 'ResourceFormat',
});


####################################################################################
# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

type UserDto {
  id: ID!
  resources: [Resource!]!
}

"""Resources uploaded by users"""
type Resource {
  id: ID!
  title: String!
  description: String!
  type: ResourceType!
  format: ResourceFormat!
  createdAt: DateTime!
  updatedAt: DateTime!
  author: UserDto!
  indexed: String
  raw: String
}

enum ResourceType {
  BOOK
  VIDEO
  AUDIO
  IMAGE
  DOCUMENT
  ARTICLE
  CODE
  OTHER
}

enum ResourceFormat {
  PDF
  DOC
  DOCX
  XLS
  XLSX
  PPT
  PPTX
  MP3
  MP4
  WAV
  AVI
  MKV
  JPG
  JPEG
  PNG
  GIF
  OTHER
}

"""
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
"""
scalar DateTime

type DeleteResponse {
  message: String!
  success: Boolean!
}

type Query {
  getOneResource(id: String!): Resource!
  getAllResources: [Resource!]!
}

type Mutation {
  createResource(resource: CreateResourceInput!): Resource!
  deleteResource(id: String!): DeleteResponse!
}

"""Create new resource"""
input CreateResourceInput {
  description: String!
  format: String!
  title: String!
  type: String!
}

user/resources reference

import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
import { ResourceDto } from './resource.dto';

@ObjectType()
@Directive('@key(fields: "id")')
export class UserDto {
  @Field(() => ID)
  id!: string;

  @Field(() => [ResourceDto])
  resources!: ResourceDto[];
}

user/rooms reference

import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
import { RoomDto } from './room.dto';

@ObjectType()
@Directive('@key(fields: "id")')
export class UserDto {
  @Field(() => ID)
  id!: string;

  @Field(() => [RoomDto])
  rooms!: RoomDto[];
}

It's expected that Rover successfully compose the subgraphs into supergraph.

0

There are 0 best solutions below