0

I have a graphql interface like this

interface Verifiable {
    verifications: [Verification!]!
}

I then implement that interface like this:

type Address implements Verifiable  @node(labels: ["Address", "Verifiable"]) @key(fields:"id"){
  id: ID! @id
  street: String
  suburb: String
  city: String
  stateProvince: String
  country: String
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
}

On running the project, I am getting this error:

Apr-12-2023 21:16:52    error   GQL API        The schema is not a valid GraphQL schema.. Caused by:
[<unnamed>] Interface field VerifiableEventPayload.verifications expected but AddressEventPayload does not provide it.

GraphQL request:582:3
581 | interface VerifiableEventPayload {
582 |   verifications: [Verification!]!
    |   ^
583 | }

GraphQL request:712:1
711 |
712 | type AddressEventPayload implements VerifiableEventPayload {
    | ^
713 |   id: ID!

Implementing my graphql server with apollo server v4. The error however occurs at getting the neo4j schema before I instantiate the apollo server.

My package.json dependencies:

"dependencies": {
    "@apollo/server": "^4.5.0",
    "@apollo/subgraph": "^2.3.4",
    "@escape.tech/graphql-armor": "1.8.1",
    "@graphql-tools/load": "^7.8.13",
    "@graphql-tools/load-files": "^6.6.1",
    "@graphql-tools/merge": "^8.4.0",
    "@graphql-tools/schema": "^9.0.17",
    "@graphql-tools/utils": "^9.2.1",
    "@neo4j/cypher-builder": "^0.2.1",
    "@neo4j/graphql": "^3.17.1",
    "@neo4j/graphql-ogm": "^3.17.1",
    "@neo4j/graphql-plugin-auth": "^2.1.0",
    "@neo4j/introspector": "^1.0.3",
    "bcrypt": "^5.1.0",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "graphql": "^16.6.0",
    "graphql-tag": "^2.12.6",
    "jsonwebtoken": "^9.0.0",
    "lodash": "^4.17.21",
    "neo4j-driver": "^5.6.0",
    "nodemailer": "^6.9.1",
    "winston": "^3.8.2"
  }

Here is the index file where I am setting up the server environment.

import express, {Express} from 'express';
import {CypherOperatorEngine, Neo4jGraphQL, Neo4jGraphQLSubscriptionsSingleInstancePlugin} from '@neo4j/graphql';
import { Neo4jGraphQLAuthJWTPlugin } from '@neo4j/graphql-plugin-auth';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { ApolloServer } from '@apollo/server';
import neo4jDriver from 'neo4j-driver';
import { OGM } from '@neo4j/graphql-ogm';
import dotenv from 'dotenv';
import {expressMiddleware} from '@apollo/server/express4'
import http from 'http';
import cors from 'cors'; 
import resolvers from './graphql/resolvers/index.js';
import typeDefs from './graphql/schema/types/index.js';
import { BayGraphQLServerContext, IAuthSubject } from './app.d.js';
import BayRegistryAuthenticationPlugin from './middleware/authentication.js';
import {logger} from './middleware/logging.js';
import apolloArmorProtection from "./middleware/armor.js";
import { BayJwt } from './services/utils/security/jwt.js';
import { Server, WebSocket, WebSocketServer } from 'ws';
import { useServer } from "graphql-ws/lib/use/ws"

dotenv.config();

logger.info(`${(new Date()).getFullYear()} Some Company (Pvt) Limited.`);
logger.info("GQL API v0.0.1 ");

const driver = neo4jDriver.driver(
    process.env.NEO4J_DATABASE_URI="bolt://127.0.0.1:7687",
    neo4jDriver.auth.basic(
        process.env.NEO4J_DATABASE_USERID="neo4j", 
        process.env.NEO4J_DATABASE_SECRET="graphista"
    )
);

const ogm = new OGM({typeDefs, resolvers});

const app:Express = express();

const keyPublic="";

app.use(express.json());

app.use(express.urlencoded({extended: true}));

const httpServer = http.createServer(app);

const neo4jGraphql = new Neo4jGraphQL({
    typeDefs,
    driver,
    resolvers,
    config:{
        driverConfig:{
            database: process.env.NEO4J_DATABASE
        },
    },
    plugins:{
        subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(),
        auth: new Neo4jGraphQLAuthJWTPlugin({
            secret: keyPublic,
            globalAuthentication: false,
        }),
    },
    features:{
        populatedBy:{
            callbacks:{
                copyright: (parent, args, context)=>"Some Company Private Limited"
            }
        }
    }
});

const webSocketServer = new WebSocketServer({
    server: httpServer,
    path: "api/v1/gql"
});


Promise.all([
    neo4jGraphql.getSubgraphSchema(),
    neo4jGraphql.assertIndexesAndConstraints({
        options:{
            create: true
        }
    }),
    //TODO had to manually change the @neo4j/graphql-ogm OGM.js to call neoSchema.getSubgraphSchema() instead of neoSchema.getSchema
    //ogm.init()
]).then(([schema])=>{
    logger.info("Neo subgraph schema generated, instantiating apollo server");

    const serverCleanup = useServer({schema}, webSocketServer);

    const apolloServer = new ApolloServer<BayGraphQLServerContext>({
        schema,
        csrfPrevention: true,
        introspection: true,
        //...apolloArmorProtection,//@escape.tech/graphql armour protection
        plugins:[
            //...apolloArmorProtection.plugins,
            ApolloServerPluginDrainHttpServer({httpServer}),
            BayRegistryAuthenticationPlugin(),
            {
                async serverWillStart(){
                    logger.info("Apollo server starting inline plugin");
                    return {
                        async drainServer(){
                            await serverCleanup.dispose();
                        }
                    }
                }
            }
        ]
    });
    apolloServer.start().then(()=>{
        app.use(
            process.env.GRAPHQL_API_PATH="/api/:version/gql",
            cors<cors.CorsRequest>(),
            //logger,
            expressMiddleware(apolloServer, {
                context: async ({req, res}) =>{
                    let user: IAuthSubject={userUUID:null};
                    let jwt: IAuthSubject = {userUUID: null};
                    await BayJwt.JwtDecode(req, req.headers?.authorization?.split(" ")[1] || "")
                        .then(decoded=>{
                            logger.info(`Generating apollo context, JWT OK!`);
                            user=decoded;
                            BayJwt.JwtLoadProfile(decoded).then(profiled=>{
                                user = profiled;
                            });
                    }).catch(error=>{
                        logger.error(error.message);
                    });
                    return {
                        req,res,user, jwt: user ? user : "", ogm,
                        cypherParams:{
                            company: "some company pvt limited"
                        }
                    }   
                },
            })
        )
    });
})
.catch(error=>{
    logger.error("Error generating neo4j schema and asserting constraints");
    logger.error(error.message);
    logger.error(JSON.stringify(error));
});

httpServer.listen(
    {
        port: process.env.GRAPHQL_API_PORT
    },
    ()=>{
        logger.info(`API started at ${process.env.GRAPHQL_API_PORT}`)
    }
);

The type definitions that are implementing interfaces and related interfaces are as below:

interface IVerifiable {
    #verifications: [Verification!]!
    isVerifiable: Boolean @default(value: false)
}

interface IValuable {
  valuations: [Valuation!]!
  isValuable: Boolean @default(value: false)
}

interface IReviewable{
  #reviews: [Persona!]!
  isReviewable: Boolean @default(value: false)
}

interface IOwnable {
  #ownerships: [Ownership!]!
  isOwnable: Boolean @default(value: false)
}

interface IPrivacy{
  #privateFields: [String!]!
  #privateFields3rdPartyAccessMode: PRIVATE_FIELD_3RD_PARTY_ACCESS_MODE!
  isConfidential: Boolean @default(value: false)
}

type Identity implements IVerifiable @node(labels: ["Identity", "Verifiable"]) @key(fields:"id"){
  id: ID! @id @readonly
  identity: String!
  type: String!
  identityUri: String
  subject: Persona @relationship(type: "IDENTIFIED_BY", direction: IN)
  issuer: Persona @relationship(type: "ISSUED_BY", direction: OUT)
  isVerifiable: Boolean!
  verifications: [Verification!]! @relationship(type: "WAS_VERIFIED", direction: OUT)
  issueDate: Date
  expiryDate: Date
  description: String
  comments: String
}


type Address implements IVerifiable  @node(labels: ["Address", "Verifiable"]) @key(fields:"id"){
  id: ID! @id @readonly
  suite: String
  street: String
  suburb: String
  city: String
  stateProvince: String
  country: String
  geoLocation: Point
  geoBoundary: [Point!]
  isResidential: Boolean @default(value: true)
  domicili: [Domicilium!]! @relationship(type: "IS_DOMICILIUM", direction: OUT)
  verifications: [Verification!]! @relationship(type: "WAS_VERIFIED", direction: OUT)
  isVerifiable: Boolean! @default(value: true)
}

type Domicilium implements IVerifiable @node(labels: ["Domicilium", "Residence", "Verifiable"]) @key(fields: "domiciliumId"){
  domiciliumId: ID! @id @readonly
  address: Address! @relationship(type: "IS_DOMICILIUM", direction: IN)
  resident: Persona! @relationship(type: "HAS_DOMICILIUM", direction: IN)
  startDate: Date
  stopDate: Date
  owned: Boolean @default(value: true)
  proofOfDomiciliumUri: String
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
  isVerifiable: Boolean! @default(value: true)
}

type Ownership implements IVerifiable @node(labels: ["Ownership", "Verifiable"]) @key(fields: "ownershipId"){
    ownershipId: ID! @id @readonly
    predecessor: Ownership @relationship(type: "CHANGE_OF_OWNERSHIP", direction: IN)
    owners: [Persona!]! @relationship(type: "HAS_OWNERSHIP", direction: IN)
    asset: Asset! @relationship(type: "OWNERSHIP_OF", direction: OUT)
    verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
    isVerifiable: Boolean!
    startDate: Date
    stopDate: Date
}
"""
  Valuation of a IValuable item. Self valuation is allowed
"""
type Valuation implements IVerifiable @node(labels: ["Valuation"]) @key(fields: "valuationId"){
  valuationId: ID! @id @readonly
  selfValuation: Boolean! @default(value: true)
  desktopValuation: Boolean! @default(value: true)
  valuer: Persona @relationship(type: "DID_A_VALUATION", direction: IN)
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
  isVerifiable: Boolean!
  value: Float!
  asset: Asset! @relationship(type: "WAS_VALUED", direction: IN)
  valuationDate: DateTime
  isValuable: Boolean!
  expiryDate: DateTime
}


type Asset implements IAsset & IVerifiable & IOwnable & IValuable @node(labels:["Asset", "Verifiable", "Ownable", "Valuable"]) @key(fields: "assetId"){
  assetId: ID! @id @readonly
  isValuable: Boolean!
  verifications: [Verification!]! @relationship(type: "VERIFIED", direction: OUT)
  isVerifiable: Boolean!
  valuations: [Valuation!]! @relationship(type: "VALUED", direction: OUT)
  ownerships: [Ownership!]! @relationship(type: "OWNED", direction: IN)
  isAsset: Boolean!
  isOwnable: Boolean!
  specification: String!
  startDate: DateTime @timestamp(operations: [CREATE])
}

Any ideas what i am getting wrong?

Thanks in advance.

  • Your question is not showing the definitions of `VerifiableEventPayload` and `AddressEventPayload `, which are what the error is referring to. – cybersam Apr 12 '23 at 20:09
  • Thats the thing @cybersam. I do not have such type definitions. They seem to be autogenerated. – ltmutiwekuziwa Apr 12 '23 at 20:39
  • Could you provide some more details/code snippets of your server code? It would also be helpful if you could provide more details on the type definitions you use for the different subgraphs. `xxxEventPayload` types when using the `@neo4j/graphql` library are autogenerated if GraphQL subscriptions are used. hence it would be interesting to see your server code. – Thomas Wiss Apr 17 '23 at 07:15
  • Thanks @Thomas Wiss. This is the only implemented subgraph. – ltmutiwekuziwa Apr 17 '23 at 15:55
  • Okay. I assume you're planning to add more subgraphs in the future for federation to pay off. Are you by any chance using GraphQL subscriptions with the `@neo4j/graphql` library? I mean these subscriptions outlined here: https://neo4j.com/docs/graphql-manual/current/subscriptions/ – Thomas Wiss Apr 18 '23 at 06:00
  • Yes @Thomas Wiss. I intend to add more subgraphs, and indeed I am using these subscriptions. Here is the code where i configure that. ``` const neo4jGraphql = new Neo4jGraphQL({ typeDefs, driver, resolvers, config:{ driverConfig:{ database: process.env.NEO4J_DATABASE }, }, plugins:{ subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), auth: new Neo4jGraphQLAuthJWTPlugin({ secret: keyPublic, globalAuthentication: false, }), } }); ``` – ltmutiwekuziwa Apr 21 '23 at 05:52
  • @ltmutiwekuziwa, I just set up a single instance using federation and GraphQL subscriptions following this guide https://neo4j.com/docs/graphql-manual/current/guides/apollo-federation/. I don't experience the issue you describe. Is it possible that you merge the auto-generated schema of the neo4j/graphql schema with another schema? It would be helpful if you could share more of your code – Thomas Wiss Apr 21 '23 at 08:59
  • @ThomasWiss I have added the index.tx file where I am setting up the server and the type definitions of the interfaces and some of the types implementing those interfaces. Your assistance is much appreciated. I've had to comment out some of the interface definition fields to avoid the error and be able to continue working, but would very much want those fields to be part of the interface(s) – ltmutiwekuziwa Apr 22 '23 at 13:21
  • @ltmutiwekuziwa, I do not see any obvious errors in your index.js file. One thing that came to mind that you can test is to remove any usage of the `@neo4j/graphql-ogm`. under the hood, the `@neo4j/graphql-ogm` calls the `Neo4jGraphQL()` lib which means any config and arguments (like auth and subscrptions) that you pass to the `Neo4jGraphQL()` constructor also need to be passed to the `@neo4j/graphql-ogm` constructor. To find the error, I would therefore try to not use the `@neo4j/graphql-ogm`. – Thomas Wiss Apr 24 '23 at 08:10
  • @ltmutiwekuziwa also, as of today, authentication (the @auth directive) in Federation is not implemented when using `@neo4j/graphql`. That's however being worked on. – Thomas Wiss Apr 25 '23 at 12:19

0 Answers0