Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

list.map is not a function #5

Open
mac2000 opened this issue Apr 26, 2020 · 15 comments
Open

list.map is not a function #5

mac2000 opened this issue Apr 26, 2020 · 15 comments

Comments

@mac2000
Copy link

mac2000 commented Apr 26, 2020

I have taken transformed-server.ts from examples and make js from it, no other changes

transformed-server.js

const { delegateToSchema, makeExecutableSchema } = require('graphql-tools');
const { ApolloServer } = require('apollo-server');
const {transformSchemaFederation} = require('graphql-transform-federation')

const products = [
  {
    id: '123',
    name: 'name from transformed service',
  },
];

const schemaWithoutFederation = makeExecutableSchema({
  typeDefs: `
    type Product {
      id: String!
      name: String!
    }
    
    type Query {
      productById(id: String!): Product!
    }
  `,
  resolvers: {
    Query: {
      productById(source, { id }) {
        return products.find(product => product.id === id);
      },
    },
  },
});

const federationSchema = transformSchemaFederation(schemaWithoutFederation, {
  Query: {
    extend: true,
  },
  Product: {
    extend: true,
    keyFields: ['id'],
    fields: {
      id: {
        external: true,
      },
    },
    resolveReference(reference, context, info) {
      return delegateToSchema({
        schema: info.schema,
        operation: 'query',
        fieldName: 'productById',
        args: {
          id: reference.id,
        },
        context,
        info,
      });
    },
  },
});

new ApolloServer({
  schema: federationSchema,
})
  .listen({
    port: 4008,
  })
  .then(({ url }) => {
    console.log(`🚀 Transformed server ready at ${url}`);
  });

Now if I will try run node transformed-server.js and try query following:

{
  _entities(representations:[{__typename:"Product",id:"123"}]) {
    ... on Product {
      id
      name
    }
  }
}

I will get an "list.map is not a function"

After looking around, underneath main query is for [Entity] and delegated results is for Product so my first attempt was to add following trasformation as a workaround:

transforms: [{
  transformResult: result => {
    result.data.productById = [result.data.productById]
    return result
  }
}]

it does fix an error with list.map error but introduces new one Cannot return null for non-nullable field Product.id

what is interesting through is that if you clone repo, put this javascript file into it and replace import with const { transformSchemaFederation } = require('./src/transform-federation'); and will run it with ts-node everything will work

i have checked that I'm using latest available version and there is no commits after

@mac2000
Copy link
Author

mac2000 commented Apr 26, 2020

Got it, to reproduce you gonna need update graphql-tools package to latest v5, then you gonna need to change your transform-federation.spec.ts by removing mocks and adding code from example then you will be able to reproduce map.list error

e.g. npm install graphql-tools@5

transform-federation.spec.ts

import { makeExecutableSchema, transformSchema, delegateToSchema } from 'graphql-tools';
import { transformSchemaFederation } from './transform-federation';
import { execute } from 'graphql/execution/execute';
import { DirectiveNode, parse, print, visit } from 'graphql/language';
import dedent = require('dedent');

describe('Transform Federation', () => {
  // it('should add a _service field'
  
  it('should resolve references', async () => {
    const executableSchema = makeExecutableSchema({
      typeDefs: `
    type Product {
      id: ID!
      name: String!
    }

    type Query {
      productById(id: String!): Product!
    }
      `,
      resolvers: {
        Query: {
          productById(source, { id }) {
            return {id: "1", name: "product1"};
          },
        }
      },
    });

    const federationSchema = transformSchemaFederation(executableSchema, {
      Product: {
        keyFields: ['id'],
        extend: true,
        resolveReference(reference: any, context: { [key: string]: any }, info) {
          return delegateToSchema({
            schema: info.schema,
            operation: 'query',
            fieldName: 'productById',
            args: {
              id: reference.id,
            },
            context,
            info,
          });
        },
      },
    });

    expect(
      await execute({
        schema: federationSchema,
        document: parse(`
          query{
            _entities (representations: {
              __typename:"Product"
              id: "1"
            }) {
              __typename
              ...on Product {
                id
                name
              }
            }
          } 
        `),
      }),
    ).toEqual({
      data: {
        _entities: [
          {
            __typename: 'Product',
            id: '1',
            name: 'product1',
          },
        ],
      },
    });
  });

  // it('should throw and error when adding resolveReference on a scalar'
});

and test result:

Transform Federation › should resolve references

    expect(received).toEqual(expected) // deep equality

    - Expected  - 8
    + Received  + 3

      Object {
    -   "data": Object {
    -     "_entities": Array [
    -       Object {
    -         "__typename": "Product",
    -         "id": "1",
    -         "name": "product1",
    -       },
    +   "data": null,
    +   "errors": Array [
    +     [GraphQLError: list.map is not a function],
        ],
    -   },
      }

@mac2000
Copy link
Author

mac2000 commented Apr 26, 2020

Hm, seems like I did get into version mismatch between projects and it seems that we will have similar issues for a while until graphql v15 and graphql-tools v5 becomes mainstream

@mac2000
Copy link
Author

mac2000 commented Apr 29, 2020

BTW most of documented transformations are available only in graphql-tools@5, so for example at moment we can not remove fields on type with FilterObjectFields because it is introduced in graphql-tools@5

@stijnbernards
Copy link

Did you manage to resolve this issue @mac2000

@mac2000
Copy link
Author

mac2000 commented Sep 23, 2020

Yes and no, oh it was so long time ago, I even do not remember all the details

Instead of having separate mesh/transformer/whatever service I just bring code pieces directly in gateway, so at that moment gateway looks something like:

const gateway = new gateway({
  services: [
    {name: 'foo', 'url': 'https://foo.com/graphql'},
    {name: 'bar', 'url': 'https://bar.com/graphql', transformations: include('./transformations/bar')},
  ]
})

so before starting gateway, the same way he is working we are dretrieving remote schema, applying required transsformations and make it executable - profit

but at the very end we have managed how to implement federation in dotnet so at moment everything works out of the box without any of such tricks

also I see a PR here from me, seems like project at moment is not alive, hopefully owners are well

@stijnbernards
Copy link

@mac2000 Thanks for the detailed explanation.
I think I'm going with the same solution to implement federation in our backend instead of using this package.

Project seems to be dead for now :(

@janhelleman
Copy link

@0xR what are your plans?

@0xR
Copy link
Owner

0xR commented Oct 3, 2020

I'm working on some other things right now, I might review a PR with a fix.

@kre8or69
Copy link

kre8or69 commented Jan 6, 2021

SO been digging around trying to solve this issue myself and happy to say i did find a solution that at least works for me.

Problem seems to exist because the query thats being run is coming from the federation part of the system so its setting the return type to [Entity]! as @mac2000 said. I fetched the expected return type from the schema and set it as the return type on the delegateToSchema function and seems to be resolving correctly now!

    Query: {
        extend: true,
    },
    User: {
        keyFields: ['id'],
        resolveReference(reference, context, info) {
            return delegateToSchema({
                schema: info.schema,
                operation: 'query',
                fieldName: 'user',
                args: {
                    id: reference.id,
                },
                context: context,
                info: info,
                returnType: info.schema._typeMap.User
            });
        },
    }

Not sure how we could put a fix for this in as its probably case by case where its actually required and might just depend on what backend service you're using, but maybe could add something to docs?

@yaacovCR
Copy link

yaacovCR commented Jan 6, 2021

Ahah. Idea in newest version of tools is to either use returnType or a transformResult method within a transform that will convert from single result to array, similar to #5 (comment)

@kre8or69
Copy link

kre8or69 commented Jan 7, 2021

Yeah I tried transforming the result but ran into the same issues with type validation and fields saying they are non-null. Even after changing the type to allow null fields the results never got merged into the final response.

Still not sure what the best way forward is. It would be far easier to just have a federation supported backend haha!

@yaacovCR
Copy link

yaacovCR commented Jan 7, 2021 via email

@kre8or69
Copy link

kre8or69 commented Jan 7, 2021

Hmm yeah you're right. If I set info.returnType to null before it delegates to the schema, it pulls the correct return type from the query being performed.

@yaacovCR
Copy link

yaacovCR commented Jan 7, 2021

I think safer than overriding the info object is modifying what you pass info delegateToSchema, either via creating a new modified info to pass or just using the returnType option explicitly to set to correct type or null as you have discovered!

@janhelleman
Copy link

janhelleman commented Feb 24, 2021

I can confirm setting a returnType works thanks @kre8or69 and @yaacovCR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants