Closed lukasbals closed 1 year ago
Many thanks for raising this bug report @lukasbals. :bug: We will now attempt to reproduce the bug based on the steps you have provided.
Please ensure that you've provided the necessary information for a minimal reproduction, including but not limited to:
If you have a support agreement with Neo4j, please link this GitHub issue to a new or existing Zendesk ticket.
Thanks again! :pray:
Many thanks for raising this bug report @lukasbals. :bug: We will now attempt to reproduce the bug based on the steps you have provided.
Please ensure that you've provided the necessary information for a minimal reproduction, including but not limited to:
If you have a support agreement with Neo4j, please link this GitHub issue to a new or existing Zendesk ticket.
Thanks again! :pray:
Hi @lukasbals! Thanks for reaching out.
When I tried to reproduce this bug I actually got a different error message Error: "Invitation" defined in resolvers, but not in schema
which is due to the @exclude
directive of the type Invitation
.
If I remove the @exclude
directive no error gets thrown and the server starts up.
Do you experience the same when you try it on your end?
Hi @lukasbals! Thanks for reaching out. When I tried to reproduce this bug I actually got a different error message
Error: "Invitation" defined in resolvers, but not in schema
which is due to the@exclude
directive of the typeInvitation
. If I remove the@exclude
directive no error gets thrown and the server starts up. Do you experience the same when you try it on your end?
Hi @tbwiss,
thanks for reproducing it. I already tried to remove the @exlude
and it doesn't change anything for me.
One interesting thing: If I log the customResolvers
in the node_modules/@neo4j/graphql/dist/schema/get-custom-resolver-meta.js
file in the getCustomResolverMeta
function I get undefined
which seems to be wrong ... and the reason why the error is thrown.
Hmm yes, that's interesting @lukasbals. That could be the reason why you get the error.
Just for completion's sake, here is the full code I used to reproduce the reported bug:
import { Neo4jGraphQL } from "@neo4j/graphql";
import * as neo4j from "neo4j-driver";
import { ApolloServer, gql } from "apollo-server";
const typeDefs = gql`
type Invitation @exclude {
id: ID! @id
invitedEmail: String!
isAccepted: Boolean! @default(value: false) @readonly
isInvited: [User!]! @relationship(type: "IS_INVITED", direction: IN)
invitedBy: User! @relationship(type: "INVITED_BY", direction: IN) @private
invitedTo: Project @relationship(type: "INVITED_TO", direction: IN)
status: InvitationStatus! @customResolver(requires: ["createdAt", "isAccepted"])
createdAt: DateTime! @timestamp(operations: [CREATE])
updatedAt: DateTime! @timestamp(operations: [CREATE, UPDATE])
}
type User {
name: String
}
type Project {
name: String
}
enum InvitationStatus {
PENDING
ACCEPTED
EXPIRED
}
`;
async function run() {
const driver = neo4j.driver(
"neo4j://localhost:7687",
neo4j.auth.basic("XXXXX", "XXXXXXXX")
);
const resolvers = {
Invitation: { status: () => "text" },
};
const neoSchema = new Neo4jGraphQL({
typeDefs: typeDefs,
driver,
resolvers,
});
const schema = await neoSchema.getSchema();
const server = new ApolloServer({
schema,
sandbox: false,
context: ({ req }) => ({ req }),
});
server.listen().then(() => console.log("Online in port 4000"));
}
run();
With these dependencies (same as you posted above):
"dependencies": {
"@neo4j/graphql": "^3.0.3",
"apollo-server": "^3.11.1",
"neo4j-driver": "^5.0.1"
}
Hi @tbwiss,
one thing I found out: If I uncomment the if
in line 42
of the node_modules/@neo4j/graphql/dist/schema/get-custom-resolver-meta.js
file the server starts up and works fine. If I log the customResolvers
then they are not undefined
(at least in the second log that appears).
See image and test output below:
Logs when running the tests:
PASS test/integration/invitation/invitations.test.ts
● Console
console.log
CR: { status: [Function: status] }
at getCustomResolverMeta (node_modules/@neo4j/graphql/src/schema/get-custom-resolver-meta.ts:65:5)
at Array.reduce (<anonymous>)
at Array.map (<anonymous>)
console.log
CR: undefined
at getCustomResolverMeta (node_modules/@neo4j/graphql/src/schema/get-custom-resolver-meta.ts:65:5)
at Array.reduce (<anonymous>)
at Array.map (<anonymous>)
So for me it seems like this getCustomResolverMeta
function is called multiple times and the first time it is called on startup the customResolvers
are missing (undefined
). What do you think?
One more thing that comes to my mind: We are using apollo-server-express
and not apollo-server
... but I assume this shouldn't be a problem 🤔
Okay @lukasbals. To debug this some more, could you check/log if the customResolvers
variable contains any data here in this get-nodes.ts
file: https://github.com/neo4j/graphql/blob/dev/packages/graphql/src/schema/get-nodes.ts#L124 ?
Also, if possible, could you please share more details of your server code? I mean the code that in my snippet above is located in the run()
function. To double check if there is something there that may cause your problem.
Hi @tbwiss,
thanks for your investigations!
This is the output if I log the customResolvers
in the get-nodes.js
file:
Determining test suites to run...Custom resolvers in get-nodes: undefined
Custom resolvers in get-custom-resolver: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
Custom resolvers in get-nodes: undefined
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-custom-resolver: { status: [Function: status] }
at getCustomResolverMeta (node_modules/@neo4j/graphql/src/schema/get-custom-resolver-meta.ts:65:5)
at Array.reduce (<anonymous>)
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: { status: [Function: status] }
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-custom-resolver: undefined
at getCustomResolverMeta (node_modules/@neo4j/graphql/src/schema/get-custom-resolver-meta.ts:65:5)
at Array.reduce (<anonymous>)
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
console.log
Custom resolvers in get-nodes: undefined
at node_modules/@neo4j/graphql/src/schema/get-nodes.ts:139:9
at Array.map (<anonymous>)
Here our server.ts
file:
import * as dotenv from 'dotenv';
dotenv.config();
import { Neo4jGraphQL } from '@neo4j/graphql';
import neo4j, { Driver } from 'neo4j-driver';
import typeDefs from './typeDefs';
import resolvers from './resolvers';
import { generate, OGM } from '@neo4j/graphql-ogm';
import express, { Request, Response } from 'express';
import { ApolloServer } from 'apollo-server-express';
import compression from 'compression';
import cors from 'cors';
import helmet from 'helmet';
import path from 'path';
import { createUser, createUserValidator } from './webhooks/users';
import { Neo4jGraphQLAuthJWTPlugin } from '@neo4j/graphql-plugin-auth';
import authMiddleware from './middleware/authMiddleware';
import { downloadDocument } from './downloads/documents';
import { ModelMap } from './types/generated';
import { setupGoogleDriveClient } from './integrations/googleDrive';
const app = express();
app.use(cors());
app.use(helmet({ contentSecurityPolicy: false }));
app.use(compression());
app.use(express.json());
const driver: Driver = neo4j.driver(
process.env.NEO4J_URI || 'bolt://localhost:7687',
neo4j.auth.basic(process.env.NEO4J_USER || 'neo4j', process.env.NEO4J_PASSWORD || 'letmein')
);
const auth = new Neo4jGraphQLAuthJWTPlugin({
// When testing the token should not ve verified because the token for testing is expired and
// we don't want to be forced to set the AUTH0_SECRET for testing
noVerify: process.env.NODE_ENV === 'test',
secret: process.env.AUTH0_SECRET ? process.env.AUTH0_SECRET.replace(/\\n/g, '\n') : '',
rolesPath: 'permissions',
});
const drive = setupGoogleDriveClient();
const ogm = new OGM<ModelMap>({ typeDefs, driver });
const neoSchema: Neo4jGraphQL = new Neo4jGraphQL({
typeDefs,
resolvers,
driver,
plugins: { auth },
});
app.get('/.well-known/apollo/server-ready', (req: Request, res: Response) => {
res.json({ status: 'pass' });
});
export const startApolloServer = async (): Promise<ApolloServer> => {
const schema = await neoSchema.getSchema();
const server = new ApolloServer({
schema: schema,
context: ({ req }) => ({ req }),
cache: 'bounded',
});
await ogm.init();
await server.start();
server.applyMiddleware({
app,
path: '/',
onHealthCheck: async () => {
try {
await driver.verifyConnectivity();
} catch (error) {
console.error('Health check failed');
throw new Error('Unable to verify connection.');
}
},
});
const port: number = parseInt(process.env.GRAPHQL_LISTEN_PORT || '4001');
if (process.env.NODE_ENV !== 'test' && !process.env.GENERATE) {
app.listen({ port }, (): void => console.log(`🚀 Server ready at localhost:${port}`));
}
return server;
};
export { driver, ogm, auth, drive };
if (process.env.NODE_ENV !== 'test') {
app.post('/webhooks/users', createUserValidator, createUser);
app.get('/downloads/documents/:document', authMiddleware, downloadDocument);
startApolloServer();
}
Thank you @lukasbals. There is nothing in your server code that strikes me as wrong.
One more thing you can check, is the this.schemaDefinition.resolvers
variable in this file https://github.com/neo4j/graphql/blob/dev/packages/graphql/src/classes/Neo4jGraphQL.ts#L236 is undefined
as well?
If this.schemaDefinition.resolvers
is undefined
it means the resolvers
passed in here:
const neoSchema: Neo4jGraphQL = new Neo4jGraphQL({
typeDefs,
resolvers,
driver,
plugins: { auth },
});
are undefined
.
hi @tbwiss,
I added a console.log
in the generateSchema
method of the Neo4jGraphQL
class and got the following result:
Determining test suites to run...Custom resolvers in the Neo4jGraphQL class genereateSchema: undefined
Custom resolvers in get-custom-resolver: undefined
console.log
Custom resolvers in the Neo4jGraphQL class genereateSchema: {
Mutation: {
claimPlots: [AsyncFunction: claimPlots],
createReport: [AsyncFunction: createNewReport],
unClaimPlots: [AsyncFunction: unClaimPlots],
acceptInvitation: [AsyncFunction: acceptInvitation],
inviteUserToProject: [AsyncFunction: inviteUserToProject],
downloadDocument: [AsyncFunction: downloadDocument],
getInTouch: [AsyncFunction: getInTouch]
},
Query: {
documents: [AsyncFunction: documents],
checkPlot: [AsyncFunction: checkPlot],
plotsDetail: [AsyncFunction: plotsDetail],
searchPlots: [AsyncFunction: searchPlots],
invitations: [AsyncFunction: invitations],
getReports: [AsyncFunction: getReports]
},
Invitation: { status: [Function: status] }
}
at Neo4jGraphQL.generateSchema (node_modules/@neo4j/graphql/src/classes/Neo4jGraphQL.ts:227:16)
console.log
Custom resolvers in get-custom-resolver: { status: [Function: status] }
at getCustomResolverMeta (node_modules/@neo4j/graphql/src/schema/get-custom-resolver-meta.ts:65:5)
at Array.reduce (<anonymous>)
at Array.map (<anonymous>)
console.log
Custom resolvers in the Neo4jGraphQL class genereateSchema: undefined
at Neo4jGraphQL.generateSchema (node_modules/@neo4j/graphql/src/classes/Neo4jGraphQL.ts:227:16)
console.log
Custom resolvers in get-custom-resolver: undefined
at getCustomResolverMeta (node_modules/@neo4j/graphql/src/schema/get-custom-resolver-meta.ts:65:5)
at Array.reduce (<anonymous>)
at Array.map (<anonymous>)
It's also undefined
on the first call but at some point, it's defined 🤔
@lukasbals I might have found the issue.
In your server code above, you use the @neo4j/graphql
OGM. Could you try passing the resolvers there too? Like:
const ogm = new OGM<ModelMap>({ typeDefs, driver, resolvers });
Background:
The OGM uses the Neo4jGraphQL
class under the hood which may cause the error you experience.
Hi @tbwiss,
thanks for the hint! It seems like it helps. The app starts and runs, but unfortunately, it leads to a new error that makes no sense to me. When calling a query that has a custom resolver and in the custom resolver I use the OGM to query something I get an Unauthenticated
error:
@neo4j/graphql:auth Could not get .req or .request from context +0ms
Error: Unauthenticated
at Model.find (/Users/lukasbals/treely/api/node_modules/@neo4j/graphql-ogm/src/classes/Model.ts:149:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async invitations (/Users/lukasbals/treely/api/src/resolvers/invitations/invitations.ts:37:25)
Do you have any idea what's the problem here?
Great! Hmm that's interesting @lukasbals. I see in your server code above that you already pass the request in the context of the ApolloServer:
const server = new ApolloServer({
schema: schema,
context: ({ req }) => ({ req }),
cache: 'bounded',
});
That's usually what gets forgotten and causes a similar error.
However, I see that it errs in the Model.find()
which is in the OGM.
Is it possible, haven't tried that myself, that you can pass the request in the context
of the Model.find()
method (ref: https://github.com/neo4j/graphql/blob/dev/packages/ogm/src/classes/Model.ts#L90-L98)?
Thanks @tbwiss!
I tried to pass the context that I get in the custom resolver to the Model.find()
call. This resulted in an infinite loop.
How can I pass just the request to the Model.find()
call in a custom resolver? Can you please give me an example?
@lukasbals, okay! Does any of this here help: https://github.com/neo4j/graphql/blob/dev/packages/ogm/tests/integration/additional-labels.int.test.ts#L67-L88 ?
Hi @tbwiss,
thanks!
I think I know what the problem is.
If I pass the resolvers to the OGM and I use the OGM inside a custom resolver I run into an infinite loop since the custom resolver calls itself again and again. The solution I found, for now, is to exclude the custom Query
and Mutation
resolvers when passing the resolvers
to the OGM. See an example here:
const ogm = new OGM<ModelMap>({
typeDefs,
resolvers: { ...resolvers, Query: {}, Mutation: {} },
driver,
});
What do you think? Should that maybe be part of the OGM library, to exclude the Query
and Mutation
resolvers?
Hi @lukasbals, glad you found a solution!
To be honest I'm not entirely sure as I'm not too familiar with the OGM but I'll bring this up with the team.
Thanks!
@lukasbals, we're currently working on a feature that may mitigate or address this: https://github.com/neo4j/graphql/pull/2761 We'll also look into the infinite loop issue as part of that linked feature.
@tbwiss This would fix the issue. But I would rather skip the validation on the ORM initialization and not on the Neo4jGraphql initialization.
You should be able to do something along the line of:
const ogm = new OGM<ModelMap>({
typeDefs,
resolvers,
driver,
config: {
startupValidation: {
typeDefs: true,
resolvers: false,
},
},
});
when https://github.com/neo4j/graphql/pull/2761 is released.
Closing this bug report as this can be addressed with the changes in https://github.com/neo4j/graphql/pull/2761. Please feel free to re-open this issue otherwise.
Describe the bug After the migration from the
@computed
directive to the@customResolver
directive it throws an errorError: Custom resolver for status has not been provided
.The only change is from
status: InvitationStatus! @computed(from: ["createdAt", "isAccepted"])
tostatus: InvitationStatus! @customResolver(requires: ["createdAt", "isAccepted"])
.Type definitions
Part of the type definition:
Part of the resolvers:
To Reproduce Steps to reproduce the behavior:
Expected behavior The app runs without throwing the error.
Screenshots Not needed.
System (please complete the following information):