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.